【Unity2d】移動処理スクリプトの生成:中編

Unity2D

Unity2D ARPG

こんにちは。なおキーヌです。

ブログ毎日更新は336日目になります。

前回はちょっとグダグダになってしまったので、後編で終わらせたかったのですが、
またまた思ったよりも長くなったので中編も追加します。

移動処理スクリプトを作成する前に、管理用のスクリプトを作成しました。

管理スクリプトを作っておけば、どこで処理してたかな……っていうのが思い出しやすくなりますし
バグが発生した時の特定もしやすくなるのでいいことづくめです。

Unityを使わないようなゲーム作りの場合、こういった管理スクリプトを作って
子クラスとして下にぶら下げていく作り方をしていました(PC版RPGツクールの影響)

しかしUnityはコンポーネント志向なので基本的に1つスクリプトをつくって、
オブジェクト毎に必要なスクリプトをアタッチしてそれぞれで動かすということが基本となっています。

ツクールの様にクラスから子クラス・孫クラスと作っても問題はないですが、
Unityの便利さが一気に消え去ってしまうのでコンポーネント志向を利用していきます。

コントローラーマネージャーの変数を静的にしよう

クラスで変数を定義するとインスタンス変数になってしまうので、
親も子も共通したクラス変数を用意する必要が出てきました。

クラス変数の指定の仕方は、アクセサの後にstaticを付けます。

すると「クラス名.変数名」でアクセスできるようになります。

「this.変数名」でアクセスできなくなっているので気を付けてください。

この時点でthisはインスタンスを表すものってことに気付きました。

今回は親子だけで使いたいのでアクセサはprotectedにしていますね。

これで親側でゲッターを設定しても取得するのはインスタンス変数ではなくなりクラス変数になるので、
子クラスからでもゲッターを呼び出しても問題なくなったわけです。

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

public class Controller_Manager : MonoBehaviour
{
    // キー入力順番配列
    protected static List<KeyCode> _PushedKeyList = new List<KeyCode>();
    protected static List<KeyCode> _PushedArrowKeyList = new List<KeyCode>();

    // _PushedKeyList用Getter
    public List<KeyCode> PushedKeyList{
        get{
             return Controller_Manager._PushedKeyList;
        }  //取得用
    }  

    protected virtual void Update() {
    }
}

個人的にちょっと引っかかってるのが変数名。

アンダースコアを付けるのはローカル変数(ここではクラス内でしかアクセスできないという意味)が、
こういったクラス変数の時でなおかつprotectedなものでもアンダースコアを名付けていいものですかね……

この辺のプログラミング規約的なことが正直あんまり理解してないので、
そろそろ理解してやって行かないとダメですね。

とりあえずこれでいきます。

間違ってたら間違ってたという事実を知ったことになり次作るときはそれをすればいいだけです。

動くのであれば今のところは問題ないので無駄なこと考えずに進みましょう!

一応整理のためにコントローラーの子クラスも記載しておきます。

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

public class Controller_Player : Controller_Manager
{

    // 毎フレーム更新
    protected override void Update()
    {
        // キー入力監視
        KeyCheck();
    }

    // キーのリスト入れ替え関数
    private void KeyAddRemove(KeyCode keycode) {
        if (Input.GetKey(keycode)) {
            // Linqの機能:リストに指定物が中身になければリストに加える
            if (!(_PushedArrowKeyList.Contains(keycode))) {
                // あれば削除
                _PushedKeyList.Add(keycode);
                // 方向キーのみ
                _PushedArrowKeyList.Add(keycode);
            }
        } else {
            // Linqの機能:リストに指定物が中身にあればリストにから外す
            if (_PushedArrowKeyList.Contains(keycode)) {
                // あれば削除
                _PushedKeyList.Remove(keycode);
                // 方向キーのみ
                _PushedArrowKeyList.Remove(keycode);
            }
        }
    }


    // キーチェック関数
    private void KeyCheck() {
        // 十字(上下左右)
        KeyAddRemove(KeyCode.LeftArrow);
        KeyAddRemove(KeyCode.RightArrow);
        KeyAddRemove(KeyCode.UpArrow);
        KeyAddRemove(KeyCode.DownArrow);

        // 掴み(G)
        KeyAddRemove(KeyCode.G);
        // 防御(S)
        KeyAddRemove(KeyCode.S);
        // 回避(D)
        KeyAddRemove(KeyCode.D);
    }
}

