[Unity]2D牧場ゲームを作ってみる　その４[キャラクターの移動・会話処理を切り分け]

# 前置き

- 前回の記事で、他のキャラクターに話しかけたときにメッセージを表示をするように実装しました。
  - [[Unity]2D牧場ゲームを作ってみる　その３[メッセージ表示]](https://qiita.com/kuro_take/items/97d61f55753a55495b34)

- 今回作成するものは大きく分けて以下の2つになります。
  - 主人公のキャラクターが会話している時は移動処理をしない。
  - 会話が終了したら移動処理に切り替えるようにする。
- 前回までの実装した動きは以下になります。
  - 会話中に`→`キーを押しているため、ずっと右に進んでいますね…。
  - 会話中は移動しないように修正していきます。

![GameProject_1_2022-02-12_00-42-20.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763295/9de3cfa7-d50b-a1c3-6fee-23d27228203b.gif)


# 今回作成するもの

- 1.キャラクターの移動・会話処理の切り分けスクリプト`MoveAndTalkChara`の追加
- 2.メッセージ表示のスクリプト`MessageCharactor`の変更
- 3.フィールドオブジェクトの基本処理のスクリプト`FieldObjectBase.cs`の変更
- 4.キー操作にあわせアニメーションに状態をわたすスクリプト`AnimationStateController.cs`の変更


## 1.キャラクターの移動・会話処理の切り分けスクリプト`MoveAndTalkChara.cs`の追加

- 新規に`MoveAndTalkChara.cs`を作成しました。このスクリプトは主人公のキャラクターの移動と会話処理を切り分けるものとなります。
    - 参考にしたサイト：[Unityでキャラクターの移動と会話処理を切り分ける](https://gametukurikata.com/program/moveandtalkcharacter)
- 1-1~1-4で説明をした後、1-5で`MoveAndTalkChara.cs`の全体を載せます。

### 1-1.キャラクターの状態を列挙型で作成

- キャラクターが移動している状態、会話している状態を列挙型で作成し、キャラクターがその状態を保持できるようにします。
- 移動している状態は`normal`、会話している状態は`talk`と設定します。他の状態を作成する時は、`CharacterState`に追加していきましょう。
- キャラクターの状態を`characterState`に保持するようにします。

```cs
    public enum CharacterState{
        normal,
        talk
    }
    private CharacterState characterState;
```



### 1-2.Updateメソッドで行動処理を分ける


- Updateメソッドで、行動処理をわけます。
- キャラクターが移動している状態では`Move()`、会話している状態では`Talk()`と呼び出すようにします。
- `animationStateController.~`は4の`AnimationStateController.cs`で説明します。

```cs
    void Update(){


        if(characterState == CharacterState.normal){
            animationStateController.MoveAnimation();
            Move();
        }else if(characterState == CharacterState.talk){
            animationStateController.NoMoveAnimation();
            Talk();
        }
        
    }
```



### 1-3.MoveとTalkメソッド

- `Update`メソッドでMoveとTalkメソッドを呼び出しているのでその中身を記述します。
- Moveでは`MoveController.cs`のコードを設定します。そのため`MoveController.cs`は不要になるため、主人公のキャラクターのInspectorからチェックを外しましょう。
- Talkは会話中に何かしら処理を入れたい時に入れましょう。

```cs
    void Move(){
        // 入力を取得
        input = new Vector2(
            Input.GetAxis("Horizontal"),
            Input.GetAxis("Vertical"));

        if (input == Vector2.zero) {
            return;
        }
        rigidBody.position += input * SPEED * Time.deltaTime;

        
    }

    void Talk(){
        
    }
```

### 1-4.主人公の状態の取得と設定の処理

- 主人公の状態を取得したり、状況を切り替える必要があるので、そのための処理を記述します。


```cs
    // 状態確認メソッド
    public CharacterState GetState(){
        return characterState;
    }

    // 状態変更メソッド
    public void SetState(CharacterState setState){
        characterState = setState;
    }
```

### 1-5.`MoveAndTalkChara.cs`の全体

- 1~1から1~4で説明したコードをまとめたいと思います。

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class MoveAndTalkChara:MonoBehaviour{

    public float SPEED = 0.5f;
    private Rigidbody2D rigidBody;
    private Vector2 input;
    public enum CharacterState{
        normal,
        talk
    }
    private CharacterState characterState;
    AnimationStateController animationStateController;
    Animator animator;
    
    void Start(){
        this.rigidBody = GetComponent<Rigidbody2D>();
        characterState = CharacterState.normal;
        this.animator = GetComponent<Animator>();

        animationStateController = GameObject.Find("char_crol_0").GetComponent<AnimationStateController>();
    }
    void Update(){


        if(characterState == CharacterState.normal){
            animationStateController.MoveAnimation();
            Move();
        }else if(characterState == CharacterState.talk){
            animationStateController.NoMoveAnimation();
            Talk();
        }
        
    }

    void Move(){
        // 入力を取得
        input = new Vector2(
            Input.GetAxis("Horizontal"),
            Input.GetAxis("Vertical"));

        if (input == Vector2.zero) {
            return;
        }
        rigidBody.position += input * SPEED * Time.deltaTime;

        
    }

    void Talk(){
        
    }

    // 状態確認メソッド
    public CharacterState GetState(){
        return characterState;
    }

    // 状態変更メソッド
    public void SetState(CharacterState setState){
        characterState = setState;
    }
}
```



## 2.メッセージ表示のスクリプト`MessageCharactor.cs`の変更

- `MessageCharactor.cs`に3つのコードを追加しました。
  - `moveAndTalkChara.SetState(MoveAndTalkChara.CharacterState.talk);`
    - 会話をする時に、`talk`に設定  
  - `moveAndTalkChara.SetState(MoveAndTalkChara.CharacterState.normal);`
    - 移動する時に、`normal`に設定

```cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class MessageCharactor : FieldObjectBase {

    // セリフ : Unityのインスペクタ(UI上)で会話文を定義する 
　　// （次項 : インスペクタでscriptを追加して、設定をする で説明）
    [SerializeField]
    private List<string> messages;
    MoveAndTalkChara moveAndTalkChara;

    void Start(){
        moveAndTalkChara = GameObject.Find("char_crol_0").GetComponent<MoveAndTalkChara>();
    }

    // 親クラスから呼ばれるコールバックメソッド (接触 & ボタン押したときに実行)
    protected override IEnumerator OnAction() {
        for (int i = 0; i < messages.Count; ++i) {
            moveAndTalkChara.SetState(MoveAndTalkChara.CharacterState.talk);

            // 1フレーム分 処理を待機(下記説明1)
            yield return null;

            // 会話をwindowのtextフィールドに表示
            showMessage(messages[i]);

            // キー入力を待機 (下記説明1)
            // yield return new WaitUntil(() => Input.anyKeyDown);
            yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
        }

        moveAndTalkChara.SetState(MoveAndTalkChara.CharacterState.normal);

        yield break;
    }
}
```

## 3.フィールドオブジェクトの基本処理のスクリプト`FieldObjectBase.cs`の変更


- `Fixedupdate()`を`Update()`に変更しただけになります。
  - 変更理由は、`Fixedupdate()`だとキーを押しているのに入力されない、反応しない、反応する時としない時があるためです。
  - [【Unity】 FixedUpdate と Input.GetKeyDown](http://unitygametsukuruyo.blogspot.com/2014/11/sample-assets-2d-character.html)
- 変更点は少ないですが、全体のコードは以下になります。

```cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

/**
 * フィールドオブジェクトの基本処理
 */
public abstract class FieldObjectBase : MonoBehaviour
{

    // Unityのインスペクタ(UI上)で、前項でつくったオブジェクトをバインドする。
　　// （次項 : インスペクタでscriptを追加して、設定をする で説明）
    public Canvas window;
    public Text target;

    // 接触判定
    private bool isContacted = false;
    private IEnumerator coroutine;


    // colliderをもつオブジェクトの領域に入ったとき(下記で説明1)
    private void OnTriggerEnter2D(Collider2D collider) {
        isContacted = collider.gameObject.tag.Equals("Player");
    }

    // colliderをもつオブジェクトの領域外にでたとき(下記で説明1)
    private void OnTriggerExit2D(Collider2D collider) {
        isContacted = !collider.gameObject.tag.Equals("Player");
    }

    private void Update() {
        if (isContacted && coroutine == null && Input.GetButton("Submit") && Input.anyKeyDown) {
            coroutine = CreateCoroutine();
            // コルーチンの起動(下記説明2)
            StartCoroutine(coroutine);
        }
    }

    /**
     * リアクション用コルーチン(下記で説明2)
     */
    private IEnumerator CreateCoroutine() {
        // window起動
        window.gameObject.SetActive(true);

        // 抽象メソッド呼び出し 詳細は子クラスで実装
        yield return OnAction();

        // window終了
        this.target.text = "";
        this.window.gameObject.SetActive(false);

        StopCoroutine(coroutine);
        coroutine = null;
    }

    protected abstract IEnumerator OnAction();

    /**
     * メッセージを表示する
     */
    protected void showMessage(string message) {
        this.target.text = message;
    }
}
```


## 4.キー操作にあわせアニメーションに状態をわたすスクリプト`AnimationStateController.cs`の変更

- Updateメソッドを`MoveAnimation`に変更しましょう。
- 移動しない、会話の時は`NoMoveAnimation`を呼び出します。

```cs
    public void MoveAnimation() {
        if (Input.anyKeyDown) {
            Vector2? action = this.actionKeyDown();
            if (action.HasValue) {
                // キー入力があればAnimatorにstateをセットする
                setStateToAnimator(vector: action.Value);
                return;
            }
        }
        // 入力からVector2インスタンスを作成
        Vector2 vector = new Vector2(
            (int)Input.GetAxis("Horizontal"),
            (int)Input.GetAxis("Vertical"));

        // キー入力が続いている場合は、入力から作成したVector2を渡す
        // キー入力がなければ null
        setStateToAnimator(vector: vector != Vector2.zero? vector : (Vector2?)null);

    }

    public void NoMoveAnimation(){
        this.animator.speed = 0.0f; 
    }
```

- 2つのメソッドを追加しました。あまり変更点はないです。
- 全体のコードは以下になります。

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/**
 * キャラの向き状態を変更するController
 */
public class AnimationStateController : MonoBehaviour {
    Animator animator;

    void Start() {
        // Animatorを取得する
        this.animator = GetComponent<Animator>();
    }

    /**
     * Update は MonoBehaviour が有効の場合に、毎フレーム呼び出されます
     * https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.Update.html
     */
    // void Update() {
    public void MoveAnimation() {
        if (Input.anyKeyDown) {
            Vector2? action = this.actionKeyDown();
            if (action.HasValue) {
                // キー入力があればAnimatorにstateをセットする
                setStateToAnimator(vector: action.Value);
                return;
            }
        }
        // 入力からVector2インスタンスを作成
        Vector2 vector = new Vector2(
            (int)Input.GetAxis("Horizontal"),
            (int)Input.GetAxis("Vertical"));

        // キー入力が続いている場合は、入力から作成したVector2を渡す
        // キー入力がなければ null
        setStateToAnimator(vector: vector != Vector2.zero? vector : (Vector2?)null);

    }

    public void NoMoveAnimation(){
        this.animator.speed = 0.0f; 
    }

    /**
     * Animatorに状態をセットする
     *    
     */
    private void setStateToAnimator(Vector2? vector) {
        if (!vector.HasValue) {
            this.animator.speed = 0.0f;
            return;
        }

        Debug.Log(vector.Value);
        this.animator.speed = 1.0f;
        this.animator.SetFloat("x", vector.Value.x);
        this.animator.SetFloat("y", vector.Value.y);

    }

    /**
     * 特定のキーの入力があればキーにあわせたVector2インスタンスを返す
     * なければnullを返す   
     */
    private Vector2? actionKeyDown() {
        if (Input.GetKeyDown(KeyCode.UpArrow)) return Vector2.up;
        if (Input.GetKeyDown(KeyCode.LeftArrow)) return Vector2.left;
        if (Input.GetKeyDown(KeyCode.DownArrow)) return Vector2.down;
        if (Input.GetKeyDown(KeyCode.RightArrow)) return Vector2.right;
        return null;
    }
}
```




# 完成

![GameProject_1_2022-02-13_21-52-46.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/763295/73d2a9bb-d979-2f4e-33ed-2801fd95540e.gif)


# 次に作成するもの（仮）

- アイテムの持ち上げ関係

# 参考にしたサイト
- [Unityでキャラクターの移動と会話処理を切り分ける](https://gametukurikata.com/program/moveandtalkcharacter)

- [【Unity】 FixedUpdate と Input.GetKeyDown](http://unitygametsukuruyo.blogspot.com/2014/11/sample-assets-2d-character.html)