Unity TopDownEngineのAchievementsで遊んでみた

# 筆者のステータス
Unity未経験だけどゲームが作りたい、と思ってTopDownEngineを衝動買いしたものの、Unityの知識なしゲームの構想なしで途方に暮れていました。とりあえずTopDownEngineが提供するAchievementsのドキュメントを読んでみて、<b>「チェックポイントを通過するとカットインが入る」</b>くらいなら自分にも作れそうだと思い始めました。

https://qiita.com/items/1f6f432cbf4e9e9c24c9

Unityのバージョンは2020.3.30f1
TopDownEngineのバージョンは2.3.1

# この記事の目的
[前の記事](https://qiita.com/items/1f6f432cbf4e9e9c24c9)ではドキュメントを読んだだけでしたが、今回は実際にAchievementsで遊びながら理解を深めていきます。

![Goal](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/b2005ab3-8429-c37c-b77f-40c6bc133bc5.gif)

# 自分用にAchievementDisplayをコピーして編集
もともと存在するAchievementDisplayはそのまま置いておいて、自分用にAchievementDisplayに関わるファイル一式をコピーしてきてそれを編集することにします。手順は次のとおり。
## ファイルのコピー

1. ProjectビューからAssets直下にお好みの名前のフォルダを作成
1. 作成したフォルダにMinimalScene3Dをそのままコピー
1. TopDownEngine/ThirdPartyの中にMMToolsというフォルダに今回ほしいファイルがあるので、以下を探してきてコピー
    1. AchievementDisplay.prefab
    1. MMAchievementDisplayer.cs
    1. MMAchievementDisplayItem.cs
1. コピーしてきたファイルをリネーム(今回は"Achievement"と"Display"の間に"Image"を追加しました。)
![ファイルのコピー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/59bf457d-9a5f-114c-dc13-197da1ad2c3f.png)

ちなみにそれぞれどういうファイルかというと、
<b>AchievementImageDisplay.prefab</b>
画面に表示されるprefab。このprefabは画像とタイトル、説明文、プログレスバーを有しています。

<b>AchievementImageDisplayer.cs</b>
実績解除イベントを引っかけて、解除した実績に応じた内容を上記prefabに入れて表示する処理を実行するスクリプト。

<b>AchievementImageDisplayItem.cs</b>
上記prefabの画像やテキストを切り替えるための定義文(正しい用語がわからない)。

## 編集
Unityではファイル名とクラス名が一致していないといけないようなので、リネームしたファイル名に合わせてスクリプト内のクラス名や変数名等を置換していきます。下に変更後のコードを貼っておきます(▲をクリックすると展開します)(コピーしたことが分かるように、"Icon"を"Picture"に書き換えたりしてます)。

<details><summary>AchievementImageDisplayItem.cs</summary><div>

```cs:AchievementImageDisplayItem.cs
using UnityEngine.UI;
using MoreMountains.Tools;

namespace MoreMountains.Tools
{
    /// <summary>
    /// This class is used to display an achievement. Add it to a prefab containing all the required elements listed below.
    /// </summary>
    [AddComponentMenu("MeganeOL/AchievementImageDisplayItem")]
    public class AchievementImageDisplayItem : MonoBehaviour 
	{		
		public Image BackgroundLocked;
		public Image BackgroundUnlocked;
        /// オリジナルはIconだったけどPictureに変更。
		public Image Picture;
		public Text Title;
		public Text Description;
		public MMProgressBar ProgressBarDisplay;
	}
}
```

</div></details>
<details><summary>AchievementImageDisplayer.cs</summary><div>

```cs:AchievementImageDisplayer.cs
using UnityEngine;
using System.Collections;
using MoreMountains.Tools;
using MoreMountains.Feedbacks;
using UnityEngine.SceneManagement;

namespace MoreMountains.Tools
{
    /// <summary>
    /// A class used to display the achievements on screen. 
    /// The AchievementDisplayItems will be parented to it, so it's better if it has a LayoutGroup (Vertical or Horizontal) too.
    /// </summary>
    [AddComponentMenu("MeganeOL/AchievementImageDisplayer")]
    public class AchievementImageDisplayer : MonoBehaviour, MMEventListener<MMAchievementUnlockedEvent>
	{
		[Header("Achievements")]
		/// the prefab to use to display achievements
		/// Itemやprefabの名称を変更しているので合わせる
		public AchievementImageDisplayItem AchievementImageDisplayPrefab;
		/// the duration the achievement will remain on screen for when unlocked
		public float AchievementDisplayDuration = 15f;
		/// the fade in/out speed
		public float AchievementFadeDuration = 0.2f;

		protected WaitForSeconds _achievementFadeOutWFS;

		/// <summary>
		/// Instantiates an achievement display prefab and shows it for the specified duration
		/// </summary>
		/// <returns>The achievement.</returns>
		/// <param name="achievement">Achievement.</param>
		public virtual IEnumerator DisplayAchievement(MMAchievement achievement)
		{
			if ((this.transform == null) || (AchievementImageDisplayPrefab == null))
			{
				yield break;
			}
			// we instantiate our achievement display prefab, and add it to the group that will automatically handle its position
			GameObject instance = (GameObject)Instantiate(AchievementImageDisplayPrefab.gameObject);
			instance.transform.SetParent(this.transform,false);

			// we get the achievement displayer
			AchievementImageDisplayItem achievementDisplay = instance.GetComponent<AchievementImageDisplayItem> ();
			if (achievementDisplay == null)
			{
				yield break;
			}

			// we fill our achievement
			achievementDisplay.Title.text = achievement.Title;
			achievementDisplay.Description.text = achievement.Description;
			// Itemの方でIconをPictureに変更したので合わせる
			achievementDisplay.Picture.sprite = achievement.UnlockedImage;
			if (achievement.AchievementType == AchievementTypes.Progress)
			{
				achievementDisplay.ProgressBarDisplay.gameObject.SetActive(true);
			}
			else
			{
				achievementDisplay.ProgressBarDisplay.gameObject.SetActive(false);
			}
...<後略>
```
</div></details>

次にprefabを編集します。手順は以下のとおり。
1. Add ComponentからAchievementImageDisplayItemを追加して、元々いたMMAchievementDisplayItemをremove
1. 表示したい位置に移動
1. 各要素をリネーム
1. ドラッグ&ドロップで要素とアイテムを紐づけ

![prefab](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/092a212c-5c62-8926-67a1-743597ab7eb4.png)

# Canvasに設置
MinimalScene3DのHierarchyビューからUICamera/Canvasに空のオブジェクトを追加して、AchievementImageという名前を付けておきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/516d6a2a-b9a0-525b-1ac1-30d4d653c5f4.png" width=30%>

Add ComponentでAchievementImageDisplayerを追加した後、"Achievement Image Display Prefab"にprefabをドラッグ&ドロップします。これを忘れると、AchievementImageDisplayerは実績解除トリガを引っかけるものの、prefabがnullになってしまい何も表示されません。

![DnD](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/5e672291-dcbc-e9ad-c94c-0156c4f868fb.png)

ここまでで、ゲーム内でキャラクターが10回ジャンプすると元々あった実績解除表示のほかにもう一つ実績解除が表示されるようになります。

![Jump Around](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/eded80ae-f827-9886-0029-6553097ab8af.png)

# チェックポイントの設置とAchievementListの編集
4箇所のチェックポイント(CheckpointA～D)を設置し、キャラクターがチェックポイントに進入するとそのチェックポイントに進入した実績が解除されるルールで作ります。AchievementIDはVisitCheckpointA～Dにしました

チェックポイントとAchievementListの編集については[前の記事](https://qiita.com/items/1f6f432cbf4e9e9c24c9)と同じやり方です。

CheckpointAのOrderには1を、CheckpointBのOrderは2、CheckpointCは3、CheckpointDは4を振りました。振ったOrderはAchievementRuleを実装するときに使います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/c0769039-a417-6689-fc78-52b81e5a066f.png" width=50%>

AchievementListに4個追加します。いずれもAchievementTypeはSimpleです。TitleとDescriptionを適当に弄りましたが、詳細は割愛します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/539555c3-7f9e-3bfe-67b3-bf413de32229.png" width=60%>

# AchievementRule

既存のAchievementRule.csのうち、`public virtual void OnMMEvent(CheckPointEvent checkPointEvent)`の中を改造しました。

```cs:AchievementRules.csより
public virtual void OnMMEvent(CheckPointEvent checkPointEvent)
{
    // チェックポイントA～Dに進入した実績解除
    switch (checkPointEvent.Order)
    {
        case 1:
            MMAchievementManager.UnlockAchievement("VisitCheckpointA");
            break;
        case 2:
            MMAchievementManager.UnlockAchievement("VisitCheckpointB");
            break;
        case 3:
            MMAchievementManager.UnlockAchievement("VisitCheckpointC");
            break;
        case 4:
            MMAchievementManager.UnlockAchievement("VisitCheckpointD");
            break;
    }
}
```

# Displayerの改造
自分用にコピー&編集で作ったAchievementImageDisplayerに、VisitCheckpointの実績解除だけ表示する分岐を追加します。

```cs:AchievementImageDisplayer.csより
public virtual IEnumerator DisplayAchievement(MMAchievement achievement)
{
    if ((this.transform == null) || (AchievementImageDisplayPrefab == null))
	{
		yield break;
	}

    // 目当ての実績解除じゃなかったら抜ける
    if (achievement.AchievementID.Contains("VisitCheckpoint") == false){
		yield break;
    }
...<後略>
```

逆にオリジナルのMMAchievementDisplayer.csの方にはVisitCheckpointの実績解除だけ表示<b>しない</b>分岐を追加します。
```cs:MMAchievementDisplayer.csより
public virtual IEnumerator DisplayAchievement(MMAchievement achievement)
{
	if ((this.transform == null) || (AchievementDisplayPrefab == null))
	{
		yield break;
	}

    // 解除した実績がAtoryAchieve系だったら抜ける
    if (achievement.AchievementID.Contains("VisitCheckpoint") == true){
		yield break;
    }
...<後略>
```

これで、設置したチェックポイントに進入するとVisitCheckpointの実績解除がカットイン風に表示されるようになりました。

![VisitCheckpoint](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1826988/29ac8868-7200-aafb-4412-0bd1ca9bd463.png)