Come posso creare un framework flessibile per gestire i risultati?


54

In particolare, qual è il modo migliore per implementare un sistema di risultati abbastanza flessibile da gestire il superamento di semplici risultati basati su statistiche come "uccidi x nemici".

Sto cercando qualcosa di più robusto di un sistema basato su statistiche e qualcosa di più organizzato e gestibile di "hardcode tutto come condizioni". Alcuni esempi impossibili o ingombranti in un sistema basato su statistiche: "Taglia un'anguria dopo una fragola", "Scendi dalla pipa mentre sei invincibile", ecc.

Risposte:


39

Penso che una sorta di soluzione robusta sarebbe quella di orientarsi verso gli oggetti.

A seconda del tipo di obiettivo che desideri supportare, hai bisogno di un modo per interrogare lo stato corrente del tuo gioco e / o la storia delle azioni / eventi che gli oggetti del gioco (come il giocatore) hanno fatto.

Supponiamo che tu abbia una classe di Achievement di base come:

class AbstractAchievement
{
    GameState& gameState;
    virtual bool IsEarned() = 0;
    virtual string GetName() = 0;
};

AbstractAchievementcontiene un riferimento allo stato del gioco. È usato per interrogare le cose che stanno accadendo.

Quindi esegui implementazioni concrete. Usiamo i tuoi esempi:

class MasterSlicerAchievement : public AbstractAchievement
{
    string GetName() { return "Master Slicer"; }
    bool IsEarned()
    {
        Action lastAction = gameState.GetPlayerActionHistory().GetAction(0);
        Action previousAction = gameState.GetPlayerActionHistory().GetAction(1);
        if (lastAction.GetType() == ActionType::Slice &&
            previousAction.GetType() == ActionType::Slice &&
            lastAction.GetObjectType() == ObjectType::Watermelon &&
            previousAction.GetObjectType() == ObjectType::Strawberry)
            return true;
        return false;
    }
};
class InvinciblePipeRiderAchievement : public AbstractAchievement
{
    string GetName() { return "Invincible Pipe Rider"; }
    bool IsEarned()
    {
        if (gameState.GetLocationType(gameState.GetPlayerPosition()) == LocationType::OVER_PIPE &&
            gameState.GetPlayerState() == EntityState::INVINCIBLE)
            return true;
        return false;
    }
};

Quindi spetta a te decidere quando verificare utilizzando il IsEarned()metodo. Puoi controllare ad ogni aggiornamento del gioco.

Un modo più efficiente sarebbe, ad esempio, avere un qualche tipo di gestore di eventi. Quindi registra gli eventi (come PlayerHasSlicedSomethingEvento PlayerGotInvicibleEvento semplicemente PlayerStateChanged) su un metodo che porterebbe il risultato nel parametro. Esempio:

class Game
{
    void Initialize()
    {
        eventManager.RegisterAchievementCheckByActionType(ActionType::Slice, masterSlicerAchievement);
        // Each time an action of type Slice happens,
        // the CheckAchievement() method is invoked with masterSlicerAchievement as parameter.
        eventManager.RegisterAchievementCheckByPlayerState(EntityState::INVINCIBLE, invinciblePiperAchievement);
        // Each time the player gets the INVINCIBLE state,
        // the CheckAchievement() method is invoked with invinciblePipeRiderAchievement as parameter.
    }
    void CheckAchievement(const AbstractAchievement& achievement)
    {
        if (!HasAchievement(player, achievement) && achievement.IsEarned())
        {
            AddAchievement(player, achievement);
        }
    }
};

13
stile nitpick: if(...) return true; else return false;è lo stesso direturn (...)
BlueRaja - Danny Pflughoeft

2
Questo è un ottimo modello per implementare un sistema di risultati. Inoltre, dimostra ancora l'idea che devi avere un modo per tenere traccia degli stati di gioco. Trovo che sia probabilmente l'idea più complicata.
Bryan Harrington,

@Spio sei un maestro ...! : D Soluzione semplice ed elegante. Congratulazioni.
Diego Palomar,

+1 Penso che un sistema di emissione / raccolta di eventi sia un ottimo modo per gestire questo problema.
ashes999,

14

In breve, i risultati vengono sbloccati quando viene soddisfatta una determinata condizione. Quindi è necessario essere in grado di produrre istruzioni if ​​per verificare la condizione desiderata.

Ad esempio, se vuoi sapere che un livello è stato completato o che un boss è stato sconfitto, devi fare in modo che la bandiera booleana diventi vera quando si verificano questi eventi.

Poi:

if(GlobalFlags.MasterBossDefeated == true && AchievementClass.MasterBossDefeatedAchievement == false)
{
    AchievementClass.MasterBossDefeatedAchievement = true;
    showModalPopUp("You defeated the Master Boss!  30 gamerscore");
}

Puoi renderlo complesso o semplicistico quanto basta per abbinare la condizione che desideri.

Alcune informazioni sui risultati di Xbox 360 sono disponibili qui .


2
+1 Ottimo articolo, ed essenzialmente quello che stavo per suggerire. Anche se vorrei che Modal ottenga il suo testo dal risultato stesso ... solo per evitare la caccia al testo se vuoi cambiare qualcosa.
Jesse Dorsey

@Noctrine - non dimenticare che qualsiasi codice pubblicato qui deve essere trattato come pseudo-codice - spesso è necessario utilizzare un codice semplificato per ottenere il risultato.
ChrisF,

