見出し画像

【Unity】3Dローグライクゲームの作り方〜Step3-2〜

前回の記事はこちら
前回は床とユニティちゃんの設置とユニティちゃんに歩きのアニメーションをつけるところまでを行いました。
今回からプログラミングを始めたいと思います。

EDirの定義

それではまず最初に「EDir」を定義するところから始めましょう。まず、プロジェクトタブの「Scripts」フォルダを開きます。その中で右クリック→「作成」→「C# スクリプト」を選択、名前を「EDir」にします。
作成したスクリプトをダブルクリックすると「Visual Studio」が立ち上がります。「EDir.cs」の中身を全て消して、以下のコードを追加して下さい。

/**
* 方向を表す定数
* 今回は4方向のみをサポートします
*/
public enum EDir
{
   Pause, // 静止(NULLの代わり)
   Left,  // 左
   Up,    // 上
   Right, // 右
   Down,  // 下
};

コメントの通り、今回は4方向のみをサポートする形で行きたいと思います。
enumとはC#において列挙型と呼ばれるものです。定数をひとまとめにしてわかりやすくしたもの、という捉え方で大丈夫です。使用する際は「EDir.Left」のようにします。

ユニティちゃんを歩かせる

何はともあれ、ユニティちゃんを歩かせてみましょう。まず、先ほどと同じ手順でC#スクリプトを作成し、名前を「PlayerMovement」にします。
以下のスクリプトを書きます。

using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
   public Animator animator;
   public float speed = 0.9f;
   public float speedDampTime = 0.1f;

   private readonly int hashSpeedPara = Animator.StringToHash("Speed");

   // Start is called before the first frame update
   void Start()
   {
       
   }
    
   // Update is called once per frame
   private void Update()
   {
       transform.position += transform.forward * speed * Time.deltaTime;
       animator.SetFloat(hashSpeedPara, speed, speedDampTime, Time.deltaTime);
   }
}

次にVisual Studioの左上にある「▶︎」ボタンをクリックします。真ん中のインフォメーションに「ビルドに成功しました。」と表示されたらOKです。「■」ボタンを押してストップします。
ユニティに戻って、ヒエラルキー内の「Player」のインスペクターに「PlayerMovement」をドラッグ&ドロップします。そして、「Player Movement」の中の「アニメーター」欄に「Player」をドラッグ&ドロップして下さい。
テストプレイをしてみましょう。

スクリーンショット 2020-04-25 5.04.50

このようにユニティちゃんが真っ直ぐ歩いてくれば成功です。
しかしおかしいですね......。どうしてユニティちゃんは床がないところでも落ちないのでしょうか?
理由は簡単、コライダーを設定していないからです。しかし今回のゲームでは(今のところ)必要ないので設定しません。


それではスクリプトの説明をしたいと思います。

transform.position += transform.forward * speed * Time.deltaTime;

transformにはこのスクリプトがアタッチされているゲームオブジェクト(この場合はPlayer)のトランスフォームコンポーネントが入ります。このコンポーネントは通常ゲームオブジェクトには必ず含まれているもので、外すことはできません。
その「position」(つまり位置)はX、Y、Z方向の値を示すVector3オブジェクトです。同じように「forward」はVector3オブジェクトで、これはそのゲームオブジェクトの青軸にあたる方向の基準ベクトルの値(つまり前進方向に1だけ進む値)を返します。
これにスピードとデルタタイム(最後のフレームから経過した時間)を掛け合わせると、1フレームにどれだけ進めばいいかがわかります。そしてこれを現在地のベクトルに毎フレーム加えることで、滑らかに真っ直ぐ進む動きが再現できるという訳です。

animator.SetFloat(hashSpeedPara, speed, speedDampTime, Time.deltaTime);

これはアニメーター内の「Speed」パラメーターを設定するメソッドです。
設定するパラメーターはハッシュ値で指定します。このハッシュ値は一回のプレイ中に値が変わることはないので、「readonly」(読み込み専用)をつけて予め取得しておきます。
次にそのパラメーターの目指す値を設定します。今回だと最終的には「speed」変数の値になればいいということです。
次は「DampTIme」(つまり変化する勢い)がどのくらいかを指定します。最後に、現在のフレームのデルタタイムを指定します。
これでいきなりではなく、ゆっくりとアニメーションを変化させることができるようになります。以上です。
ですがついでにUnityにおける「public」と「private」の違いについて軽く説明しておこうと思います。
publicがついている変数はUnityエディター内でも値を変更できますが、privateがついている変数はそのスクリプト内でしか値を変更できません。
今回の場合、「speed」と「speedDampTime」は後から値を調整できるようにpublicにしてあります。「Player Movement」のパラメーターの値を変えてみて、自分好みの設定に調節して下さい。

ユニティちゃんをキー操作で動かす

