明日の自分へ

主にUnityのゲーム開発に関することを呟いていく予定

【Unityゲーム制作】自作ゲームを教材に制作講座風にリメイクする#3 (Unity1week202102)

自作ゲームを教材にゲーム制作の流れを眺める企画の、第3回目。
今回は最後に余談として、制作時の失敗について触れています。
こんばんは、M橋です。

今回は、次の「3」と「4」に取り組む内容です。
 

  1. ステージを作成する
  2. ステージを回転させる処理を実装する
  3. プレイヤーの操作を受け付ける処理を実装する
  4. プレイヤーの操作と、ステージの回転を紐づける
  5. 球体を作成する
  6. 球体が落下する処理を実装する
  7. ステージとの衝突処理を実装する
  8. ゴールを作成する
  9. 球体がゴールに到達したことを判定する処理を実装する
  10. 障害物を作成する
  11. 球体との衝突処理を実装する
  12. 時間を計測するタイマーを作成する
  13. 時間を表示するUIを作成する

本記事の制作環境は以下の通りです。

  • OS : Windows 10 64bit
  • Unity : 2020.1.3f1 Personal

目標

前回の記事で、「オブジェクト(ステージ)を回転させる処理を実装する」ところまで行いました。
ですが、動きを眺めているだけではゲームになりません。
今回は、ゲームに必須の「プレイヤーの操作をゲーム内処理に紐づける」ことが目標です。

手順

ざっくりと以下のようになります。

  • プレイヤーの操作を受け付ける処理を実装する
    • 操作方法を決める
    • プレイヤーの操作をスクリプトで受け取る処理を実装する
  • プレイヤーの操作と、ステージの回転を紐づける
    • プレイヤーの操作を、ゲーム内の意味のある値に変換する
    • 操作から受け取った値をオブジェクトの回転処理に組み込む

順番に見ていきましょう。

操作方法を決める

一口に操作方法と言っても、最近では様々な選択肢があります。
最も多いと思われるのは、スマホゲーム等のタッチ・スワイプ等の操作。
PCゲームであればマウス・キーボード入力やゲームパッド
操作方法の選択肢になります。
Unityではこれらの操作方法に合った処理が用意されているので
プレイヤーにどんな操作をさせたいか
というところから、ゲームでの実装を考えていきます。

今回はマウス一つで操作できることで進めていきます。

プレイヤーの操作をスクリプトで受け取る処理を実装する

プレイヤーのマウス操作を受け付けるために、次の関数があります。*1

public static bool GetMouseButton(int button); // (1)
public static bool GetMouseButtonDown(int button); // (2)
public static bool GetMouseButtonUp(int button); // (3)

それぞれ、次の用途に使えます。

  1. マウスが押されている間に、何か処理をしたい時
  2. マウスが押された瞬間に、何か処理をしたい時
  3. マウスが押された後に離された瞬間に、何か処理をしたい時

例えば、次のスクリプトは、それぞれの処理が行われたフレームを表示させます。

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Debug.Log($"Mouse Pressed at {Time.frameCount}");
        }
        if (Input.GetMouseButton(0))
        {
            Debug.Log($"Mouse Held at {Time.frameCount}");
        }
        if (Input.GetMouseButtonUp(0))
        {
            Debug.Log($"Mouse Released at {Time.frameCount}");
        }
    }

このスクリプトを実行すると、マウス操作によって下のようなログが表示されます。

f:id:e684-creaim:20210321142019p:plain
マウス左ボタンを押した時
f:id:e684-creaim:20210321142053p:plain
マウス左ボタンを離した時

これらの関数を使うことで、マウス操作に応じた何らかの処理をすることが出来ます。

プレイヤーの操作を、ゲーム内の意味のある値に変換する

この辺りからゲーム開発っぽくなります。*2
というのも、ここまではお決まりの作業のようなものだからです。
オブジェクトの回転も、マウス操作の受付も、
既にある物・手順をなぞっているに過ぎません。
しかし、ここで意味のある値を考え始めたところから、
このゲームをどう構築してやろうかという
開発者の意思が試されることになります。