1
Il link ai risultati di Xbox 360 è morto.
hangy


8

Che cosa succede se ogni azione che il giocatore intraprende invia un messaggio al AchievementManager? Quindi il manager può verificare internamente se determinate condizioni sono state soddisfatte. I primi oggetti pubblicano messaggi:

AchievementManager::PostMessage("Jump", "162");

AchievementManager::PostMessage("Slice", "Strawberry");
AchievementManager::PostMessage("Slice", "Watermelon");

AchievementManager::PostMessage("Kill", "Goomba");

E poi i AchievementManagercontrolli se deve fare qualcosa:

if (!strcmp(m_Message.name, "Slice") && !strcmp(m_LastMessage.name, "Slice"))
{
    if (!strcmp(m_Message.value, "Watermelon") && !strcmp(m_LastMessage.value, "Strawberry"))
    {
        // achievement unlocked!
    }
}

Probabilmente vorrai farlo con le enumerazioni invece delle stringhe. ;)


Questo è in linea con quello che stavo pensando, ma si basa ancora su un sacco di cose che vengono codificate in una funzione. A parte la manutenibilità, almeno ogni risultato deve essere verificato per l'idoneità ogni volta attraverso la funzione.
lti

2
Vero? Puoi mettere i condizionali in uno script esterno, purché tu abbia un modo per tenere traccia di ciò che è accaduto nel gioco.
knight666,

6
-1 questo puzza di cattivo design. Se hai intenzione di chiamare direttamente AchivementManager, rendi ciascuno di questi "messaggi" una funzione separata. Se hai intenzione di utilizzare i messaggi, crea un gestore dei messaggi in modo che anche altre classi possano utilizzare i messaggi (sono sicuro che Goomba sarebbe interessata a sapere che è stato ucciso) e di rimuovere quell'accoppiamento AchievementManagerin ogni classe (che è ciò che OP stava chiedendo come evitare in primo luogo). E usa un enum o classi separate per i tuoi messaggi, non letterali di stringa - usare letterali di stringa per passare attraverso lo stato è sempre una cattiva idea.
BlueRaja - Danny Pflughoeft il

4

L'ultimo disegno che ho usato si basava sull'avere un set di contatori persistenti per utente e quindi avere i risultati chiave su un certo contatore che colpiva un certo valore. La maggior parte erano una singola coppia obiettivo / contatore in cui il contatore sarebbe sempre solo 0 o 1 (e il risultato innescato su> = 1), ma puoi usarlo anche per "tizi X uccisi" o "forzieri X trovati". Significa anche che puoi impostare i contatori per qualcosa che non ha obiettivi e verrà comunque monitorato per un uso futuro.


3

Quando ho implementato gli obiettivi nella mia ultima partita, ho fatto tutto basato sulle statistiche. I risultati vengono sbloccati quando le nostre statistiche raggiungono un determinato valore. Prendi in considerazione Modern Warfare 2: il gioco sta monitorando tonnellate di statistiche! Quanti scatti hai realizzato con SCAR-H? Quante miglia hai corso durante l'utilizzo del vantaggio leggero?

Quindi, nella mia implementazione, ho semplicemente creato un motore statistico, quindi ho creato un gestore dei risultati che esegue query molto semplici per controllare lo stato dei risultati durante il gioco.

Mentre la mia implementazione è piuttosto semplicistica, fa il suo lavoro. Ne ho scritto e condiviso le mie domande qui .


2

Usa il calcolo degli eventi . Quindi crea alcune condizioni preliminari e azioni che vengono applicate dopo aver soddisfatto le condizioni preliminari:

  • precondizioni: hai ucciso 1000 nemici, hai 2 gambe
  • azioni: dammi lecca-lecca, dammi super-duper-shotgun-13
  • azioni per la prima volta: dì "sei così fantastico!"

Usalo come (non ottimizzato per la velocità!):

  • Memorizza tutte le statistiche.
  • Statistiche delle query per i presupposti.
  • Applica azioni.
  • Applicare una sola volta le azioni.

Se vuoi renderlo veloce:

  • Cache qualunque cosa tu voglia, conservane parti in alcuni alberi, hash ...
  • Apporta le modifiche in modo incrementale in modo da non applicare tutte le azioni in ogni momento ma queste che sono nuove ...)

Nota

È difficile dare i migliori consigli poiché tutte le cose hanno pro e contro.

  • "Qual è la migliore struttura di dati?" implica "Quali operazioni vuoi fare di più? Ricerca, rimozione, aggiunta ..."
  • Fondamentalmente pensi a queste proprietà: facilità di codifica, velocità, chiarezza, dimensioni ...

0

Cosa c'è che non va nei controlli IF dopo che si è verificato l'evento di realizzazione?

if (Cinimatics.done)
   Achievement.get(CINIMATICS_SEEN);

if (EnemiesKiled > 200)
   Achievement.get(KILLER);

if (Damage > 2000 && TimeSinceFirstDamage < 2000)
   Achievement.get(MEAT_SHIELD);

InvitationAccepted = Invite.send(BestFriend);
if (InvitationAccepted)
   Achievement.get(A_FRIEND_IN_NEED);

3
'Sto cercando qualcosa di più organizzato e gestibile di "hardcode tutti come condizioni". '. Anche se questo è sicuramente un buon metodo KISS per un piccolo gioco.
Il comunista Duck il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.