達成はこのような長方形のメダルであり、ユーザーは何らかのアクションを実行することで表彰されます。 Linderdaumパズルには、このようなメダルが約100個あります。 UIでの表示例を次に示します。
いくつかの考え:
- 各成果は、一連のアクション(写真を収集する、時間を過ごすなど)またはイベント(5秒よりも早く写真を収集する、Facebookに行くなど)に関連付けられています。
- 一部の成果には、この成果に向けた現在の進捗状況を示す説明文があります。
- あなたがそれらを取得するまで見えない秘密の成果があります。
- ゲームの無料版では利用できない実績があります。 :)
- ユーザーがアチーブメントを失うことがないように、アチーブメントを保護する必要があります。
コーディングを開始します。 開始するには、多額の列挙を開始します。この列挙には、所有しているすべてのものがリストされています。
enum LAchievement { LA_SUPPORTER = 0, LA_REVIEWER, LA_MONTHLING, LA_CASUAL, LA_ENTHUSIAST, LA_FANATIC, LA_PUZZLENEWBIE3X3, // ... // - , };
ゲームの新しいバージョンでは、リストの最後に新しい実績を自由に追加できます。 ただし、すでにリリースされているバージョンでは順序を変更できません。 理由は明らかだと思います。
2つのタイプを宣言します。
typedef bool (*HasAchievementProc)(void); // , typedef LString (*GetNoteProc)(void); // -, , " 99 "
秘密の成果かどうかを判断するために、このタイプを定義します。
enum AchievementVisibility { L_VIS, L_HID, };
ブールだけでうまくいくことが可能であったことは明らかです(そして、それは非常に最初でした)が、テーブルの定数を埋めると、さまざまなブールからの成果が目に見え始めたため、ブールは開発プロセス中に放棄されました。
ある成果の説明は、最終的に次のようになり始めました。
struct sAchievement { int FID; // LAchievement bool FPaidVersion; // ? const char* FName; // , const char* FDescription; // , HasAchievementProc FProc; AchievementVisibility FHidden; const char* FProgressNote; // , "%s solved" GetNoteProc FNoteProc; bool FShowNoteAfterAwarding; // // , . // . // generated at runtime iGUIView* FViewPlate; iGUIView* FViewNote; clCVar* FAwarded; };
次に、成果自体を発明する創造的な作業と、sAchievement要素の巨大なテーブルに記入するという猿の作業を開始します。 これが達成システム全体の中心です。 以下にいくつかの行を示します。
static sAchievement Achievements[] = { { LA_SUPPORTER, false, "Supporter", "Purchased Linderdaum Puzzle HD", &Check_Supporter, L_VIS, NULL }, { LA_REVIEWER, false, "Reviewer", "Added a review on Google Play", &Check_Reviewer, L_VIS, NULL }, { LA_MONTHLING, false, "Month's campaign", "Used the game for one month", &Check_Monthling, L_VIS, "%s days", &Get_DaysSinceFirstUse, true }, { LA_CASUAL, false, "Casual", "Spent half an hour in game", &Check_Casual, L_VIS, "%s minutes", &Get_MinutesInGame, false }, { LA_ENTHUSIAST, false, "Enthusiast", "Spent 2 hours in game", &Check_Enthusiast, L_VIS, "%s minutes", &Get_MinutesInGame, false }, { LA_FANATIC, true, "Fanatic", "Spent 10 hours in game", &Check_Fanatic, L_VIS, "%s hours", &Get_HoursInGame, false }, // ... // - , }
Check_ *関数は、「アクションのシーケンス」タイプの成果を受け取るための条件をチェックします。 そのような関数の典型的な内容:
bool Check_Monthling() { LDate FirstRun = LDate( FirstRunDate.GetString() ); LDate Today; int Days = Today-FirstRun; return Days >= 30; }
「単一イベント」タイプのイベントの場合、そのような関数は不要であり、それらのテーブルではNULLであることに注意してください。 そのような成果を授与のためにキューに入れることは、ゲームコードで直接実行されます。
if ( Time < 5.0 ) g_Achievements->Award( LA_BLINKOFANEYE );
また、 FProgressNoteとFNoteProcがあることにお気づきでしょう。 FNoteProcを1つだけ実行して、すぐにフレーズを返すことができないのはなぜですか? すべてがシンプルです。 フレーズを現在の言語にローカライズするため。 テンプレートはローカライズされ、string-numberがテンプレートに返されます 。これはFNoteProcから返されます 。
これで、すべてが静的データに命を吹き込む準備ができました。 これを行うには、もう少しプログラムする必要があります。 Achievement ManagerとAchievementsのUI Managerが必要です。 彼らが何をするか見てみましょう。
class clAchievementsManager: public iObject { public: // // // clAchievementsManager // /// trigger the award for a one-time achievement virtual void Award( LAchievement Achievement ); virtual void AwardName( const LString& AchievementName ); virtual bool IsAwarded( LAchievement Achievement ) const; /// called automatically every 6 seconds or so to check new achievements virtual void ProcessAchievements(); virtual void RecheckAchievements(); // - public: std::deque<LAchievement> FPendingAwards; iGUIView* FAchievementsText; mlNode* FNode_Awarded; };
ProcessAchievements()は6秒ごとに呼び出され、
Env->SendAsyncCapsule( BindCapsule( &clAchievementsManager::ProcessAchievements, this ), 6.0 );
内部では、このコードのようなもの(少しスクランブル):
void clAchievementsManager::ProcessAchievements() { // save gamestate // ... RecheckAchievements(); // check achievements once in a while Env->SendAsyncCapsule( BindCapsule( &clAchievementsManager::ProcessAchievements, this ), 6.0 ); // nothing new to award if ( FPendingAwards.empty() ) return; LAchievement A = FPendingAwards.front(); FPendingAwards.pop_front(); // this achievement had been awarded long time ago if ( Achievements[ A ].FAwarded->GetBool() ) return; Achievements[ A ].FAwarded->SetBool( true ); // don't lose achievements in case of crash g_Game->SaveAchievements( g_SaveAchievementsFileName ); // show nice message here Env->Renderer->GetCanvas()->AnnounceObject( Construct<clAchievementAnnouncer>( Env, A, FNode_Awarded ), 0.0, 5.0 ); clPuzzl_AchievementsContainer* C = Env->GUI->FindView<clPuzzl_AchievementsContainer>("AchievementsContainer"); // update UI if ( C ) C->RecreateSubViews(); }
複雑なことは何もありません。 条件を確認し、 Award()メソッドが入れるキューから「イベント」タイプの実績を配布するだけです。 clAchievementAnnouncerクラスは、次のようにUI全体に美しいプレートを描画します。
ゲームも6秒ごとに保存されることに注意してください-ユーザーが進行状況を失うことは望ましくありません。
RecheckAchievements()メソッドは、最初のスクリーンショットにあったすべての実績のテーブルでUIを更新します。 UIはclPuzzl_AchievementsContainerクラスによって直接管理されます。これは、UIシステムに応じて非常に具体的になります。 私たちと一緒に、彼はサイコロをカップで満たすだけです(もう一度、最初のスクリーンショットを見てください)。
死後
ゲームがリリースされ、成果のシステムがうまく機能します。 Flurryを通じてavivokの統計を追跡し、受信した成果の数と成果を観察する機会があります。 これはバランスを整えるのに役立ちます。 より複雑なゲームの場合、そのようなフィードバックの助けを過大評価することは困難です。
私がしたかったが、時間がなかったから:
- Twitterユーザーにメッセージを表示して、成果の口コミ性を高めます。 たとえば、Osmosのように、FourSquareはそうします。
- ローカルではなく、ユーザーアカウントのクラウドに実績を保存します。 Google App Engineまたは同様のサービスを試してみる価値があります。 そこで、ゲームの状態を保存できます。 大きなジグソーパズルを組み立てるとき、1つの写真に数時間を費やすことができるとき、これは特に重要です。
PSゲームはLinderdaum Engineで行われます。