筆者のステータス
ゲームを作りたくなってTopDownEngineを衝動買いしたけどUnityまったく未経験につき一瞬で挫折。デモ(MinimalSandbox3D)の起動で止まっているので、とりあえずTopDownEngineのドキュメントとコードを読もうかなと思いました。という状態です。(したがって想定読者層は私と同じようなUnity未経験者です。)
Unityのバージョンは2020.3.30f1
TopDownEngineのバージョンは2.3.1
Achievementsとは
よくゲームで、本筋とは別に「敵を○○体撃破した!」みたいな実績が解除されていくアレです。たとえば、MinimalSandbox3Dでは10回ジャンプしたら画面の右下に"Jump Around"と書かれた四角いやつが出てきて、実績が解除されます。
もし手元の環境で10回ジャンプしても何も出てこない場合は、既に実績が解除されてしまっている可能性があるので、メニューバーからTools > More Mountains > Reset all achievementsを選択してリセットしてみてください。
AchievementList.asset
まずは↓を読みました。
ProjectビューからCommon/Resources/Achievementsの中にAchievementListっていうassetがあって、それを選ぶとInspectorビューにAchievementsが表示されます。
リストはResourcesフォルダ内にないとUnityがリストをロードできないから注意なんだそうです。
AchievementsRules.cs
HierarchyビューのMinimalSandbox3D/Managers/AchievementRulesから、あるいは直接Common/Scripts/Achievements/AchievementsRules.csからコードを見ることができます。
具体的なことはドキュメントには記載がなかったですが、リファレンスの方に載ってました。
AchievementRules.csに書かれているAchievementRulesクラスの定義を見ると、MMAchievementRulesクラス(これがbase classらしい)と複数のイベントリスナーを継承していました。リファレンスに書いてあるとおりです(doxygenだから当たり前か)。
/// <summary>
/// This class describes how the Top Down Engine demo achievements are triggered.
/// It extends the base class MMAchievementRules
/// It listens for different event types
/// </summary>
public class AchievementRules : MMAchievementRules,
MMEventListener<MMGameEvent>,
MMEventListener<MMCharacterEvent>,
MMEventListener<TopDownEngineEvent>,
MMEventListener<MMStateChangeEvent<CharacterStates.MovementStates>>,
MMEventListener<MMStateChangeEvent<CharacterStates.CharacterConditions>>,
MMEventListener<PickableItemEvent>,
MMEventListener<CheckPointEvent>,
MMEventListener<MMInventoryEvent>
{
...<後略>
引き続きAchievementRules.csの中を読んでいくと、理解できる所を見つけられました。
public virtual void OnMMEvent(MMCharacterEvent characterEvent)
{
if (characterEvent.TargetCharacter.CharacterType == Character.CharacterTypes.Player)
{
switch (characterEvent.EventType)
{
case MMCharacterEventTypes.Jump:
MMAchievementManager.AddProgress ("JumpAround", 1);
break;
}
}
}
上のコードは、characterEventをトリガにして、EventTypeがJumpなら"JumpAround"のProgressを1進める。という処理のようです。ためしにMMAchievementManager.AddProgress ("JumpAround", 2);
に書き換えると、キャラクターが1回ジャンプするごとに"JumpAround"のProgressが2加算され、その結果5回ジャンプしただけで実績が解除されました。
ちなみにJumpというイベントトリガは、Common/Scripts/Characters/Core/CharacterEvents.csに定義らしきものがありましたが、今は深追いしないことにします。
public virtual void OnMMEvent(CheckPointEvent checkPointEvent)
{
if (checkPointEvent.Order > 0)
{
MMAchievementManager.UnlockAchievement("SteppingStone");
}
}
上のコードでは、checkPointEventをトリガにして、Orderが0より大きければ実績が解除されるみたいです。ためしにチェックポイントを追加してみました。(コアラ2DのDemoを見てマネしました)。
- Levelの下にCreate EmptyでCheckPointAという空のオブジェクトを作成(一応Create Empty ParentでCheckPointsも作成)
- CheckPointAにAdd ComponentでBox Colliderを追加
- Is Triggerにチェック
- Box Colliderのサイズを3×3×3に変更
- Scene上で見やすいようにアイコンを選択(多分必須ではない操作)
- さらにAdd ComponentでCheckpointを追加
- Check Point Orderを1に変更
これで、キャラクターがCheckPointAに行くと"SteppingStone"の実績が解除されました。これが正しい手順なのかはまだ不明ですが、今は深追いしないことにします。
AchievementDisplay
ドキュメントには細かい説明がなく、コード見たり色々触って徐々にわかりはじめました。AchievementDisplayは3つのファイル(もっとあるかも)で構成されています。
AchievementDisplay.prefab
実績解除時に画面に表示される四角いやつはThirdParty/MoreMountains/MMTools/Tools/MMAchievements/Prefabsの中にAchievementDisplayというprefabで位置や寸法やレイアウトが定義されています。
MMAchievementDisplayItem.cs
さっきのAchievementDisplay.prefabのInspectorビューに、MMAchievementDisplayItemというComponentがあったかと思います。そこには
- Background Locked
- Background Unlocked
- Icon
- Title
- Description
- Progress Bar Display
という項目があって、prefabの各要素と対応して紐づけられています。
後述のMMAchievementDisplayer.csの中で、解除された実績に応じた画像や文字列をメンバに格納することで、解除した実績を動的に表示することができています。
MMAchievementDisplayer.cs
コードはThirdParty/MoreMountains/MMTools/ToolsForMMFeedbacks/Tools/Achievementsの中にあります。見た感じ、OnMMEvent()
でMMAchievementUnlockedEventを引っかけて、DisplayAchievement()
の中でinstance
からachievementDisplay
を生成して、しかるべき画像やメッセージをAchievementItemの各メンバに格納する仕組みになっているようです。
// we instantiate our achievement display prefab, and add it to the group that will automatically handle its position
GameObject instance = (GameObject)Instantiate(AchievementDisplayPrefab.gameObject);
instance.transform.SetParent(this.transform,false);
// we get the achievement displayer
MMAchievementDisplayItem achievementDisplay = instance.GetComponent<MMAchievementDisplayItem> ();
if (achievementDisplay == null)
{
yield break;
}
// we fill our achievement
achievementDisplay.Title.text = achievement.Title;
achievementDisplay.Description.text = achievement.Description;
achievementDisplay.Icon.sprite = achievement.UnlockedImage;
...<後略>
そして同じくinstance
からachievementCanvasGroup
を生成して、画像のフェードイン・フェードアウトを実行しているようです。
// we fade it in and out
CanvasGroup achievementCanvasGroup = instance.GetComponent<CanvasGroup> ();
if (achievementCanvasGroup != null)
{
achievementCanvasGroup.alpha = 0;
StartCoroutine(MMFade.FadeCanvasGroup(achievementCanvasGroup, AchievementFadeDuration, 1));
yield return _achievementFadeOutWFS;
StartCoroutine(MMFade.FadeCanvasGroup(achievementCanvasGroup, AchievementFadeDuration, 0));
}
次の記事
以上で大まかな構造や仕組みが理解できたところで、次の記事では実際にAchievementの機能を使ってみようと思います。