さて、ユニティちゃんは歩き始めましたが、勝手に前進するだけでこれではゲームとは言えません。という訳で、せめてユニティちゃんを自由に動かせるようにしましょう。
「PlayerMovement」スクリプトをダブルクリックし、Visual Studioを開きます。コードを以下のように編集して下さい。

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
   public Animator animator;
   public EDir direction = EDir.Up;
   public float speed = 0.9f;
   public float speedDampTime = 0.1f;

   private readonly int hashSpeedPara = Animator.StringToHash("Speed");

   // Start is called before the first frame update
   void Start()
   {
       
   }
    
   // Update is called once per frame
   private void Update()
   {
       EDir d = KeyToDir();
       if(d == EDir.Pause)
           animator.SetFloat(hashSpeedPara, 0.0f, speedDampTime, Time.deltaTime);
       else
       {
           direction = d;
           transform.rotation = DirToRotation(direction);
           transform.position += transform.forward * speed * Time.deltaTime;
           animator.SetFloat(hashSpeedPara, speed, speedDampTime, Time.deltaTime);
       }
   }

   /**
   * 入力されたキーに対応する向きを返す
   */
   private EDir KeyToDir()
   {
       if (!Input.anyKey)
       {
           return EDir.Pause;
       }
       if (Input.GetKey(KeyCode.LeftArrow))
       {
           return EDir.Left;
       }
       if (Input.GetKey(KeyCode.UpArrow))
       {
           return EDir.Up;
       }
       if (Input.GetKey(KeyCode.RightArrow))
       {
           return EDir.Right;
       }
       if (Input.GetKey(KeyCode.DownArrow))
       {
           return EDir.Down;
       }
       return EDir.Pause;
   }

   /**
   * 引数で与えられた向きに対応する回転のベクトルを返す
   */
   private Quaternion DirToRotation(EDir d)
   {
       Quaternion r = Quaternion.Euler(0, 0, 0);
       switch (d)
       {
           case EDir.Left:
               r = Quaternion.Euler(0, 270, 0); break;
           case EDir.Up:
               r = Quaternion.Euler(0, 0, 0); break;
           case EDir.Right:
               r = Quaternion.Euler(0, 90, 0); break;
           case EDir.Down:
               r = Quaternion.Euler(0, 180, 0); break;
       }
       return r;
   }
}

一気にコード量が増えた気がしますが、やっていることは単純です。

private EDir KeyToDir()
{
    if (!Input.anyKey)
    {
        return EDir.Pause;
    }
    if (Input.GetKey(KeyCode.LeftArrow))
    {
        return EDir.Left;
    }
    if (Input.GetKey(KeyCode.UpArrow))
    {
        return EDir.Up;
    }
    if (Input.GetKey(KeyCode.RightArrow))
    {
        return EDir.Right;
    }
    if (Input.GetKey(KeyCode.DownArrow))
    {
        return EDir.Down;
    }
    return EDir.Pause;
}

「KeyToDir」メソッドはキー入力を受け取って、対応する向きの定数(EDir)を返します。
中身を説明しますと、「GetKey」関数は引数に渡したキーコードに対応するキーが入力されているかどうかをみるもので、押している間ずっと関数が呼び出される毎にTrueを返します。今回の場合は方向キー全てにおいて、入力されているかどうかをみて処理を分岐しています。
しかし、キーが入力されていないにも関わらず、毎フレーム全ての判定を行うのは無駄です。なので最初に、「anyKey」の状態を見てこの後の処理を行うか決めます(「anyKey」には入力されているキーがあればTrueが格納される)。
もし何もキーが入力されていなければ、NULL値代わりの「EDir.Pause」を返し、処理を中断します。同様に、入力されているキーが方向キーでない場合(例えば「Z」キーなど)であっても、「EDir.Pause」を返します。

private Quaternion DirToRotation(EDir d)
{
    Quaternion r = Quaternion.Euler(0, 0, 0);
    switch (d)
    {
        case EDir.Left:
            r = Quaternion.Euler(0, 270, 0); break;
        case EDir.Up:
            r = Quaternion.Euler(0, 0, 0); break;
        case EDir.Right:
            r = Quaternion.Euler(0, 90, 0); break;
        case EDir.Down:
            r = Quaternion.Euler(0, 180, 0); break;
    }
    return r;
}

「DirToRotation」メソッドは引数で与えた向きの定数に対応した回転の四次元ベクトルを返します。まずrに初期値を設定します(この場合は上方向)。そしてswitch文でそれぞれの向きに対応した回転のベクトルをrに代入しています。
「Euler」関数はX、Y、Z方向の角度を指定することで四次元の回転ベクトルに変換してくれる優れものです。
因みに今回は、操作しやすいように現在のカメラの向きに合わせた回転角度になっています。今後カメラの位置を調節する際に、値を変更する可能性があります。

private void Update()
{
    EDir d = KeyToDir();
//       : (省略)
}

最後にこの部分を説明させて下さい。
直接変数directionにKeyToDirメソッドの返り値を代入しないのは、EDir.Pauseが返される可能性があるからです。directionの値がEDir.Pauseで上書きされてしまったら、実際に向いている方向がわからなくなります。それは後々困るということで間接的に値を受け取るようにしています。

それではビルドして、実際にテストプレイしてみましょう。
今度は勝手に動き出さないはずです。
何か方向キーを入力してみましょう。その方向に動いたら成功です。

という訳でユニティちゃんを動かすことができました。しかし待って下さい、一般的なローグライクって決まったマス目に沿って進むものではなかったでしょうか。なのにこのスクリプトでは自由に動き回れてしまいます。
これではいけないということで、次回はグリッド移動を実装したいと思います。

この記事が気に入ったらサポートをしてみませんか?