これでプレイヤーコントローラーで入力されて配列に格納された状態を、
プレイヤーステートからキーコントローラーマネージャーにアクセスして参照できるようになりました。

ステートクラスの確認

ステートの方も、もう一度コードを見ておきましょう。

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

public class State_Player : State_Manager
{
  // キー状態リスト
  protected List<KeyCode> _PushedKeyList;

  protected override void Start() {
      // コントローラーマネージャーのキーリストを取得
      _PushedKeyList = GameObject.FindGameObjectsWithTag("Controller_Manager")[0].GetComponent<Controller_Player>().PushedKeyList;
  }

  protected override void Update() {
      // テストで0個目の要素を表示
      ShowListContentsInTheDebugLog(_PushedKeyList);
  }
}

最終的には使いませんが、「ShowListContentsInTheDebugLog」は親であるState_Managerで定義しているので子であるState_Playerでも使えます。

Start関数の中に入っているコードが少し長いですが、やっていることは

ゲームオブジェクトであるタグ「Controller_Manager」が付いたものを探します。

FindGameObjectsWithTagは配列を返しますのがController_Managerは1つしかないので0個目を指定すればOKです。

過去にFindでオブジェクトを探すよりもタグを付けて探した方が高速だっていうのをみたことがあって
それ以来このやりかたをしているのですが1つを取るためこのやり方は果たして正しいのかは不安です。

もしかしたらUnityアップデートで逆に遅くなってるかもしれません……!

しかし問題なく取れてるので今はヨシとしましょう。

ステートクラスで状態の変化をさせる

中々移動処理を書けてないですが、今後のためにも大切になる処理なのでもう少しの我慢です。

キー入力配列をみて、ステートマネージャーにある変数を操作してみましょう。

キー配列は常に監視する形になります。

手始めにキー配列をみて向きを変更する関数を作りましょう。

基本的にキー配列に対して最初に見つかった十字入力を元に変数を切り替えるだけのシンプルな関数です。

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

public class State_Manager : MonoBehaviour
{
  // 向き
  protected static int _direction = 0;
  // 移動タイプ
  protected static int _moveType = 0;
  // アニメーション状態
  protected static int _nowAnim = 0;

  protected virtual void Start() {}
  protected virtual void Update() {}

  // 向き変数変更
  protected setDirection(KeyCode arrowDirect) {
    switch(arrowDirect) {
      case KeyCode.DownArrow:
        _direction = 0;
        break;
      case KeyCode.UpArrow:
        _direction = 1;
        break;
      case KeyCode.LeftArrow:
        _direction = 2;
        break;
      case KeyCode.RightArrow:
        _direction = 3;
        break;
      default:
        _direction = -1;
        break;
    }
    Debug.Log(_direction);
  }
}

時々ifとswitchどっちの方がいいの?ってなるのですが、色々調べていると
上記の分岐の場合ifだと右と判断するまでに3回処理が入ります。

一方switchだと1回の処理で済むので軽くなるそうです。

正直今の性能のPCではそこまでスピードに差はでないというかわからないレベルなので、
あまり気にしなくて好きな方を使えばいいと思います。

私は今後ギリギリの性能で突き詰めてコードを書くということをやるかもしれないので
このあたりのコードの最適化を意識していきたいと思っているだけです。

話を戻すと、十字に対して0~3の値を入れていますね。

最後の-1は十字以外の値を入れた時になるようになってます。

プロパティを見て_directionに-1が入っていたらバグがあるという感じですね。

次はプレイヤーステート側でキー配列を取得して、その配列の中身を関数に投げて
変数を変更する仕組みを作ってみましょう。

……と思ったのですがよく考えたら十字キーだけ格納するために「Controller_Player」で専用のリストを作っていましたね。

それを活用して0個を取るという仕組みにすればいちいちループ処理をしなくても済みそうです。

Controller_Managerクラスに以下のゲッターを追加しましょう。

    public List<KeyCode> PushedArrowKeyList{
        get{
             return Controller_Manager._PushedArrowKeyList;
        }
    }  

これによりState_Playerクラスはこうなります。

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

public class State_Player : State_Manager
{
  // キー状態リスト
  private List<KeyCode> _PushedKeyList;
  private List<KeyCode> _PushedArrowKeyList;

