ひつじコレクション - 3.フォロワーを作成

[ミニゲームを作ってUnityを学ぶ！[ひつじコレクション編]](https://qiita.com/YzRoid/items/8dfd6c168b916ac82a0d)
###**第3回目: フォロワーを作成**

ひつじコレクションでのプレイヤーの目的はステージ内に出現するひつじさんに接触し、追従を開始したひつじさんを無事にゴールのエリアまで連れ帰ることです。

このひつじさんのことを「追従する者」という意味でフォロワーと呼ぶことにし、今回はこのフォロワーがステージにポップアップするところまでを実装していきます。

#アセットをインポート
まずはじめに、今回使用する2つのアセットをストアからインポートします。

![action_sheep_ss_3_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/7b4d6d0b-3b87-e34a-ea94-9d2c1c5bff61.jpeg)


Lovely Animals Pack
[Particle Collection SKJ 2016_Free samples](https://assetstore.unity.com/packages/vfx/particles/particle-collection-skj-2016-free-samples-72399)

<font color="Red">追記：
ひつじさんの含まれるアセット「Lovely Animals Pack」が少し前まで無料だったのですが、現在は有料になってしまったようです……
仕方ないのでこれからひつじコレクションを作ってみようとしている方は、本作で敵キャラクターとして登場させるスライムが含まれた下記アセットの中にウサギらしきクリーチャーのプレハブがありますので、そちらで代用してみてください。

![action_sheep_ss_3_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/957d0dd1-7b23-3399-5643-c31d98af8492.jpeg)


[Level1 Monster Pack](https://assetstore.unity.com/packages/3d/characters/creatures/level-1-monster-pack-77703)



#フォロワーの作成
ひつじさん（もしくはウサギさん）をフォロワーとして利用できるよう準備していきます。

###<font color="Blue">【ひつじさんの場合】</font>

+ JKT_Art/Animals/sheep/sheepをシーンに配置して名前を「Follower」に変更
+ Transformを以下のように設定

<Pre>
Position: x=0, y=0, z=0
Rotation: x=0, y=0, z=0
Scale: x=0.05, y=0.05, z=0.05
</Pre>

+ 新しくAnimatorControllerの「FollowerAnimController」を作成
+ すでにアタッチされているAnimatorからcontrollerをFolowerAnimControllerに差し替える
+ 同じくAnimatorのApplyRootMotionのチェックを外す
+ CapsuleColliderをアタッチして以下のように設定

<pre>
Is Trigger: チェックを入れる
Center: x=0.04, y=4.2, z=0.13
Radius: 4.2
Height: 9
Direction: Z-Axis
</pre>

+ 子のshadow(1)オブジェクトについて、アタッチされているMeshColliderを取り除く
+ Followerオブジェクトをプレハブに書き出す



###<font color="Red">【ウサギさんの場合】</font>

+ Level_1_Monster_Pack/Prefabs/Rabbit/Rabbit_Redをシーンに配置して名前を「Follower」に変更
+ Transformを以下のように設定

<Pre>
Position: x=0, y=0, z=0
Rotation: x=0, y=0, z=0
Scale: x=25, y=25, z=25
</Pre>

+ 新しくAnimatorControllerの「FollowerAnimController」を作成
+ すでにアタッチされているAnimatorからcontrollerにFolowerAnimControllerを設定
+ 同じくAnimatorのApplyRootMotionのチェックを外す
+ CapsuleColliderをアタッチして以下のように設定

<pre>
Is Trigger: チェックを入れる
Center: x=0, y=0.01, z=0.001
Radius: 0.01
Height: 0.02
Direction Y-Axis
</pre>

+ Followerオブジェクトをプレハブに書き出す

![action_sheep_ss_3_3.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/d8f48095-312f-9ebd-56c7-34ec9f8f62fc.jpeg)


###アニメーションの設定
FollowerAnimControllerをダブルクリックしてAnimatorウインドウを開き、Followerオブジェクトのアニメーションを設定していきます。

+ IdleとRunのステートを作成
+ IdleからRun, RunからIdleにトランジション（矢印）を引く

![action_sheep_ss_3_4.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/df2fcb2e-d971-4ec3-4281-603348a67574.jpeg)


+ 各ステートに対応するクリップを設定

|  | Idle | Run |
|:-:|:-:|:-:|
| ひつじさん | ~sheep/ani/idle  | ~sheep/ani/walk  |
| ウサギさん | ~Models/Rabbit_Level_1/rabbit_idle  | ~Models/Rabbit_Level_1/rabbit_moveもしくはぴょんぴょん飛び跳ねるのが嫌な場合はIdle時のアニメーションと同じクリップ  |

+ parametersタグを選択し、Bool型のIsRunを作成
+ それぞれの遷移条件を以下のように設定

![action_sheep_ss_3_5.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/d924a381-943e-b4a0-bda2-fcdf52a32f21.jpeg)



###フォロワーのスクリプトを作成
続いて、Followerオブジェクトを制御するためのスクリプトを作成します。

+ 「FollowerModel」という名前のスクリプトを作成し、Followerオブジェクトにアタッチ

```cs:FollowerModel.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

    public class FollowerModel : MonoBehaviour
    {

        //--------
        // 状態 //
        //---------------------------------------------------------------------------------

        public enum STATE
        {
            DEFAULT = 0,
            POPUP,
            FOLLOW,
            BLOW_OFF,
            SLEEP
        }
        public STATE State { get; private set; }

        //-------------
        // アクション //
        //---------------------------------------------------------------------------------

        /// <summary>
        /// ポップアップ
        /// </summary>
        public void Popup()
        {
            State = STATE.POPUP;
            gameObject.SetActive(true);
        }

    }

```

Stateプロパティと1つのメソッドだけを持つ最小限の構成です。
Popup()によってStateをPOPUP状態に変更し、自身をActiveにすることで画面に描画されるようになります。
<font color="Blue">フォロワーはこのPopup()が実行されるまで画面に描画されないようにするため、オブジェクト名の横にあるチェックを外してデフォルトでは描画されない状態にしておきます。</font>

![action_sheep_ss_3_6.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/b46de8d5-0688-b4e5-1ad6-97998a7bb247.jpeg)



#ポップアップポイントの作成
フォロワーの準備ができましたので、次はフォロワーがポップアップするステージ内のポイントを作成していきます。

+ 「PopupPoint」という名前のCubeをシーンに配置し、Transformを以下のように設定

<pre>
Position: x=0, y=0, z=0
Rotation: x=0, y=0, z=0
Scale: x=0.8, y=0.8, z=0.8
</pre>

+ MeshRendererのチェックを外して透明な状態に変更
+ BoxColliderのIsTriggerにチェックを入れる
+ プレハブに書き出し

###ポップアップ時のエフェクト
+ PopupPointの子として「ParticleCollection~/Prefab/Magic_ring/magic_ring_03」を配置
+ 上記のmagic_ring_03についてTransformを以下のように設定

<Pre>
Position: x=0, y=0.2, z=0
Rotation: x=-90, y=0, z=0
Scale: x=0.16, y=0.16, z=0.16
</Pre>

+ magic_ring_03の子要素それぞれについて、インスペクタから以下の修正をしてサイズを調整

<pre>
全ての子要素（01, 01_B, 02, 02_B, 03）について：

    Scaleing ModeをLocalからHierarchyに変更


02について：

    StartSizeを変更(min=0.1, max=2)
    Shape内のRadiusを変更（2.2）
    Renderer内のPivot.Yを変更（0.38）


02Bについて：

    Renderer内のPivot.Yを変更（0.42）
</pre>

![action_sheep_ss_3_7.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/fcc6657e-80ad-2ba6-df45-76ad8962f206.jpeg)


上のように表示されるようになったら<font color="Red">magic_ring_03のオブジェクト名の横にあるチェックを外し、エフェクトを非アクティブな状態に変更</font>しておきます。



###ポップアップポイントのスクリプトを作成
続いて、ポップアップポイントを制御するためのスクリプトを作成します。

+ 「PopupPoint」という名前のスクリプトを作成し、PopupPointオブジェクトにアタッチ

```cs:PopupPoint.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

    public class PopupPoint : MonoBehaviour
    {

        [SerializeField]
        private GameObject mEffect; // 子オブジェクトのmagic_ring_03を指定
        [SerializeField]
        private GameObject prefabFollower; // フォロワーのプレハブを指定
        private FollowerModel mFollower;

        /// <summary>
        /// フォロワーがポップ中か判定
        /// </summary>
        /// <returns>ポップ中の場合はtrueを返す</returns>
        public bool IsExsistFollower()
        {
            if (mFollower != null)
            {
                if (mFollower.State == FollowerModel.STATE.POPUP) return true;
            }
            return false;
        }

        /// <summary>
        /// フォロワーをポップアップできる状態か判定
        /// 
        /// フォロワーが一度もポップアップしていない or 関連するフォロワーが休眠状態
        /// の場合はポップアップが可能
        /// </summary>
        /// <returns>ポップアップ可能ならばtrueを返す</returns>
        public bool IsReadyPopupFollower()
        {
            if (mFollower == null) return true;
            if (mFollower.State == FollowerModel.STATE.SLEEP) return true;
            return false;
        }

        /// <summary>
        /// フォロワーをポップアップさせる
        /// 同時にエフェクトを発生させる
        /// </summary>
        public void PopupFollower()
        {
            if (mFollower == null)
            {
                GameObject model = Instantiate(prefabFollower, transform.position, Quaternion.identity);
                mFollower = model.GetComponent<FollowerModel>();
            }
            mFollower.Popup();
            mEffect.SetActive(true);
        }

    }

```

PopupPointは2つの判定を持ち、PopupFollower()を実行することでフォロワーをステージ内に出現させます。

#StageConstructor, StageManagerの修正
フォロワーとポップアップポイントに対応するため、StageConstructorとStageManagerにコードを追加します。

```cs:StageConstructor.cs

    public void LoadStage(int stageId)
    {
        // 指定されたステージファイルを読み込んで1行ずつ処理
        string filePath = "stage_" + stageId;
        TextAsset textAsset = Resources.Load(filePath) as TextAsset;
        string text = textAsset.text;
        string line;
        using (TextReader reader = new StringReader(text)) // usingとは、処理終わりにstreamの解放を自動で行う構文（finally句でDisposeを実行するコードと同じ）
        {
            while ((line = reader.ReadLine()) != null)
            {
                // ステージサイズ
                if (line.StartsWith("@size"))
                {
                    line = REGEX_WHITE.Replace(line, "");
                    string[] mapSize = line.Split(DELIMITER, System.StringSplitOptions.RemoveEmptyEntries);
                    mStageX = int.Parse(mapSize[1]);
                    mStageY = int.Parse(mapSize[2]);
                    mStageMap = new int[mStageY, mStageX];
                    InstantiateFloor();
                    continue;
                }

                // ステージ構造（ブッシュ）
                if (line.StartsWith("@bush"))
                {
                    // ステージ構造は16進数表記の文字列なため、10進数のint配列に変換
                    StringBuilder sbStage = new StringBuilder();
                    for (int y = 0; y < mStageY; y++)
                    {
                        sbStage.Append(reader.ReadLine());
                    }
                    int start = 0;
                    for (int y = 0; y < mStageY; y++)
                    {
                        for (int x = 0; x < mStageX; x++)
                        {
                            mStageMap[y, x] = Convert.ToInt32(sbStage.ToString(start, 2), 16);
                            start += 2;
                        }
                    }
                    // ステージ構造を元にブッシュを生成
                    InstantiateBushs();
                    continue;
                }

                // プレイヤー初期位置
                if (line.StartsWith("@player"))
                {
                    line = REGEX_WHITE.Replace(line, "");
                    string[] strPlayerPos = line.Split(DELIMITER, System.StringSplitOptions.RemoveEmptyEntries);
                    InstantiatePlayer(int.Parse(strPlayerPos[1]), int.Parse(strPlayerPos[2]));
                    continue;
                }

ポップアップポイントを生成するコードを追加

                // ポップアップポイント
                if (line.StartsWith("@popup"))
                {
                    line = REGEX_WHITE.Replace(line, "");
                    string[] strPopupPos = line.Split(DELIMITER, System.StringSplitOptions.RemoveEmptyEntries);
                    InstantiatePopup(int.Parse(strPopupPos[1]), int.Parse(strPopupPos[2]));
                }

ここまで：ポップアップポイントを生成するコード

            }
        }
    }

        //----------------------
        // ポップアップポイント //
        //---------------------------------------------------------------------------------

        [SerializeField]
        private GameObject prefabPopupPoint; // ポップアップポイントのプレハブを指定
        private List<PopupPoint> mPopupList = new List<PopupPoint>();

        public List<PopupPoint> GetPopupList()
        {
            return mPopupList;
        }

        /// <summary>
        /// ポップアップポイントを指定されたステージ座標に生成
        /// PopupPointの参照を保持する
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void InstantiatePopup(int x, int y)
        {
            Vector3 pos = new Vector3(x * BLOCK_SIZE + BLOCK_SIZE, 0, y * -BLOCK_SIZE - BLOCK_SIZE);
            GameObject obj = Instantiate(prefabPopupPoint, pos, Quaternion.identity);
            obj.transform.parent = mTrans;
            PopupPoint popupPoint = obj.GetComponent<PopupPoint>();
            mPopupList.Add(popupPoint);
        }

```

LoadStage()にステージファイルに記述された@popupによってポップアップポイントを生成するためのコードを追加しました。
また、その下には実際にポップアップポイントをインスタンス化するメソッドとそれらの参照を取得するメソッドを実装しています。

```cs:StageManager.cs

    void Start()
    {
        mConstructor = GetComponent<StageConstructor>();
        LoadStage(1);

追加    StartCoroutine("PopupFollower");
    }

    //-------------------
    // フォロワーの管理 //
    //---------------------------------------------------------------------------------

    private readonly float POPUP_DURATION = 5.0f;
    private readonly int MAX_FOLLOWER_COUNT = 4; // アクティブ状態のフォロワー最大数
    private int mFollowerCount; // アクティブ状態のフォロワー数

    /// <summary>
    /// ステージ内に存在するフォロワーの数が最大に達していない場合は
    /// ポップアップ可能なポイントからランダムに1つ選択してフォロワーをポップアップする
    /// </summary>
    /// <returns></returns>
    private IEnumerator PopupFollower()
    {
        while (true)
        {
            yield return new WaitForSeconds(POPUP_DURATION);
            if (mFollowerCount < MAX_FOLLOWER_COUNT)
            {
                var newList = mConstructor.GetPopupList().Where(i => i.IsReadyPopupFollower());
                if (newList.Any())
                {
                    var popupPoint = newList.OrderBy(i => Guid.NewGuid()).Take(1).ToList();
                    popupPoint[0].PopupFollower();
                    mFollowerCount++;
                }
            }
        }
    }

```

新しくPopupFollower()というコルーチンを実装し、ステージを生成した直後に開始しています。
これによってステージ内に出現しているフォロワーが4匹に満たない場合は5秒毎に1匹ずつフォロワーが出現します。

![action_sheep_ss_3_8.jpg](https://qiita-image-store.s3.amazonaws.com/0/239877/6c81ca5c-c189-bdb3-1e8d-5907cd80b476.jpeg)


プロジェクトを実行してフォロワーがポップアップされることと、プレイヤーがポップアップポイントおよびフォロワーをすり抜けできることを確認してください。

---

[次のページに進む](https://qiita.com/YzRoid/items/75d4f8181b5f8a3ae5fa)
[イントロダクションに戻る](https://qiita.com/YzRoid/items/8dfd6c168b916ac82a0d)
<BR>
<div><img src="http://unity-chan.com/images/imageLicenseLogo.png" alt="ユニティちゃんライセンス"><p>この作品は<a href="http://unity-chan.com/contents/license_jp/" target="_blank">ユニティちゃんライセンス条項</a>の元に提供されています</p></div>
<Br><Br>