成果を処理するための柔軟なフレームワークを設定するにはどうすればよいですか?


54

具体的には、「キルx敵」などの単純な統計駆動型の成果を超える処理を行うのに十分な柔軟性を備えた達成システムを実装する最良の方法は何ですか。

統計ベースのシステムよりも堅牢なものと、「それらをすべて条件としてハードコードする」よりも組織的で保守可能なものを探しています。統計ベースのシステムでは不可能または扱いにくい例:「イチゴの後にスイカをスライスする」、「無敵の間にパイプを下る」など。

回答:


39

一種の堅牢な解決策は、オブジェクト指向の方法を採用することだと思います。

サポートしたい成果に応じて、ゲームの現在の状態やゲームオブジェクト(プレーヤーなど)が行ったアクション/イベントの履歴を照会する方法が必要です。

次のような基本的な達成クラスがあるとします。

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

AbstractAchievementゲームの状態への参照を保持します。発生していることを照会するために使用されます。

次に、具体的な実装を行います。あなたの例を使用しましょう:

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;
    }
};

その後、IsEarned()メソッドを使用してチェックするタイミングを決定するのはあなた次第です。ゲームの更新ごとに確認できます。

より効率的な方法は、たとえば、ある種のイベントマネージャーを使用することです。そして、パラメータで実績を取得するメソッドにイベント(PlayerHasSlicedSomethingEventまたは、PlayerGotInvicibleEventまたは単にPlayerStateChanged)を登録します。例:

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
スタイルのnitpick: if(...) return true; else return false;と同じですreturn (...)
BlueRaja-ダニーPflughoeft

2
これは、達成システムを実装するための非常に優れたテンプレートです。さらに、ゲームの状態を追跡する方法を用意する必要があるという考えも示しています。おそらく最も複雑なアイデアだと思います。
ブライアンハリントン

@スピオあなたは名人です...!:Dシンプルでエレガントなソリューション。おめでとう
ディエゴパロマー

+1イベントの発行/収集システムは、この問題を処理するのに最適な方法だと思います。
ashes999 14年

14

要するに、特定の条件が満たされると、成果はロック解除されます。そのため、必要な条件をチェックするifステートメントを作成できる必要があります。

たとえば、レベルが完了したか、ボスが敗北したことを知りたい場合は、これらのイベントが発生したときにブール値フラグをtrueにする必要があります。

次に:

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

必要に応じて、これを複雑または単純化して、目的の条件に一致させることができます。

Xbox 360の実績に関する情報は、こちらでご覧いただけます


2
+1すばらしい記事であり、基本的に私が提案しようとしていたもの。モーダルにアチーブメント自体からテキストを取得させますが、何かを変更したい場合にテキストのハンティングを避けるためだけです。
ジェシードーシー

@Noctrine-ここに投稿されたコードは擬似コードとして扱われることを忘れないでください-ポイントを理解するために単純化されたコードを使用することがしばしば必要です。
ChrisF

1
Xbox 360の実績のリンクは機能していません。
ハンジー


8

プレイヤーが行うすべてのアクションがメッセージを投稿した場合はどうなりますAchievementManagerか?その後、マネージャーは特定の条件が満たされているかどうかを内部的に確認できます。最初のオブジェクトはメッセージを投稿します:

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

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

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

そして、AchievementManager何かをする必要があるかどうかをチェックします:

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

ただし、文字列ではなく列挙型を使用してこれを行うことをお勧めします。;)


これは私が考えていた通りですが、それでも1つの関数にハードコードされているものに依存しています。保守性はさておき、少なくともすべての成果は、機能を通じて毎回適格性をチェックする必要があります。
lti

2
しますか?ゲームで何が起こったのかを追跡する方法がある限り、外部スクリプトに条件を入れることができます。
knight666

6
-1この悪いデザインの臭い。AchivementManagerを直接呼び出す場合は、それらの「メッセージ」をそれぞれ個別の関数にします。あなたがメッセージを使用するつもりなら、他のクラスは、(私は確かクリボーは、彼が殺されています知っているに興味があるんだ)と同様のメッセージを使用することができ、およびに結合することを削除するので、メッセージマネージャを作るAchievementManager内のすべてのクラス(OPがそもそも避ける方法を尋ねていたものです)。また、メッセージには文字列リテラルではなく列挙型または個別のクラスを使用します。文字列リテラルを使用して状態をやり取りすることは常に悪い考えです。
BlueRaja-ダニーPflughoeft

4

私が使用した最後のデザインは、ユーザーごとの永続的なカウンターのセットに基づいており、特定のカウンターを特定の値に到達させるキーポイントを達成しています。ほとんどはカウンターが0または1である単一のアチーブメント/カウンターペアでしたが(アチーブメントは> = 1でトリガーされます)、これを "killed X dudes"または "found X chests"にも使用できます。また、実績のないものに対してカウンタを設定でき、今後の使用のために引き続き追跡されることを意味します。


3

前回のゲームで実績を実装したとき、すべて統計に基づいて作成しました。統計が特定の値に達すると、成果はロック解除されます。Modern Warfare 2を検討してください:ゲームは大量の統計情報を追跡しています!SCAR-Hで何枚撮影しましたか?ライトウェイトパークの使用中に何マイル走ったか?

そのため、実装では、単純に統計エンジンを作成し、ゲームプレイ全体で実績のステータスを確認するための非常に簡単なクエリを実行する実績マネージャーを作成しました。

私の実装は非常に単純ですが、仕事は完了しています。それについて書いて、ここで質問を共有しました


2

使用イベント微積分。次に、前提条件が満たされた後に適用されるいくつかの前提条件とアクションを作成します。

  • 前提条件:敵を1000人倒し、2本の足を持っている
  • アクション:ロリーポップをくれ、スーパーデュパーショットガン-13をくれ
  • 初めてのアクション:「あなたはすごい!」

次のように使用します(速度が最適化されていません!):

  • すべての統計を保存します。
  • 前提条件の統計を照会します。
  • アクションを適用します。
  • 1回限りのアクションを1回適用します。

高速にしたい場合:

  • 好きなものをキャッシュし、その一部をツリー、ハッシュに保存します...
  • 変更をインクリメンタルにして、すべてのアクションを常に適用しないでください。ただし、これらは新しいものです...)

注意

すべてのものには長所と短所があるため、最善のアドバイスを与えることは困難です。

  • 「最高のデータ構造とは?」「これで最もやりたい操作は?検索、削除、追加...」
  • あなたは基本的にこれらのプロパティで考えます:コーディングの容易さ、速度、明快さ、サイズ...

0

達成イベントが発生した後のIFチェックの何が問題になっていますか?

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
「私は「すべてを条件としてハードコードする」よりも、より組織化され保守可能なものを探しています。'。これは間違いなく小さなゲームに適したKISSメソッドです。
共産主義のダック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.