と、大げさな話は脇に置きまして、
ここで「プレイヤーの操作」とは何かをおさらいしましょう。
今回のゲームでは「重力の向きを変えることがプレイヤーの操作」としています。
具体的には「ステージを回転させることがプレイヤーの操作」です。
では、ステージを回転とは何でしょうか。

前回、ステージを回転させる処理を実装しました。
実装したコードを抜粋すると

transform.Rotate(new Vector3(0f, 0f, rotationSpeed) * Time.deltaTime);

transform.Rotateという関数を使っていますね。
transform.Rotateは、「オブジェクトを、現在の状態から指定された値だけ回転させる」機能を持った関数です。
そのため引数に設定する値は、「現在の状態から回転させたい量」になります。
前回の実装では、rotationSpeedという固定の値を使って、
毎フレーム回転させたい量を計算して引数にしていたのです。*3

では、この回転させたい量をプレイヤー操作から得るにはどうすれば良いでしょうか。
結論としては、次の流れで取得することができます。

  1. 前フレームのマウス位置と現フレームのマウス位置の差分を計算する
  2. マウス位置の差分をステージの回転量に変換する
  3. 次フレームの計算用に現フレームのマウス位置を保存する

1.で、プレイヤーの操作によって変化する値を取得します。
2.で、その値を今回の目的である回転させたい量に変換します。
3.は、今回の実装が前フレームとの差分を基準であるために必要な内部処理です。*4

実装では次のようなスクリプトになります。
Unity上でマウス位置というと、Vector3という3次元の型で扱われますが、
今回は3次元も情報は不要です。
マウスを左右に移動させればステージが回転するという法則を作り、
X座標のみを扱うようにしています。*5

using UnityEngine;

namespace chapter2
{
    /// <summary>
    /// ステージの動きを制御するクラス
    /// </summary>
    public class StageController : MonoBehaviour
    {
        public float sensitivity = 40f; // マウス感度

        private float rotationSpeed = 0f; // 回転速度
        private float previousMousePositionX; // 前フレームでのマウスカーソル位置(X座標)

        // Update is called once per frame
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                // マウスクリックの最初の位置を保存する
                previousMousePositionX = Input.mousePosition.x; // (0)
            }
            if (Input.GetMouseButton(0))
            {
                // マウスポインタの移動量を計算する
                float move = Input.mousePosition.x - previousMousePositionX; // (1)

                // マウスポインタの移動量を回転速度に変換する
                rotationSpeed = move * sensitivity; // (2)

                // マウスクリックの位置を保存する
                previousMousePositionX = Input.mousePosition.x; // (3)
            }

            // 回転処理
            transform.Rotate(new Vector3(0f, 0f, rotationSpeed) * Time.deltaTime);
        }
    }
}

Update関数の中身が一気に増えましたが、処理が4行追加されているだけです。

0. マウスクリックの最初の位置を保存する

if (Input.GetMouseButtonDown(0))
{
    previousMousePositionX = Input.mousePosition.x; // (0)
}

最初にクリックした瞬間だけ、使う関数が異なるので条件式で分けています。
本質的には(3)と変わりませんので、ここでは説明を省略します。

1. マウスポインタの移動量を計算する
2. マウスポインタの移動量を回転速度に変換する
3. マウスクリックの位置を保存する

if (Input.GetMouseButton(0))
{
    float move = Input.mousePosition.x - previousMousePositionX; // (1)
    rotationSpeed = move * sensitivity; // (2)
    previousMousePositionX = Input.mousePosition.x; // (3)
}

現フレームのマウス位置はInput.mousePosition.xで取得できます。
そして前フレームでのマウス位置は、previousMousePositionXに保存しています。
この2つの差分を取ることで、1フレーム分のマウス移動量が計算できます。