  protected override void Start() {
      // コントローラーマネージャーのキーリストを取得
      _PushedKeyList = GameObject.FindGameObjectsWithTag("Controller_Manager")[0].GetComponent<Controller_Player>().PushedKeyList;
      // コントローラーマネージャーの矢印のみキーリストを取得
      _PushedArrowKeyList = GameObject.FindGameObjectsWithTag("Controller_Manager")[0].GetComponent<Controller_Player>().PushedArrowKeyList;
  }

  protected override void Update() {
      // テストで0個目の要素を表示
      ShowListContentsInTheDebugLog(_PushedKeyList);
  }
}

Start関数の中身が冗長すぎますね。

ローカル変数を使ってスッキリさせてみましょう。

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


public class State_Player : State_Manager
{
  // コントローラープレイヤースクリプト
  private Controller_Player _cm;

  protected override void Start() {
      _cm = GameObject.FindGameObjectsWithTag("Controller_Manager")[0].GetComponent<Controller_Player>();
  }

  protected override void Update() {
      // テストで0個目の要素を表示
      ShowListContentsInTheDebugLog(_cm.PushedArrowKeyList);
      ShowListContentsInTheDebugLog(_cm.PushedKeyList);
  }
}

処理負荷的にはほぼ変わらないので修正が入ったときも変数の部分を変数のところだけにすれば楽になります。

こういったあからさまに冗長にコードを書いたときはスッキリさせてあげると後から見やすくなりますし
メンテ性も向上しますので積極的に最適化していきましょう。

これで十字キーだけを格納する配列も準備できたので、
コントローラー側でも十字キーだけを格納する仕組みに作り替えましょう。

Controller_PlayerのKeyAddRemove関数を改良します。

    // キーのリスト入れ替え関数
    private void KeyAddRemove(KeyCode keycode) {
        if (Input.GetKey(keycode)) {
            // Linqの機能:リストに指定物が中身になければリストに加える
            if (!(_PushedKeyList.Contains(keycode))) {
                _PushedKeyList.Add(keycode);
                // 方向キーのみ
                switch(keycode) {
                  case KeyCode.DownArrow:
                  case KeyCode.UpArrow:
                  case KeyCode.LeftArrow:
                  case KeyCode.RightArrow:
                    _PushedArrowKeyList.Add(keycode);
                    break;
                  default:
                    break;
                }
            }
        } else {
            // Linqの機能:リストに指定物が中身にあればリストにから外す
            if (_PushedKeyList.Contains(keycode)) {
                _PushedKeyList.Remove(keycode);
                // 方向キーのみ
                switch(keycode) {
                  case KeyCode.DownArrow:
                  case KeyCode.UpArrow:
                  case KeyCode.LeftArrow:
                  case KeyCode.RightArrow:
                    _PushedArrowKeyList.Remove(keycode);
                    break;
                  default:
                    break;
                }
            }
        }
    }

クラス変数は子クラスであればクラス名を省略して書くことが出来ます。

実行してみても問題なくキー配列が格納できていたらOKです。

長くなりましたがこれで十字キーだけを参照できるようになったので、
0個目の要素を取って向き変更関数を実行してみましょう。

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


public class State_Player : State_Manager
{
  // コントローラープレイヤースクリプト
  private Controller_Player _cm;

  protected override void Start() {
      _cm = GameObject.FindGameObjectsWithTag("Controller_Manager")[0].GetComponent<Controller_Player>();
  }

  protected override void Update() {
    // 向きを変更
    setMoveType(_cm.PushedArrowKeyList[0]);
  }
}

これで実行して、プロパティの値がちゃんと変更されてたら完璧です!

Unity2D 出力結果

後編は移動タイプとアニメーション切り替え関数の作成

残りは移動タイプとアニメーションタイプを切り替えて、
やっとこさ実際の移動処理スクリプトを作ることができそうです。

ちゃんとこうやって設計しておかないと何度手間にもなってしまうので、
プログラミングに慣れて来たらコードを書く前に設計してみるのも1つの上達法です。

何度も言いますが、設計とかをやったことが無い場合はがむしゃらにコードを組んで失敗しまくってください。

失敗することでこうしたらいいとか、ああしたらいいとかが浮かんできます。

失敗したら儲けものとおもいましょう。

それでは。