moveは、(1)で計算したマウスの移動量でした。
それをそのままtransform.Rotateの引数に使っても良いのですが、
プレイヤーの操作に直接関わる箇所なので、調整をしたくなることが多々あります。
sensitivityは、その調整用に用意した変数です。
例えば、マウスを左に移動させたときに「ステージが左回転する or 右回転する」
変化させたい時などに使います。

  • (3) マウスクリックの位置を保存する

これは(1)で計算するために現在の値を保存しています。
(0)で書いた処理も、同じ理由ですね。

操作から受け取った値をオブジェクトの回転処理に組み込む

rotationSpeed変数の値が、前回は固定値だったものが
上で書いたように計算した値に変わりました。
ただし、実装は前回と変わっていません。

以上のようにスクリプトを変更すると
このようにステージを回転させることができるようになります。

「(2) マウスポインタの移動量を回転速度に変換する」で調整について書いたように、
回転の方向を逆にすることも出来ます。

まとめ

今回は、プレイヤーの操作をゲームに反映させる部分として
マウス操作でステージを回転させることについて書きました。
次回はプレイヤーの分身となるキャラクターを実装していきます。

余談

さて、冒頭に挙げたUnity1Week参加中に失敗した話について、ここに書きます。
ここからは余談ですので、興味のある方以外はスルーしてしまってください。

記事中ではステージを回転させる処理について、唐突にtransform.Rotate関数を引っ張り出してきました。
ただ、実際の開発では、やりたいことを実現する手段は複数あります
その中で、どれが今の開発に最適かは自分で判断する必要があります。*6
今回の場合は、プレイヤー操作を受け付ける手段
ステージを回転させる手段の2つが該当します。
と言ってもこの2つは対になっているので、どちらかを決めるともう一方も殆ど決定されます。

実は私の場合、開発時点ではプレイヤー操作を受け付ける手段を先に考えていました。
その時の流れは、

  1. クリックされた時のマウス位置を保存する
  2. クリックされた時のステージの回転値を保存する
  3. 現フレームのマウス位置とクリック位置の差分をステージの回転量に変換する

となります。
記事中の流れと同じく3段階ですが、こちらはステージを回転させる手段の考えが少し複雑になっていたのです。
そして、そもそも上の流れで実装する必要性を考えるまでに
時間がかかってしまいました。いわゆる「手段が目的化する」状態です。
結果的に、ステージを回転させる手段から実現を考えた結果、
この記事と同じ流れに至っています。

なお、上の流れを実装したものを載せておきます。
(記事が長くなってしまったので、解説については省略させていただきます)

using UnityEngine;

namespace chapter2
{
    /// <summary>
    /// ステージの動きを制御するクラス
    /// </summary>
    public class StageController2 : MonoBehaviour
    {
        public float sensitivity = 0.05f; // マウス感度

        private float rotationValue = 0f; // 回転量
        private Quaternion baseRotation; // クリック時の回転値
        private float clickPositionX; // クリック位置(X座標)

        // Update is called once per frame
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                // マウスクリックの最初の位置を保存する
                clickPositionX = Input.mousePosition.x;

                // マウスクリック時点の回転の値を基準値として保存する
                baseRotation = transform.rotation;
            }
            if (Input.GetMouseButton(0))
            {
                // マウスポインタの移動量を計算する
                float move = Input.mousePosition.x - clickPositionX;

                // マウスポインタの移動量を回転量に変換する
                rotationValue = move * sensitivity;
            }

            // 回転処理。基準値から回転量だけ回転させる
            transform.rotation = baseRotation * Quaternion.Euler(new Vector3(0f, 0f, rotationValue));
        }
    }
}

どのように実現するかが自由な分、なぜそれが必要なのか
良く考える癖をつけておかないといけない事例でした。

*1:詳細は公式のリファレンスをご覧ください。https://docs.unity3d.com/ScriptReference/Input.html

*2:少なくとも私はそう思ってます

*3:Time.deltaTimeはゲームの実行状況によって変動するので、計算結果は一定ではありません

*4:後述する失敗は、ここで言う基準と関連します

*5:このような単純なところからゲーム開発は始まります

*6:実装に限らず、設計についても言えます。さらに個人開発なら猶更のことですね。