Unity 6でスワイプして車を走らせる ― SwipeCarで学ぶスワイプ判定・効果音・距離表示(PC版)

広告

マウスで車を「シュッ」とスワイプして走らせ、旗の手前でぴったり止める――今回はそんなゲーム「SwipeCar」を作ります。

前回のルーレットでは、回転の速さに0.96を掛け続けて「だんだん止まる」動きを作りました。SwipeCarでも同じ考え方を使います。違うのは、回転ではなく移動になること、そしてスワイプの長さで速さが決まることです。さらに、効果音と「旗までの残り距離」の表示も加えて、少しゲームらしくしていきます。

Unity 6でルーレットを作る ― クリックで回して、だんだん止める仕組み(PC版)

この記事で作るもの

  • 画面の左に車、右に旗を置く
  • マウスを押して横に動かし、離すと車が走り出す(スワイプ)
  • スワイプが長いほど速く走り、だんだん遅くなって止まる
  • 走り出すときに効果音が鳴る
  • 画面に「旗までの残り距離」を表示する
SwipeCarの流れ:マウスを右にスワイプすると車が動き出し、だんだん止まり、旗までの残り距離が表示される
項目内容
UnityUnity 6(サンプルは6000.1で作成)
テンプレートUniversal 2D(URP)
入力Input System(UnityEngine.InputSystem)
文字の表示TextMeshPro(TMPro)
操作PC版:マウスでスワイプ

この記事では、スクリプトを2つ作ります。役割を分けておくと、どこに何を書けばよいかが分かりやすくなります。

スクリプト付ける場所役割
CarController車(car)スワイプを受け取って、車を走らせる
GameDirector空のオブジェクト(GameDirector)車と旗の距離を計算して、画面に表示する

1. シーンを準備する

使う素材は、車(car.png)、旗(flag.png)、地面(ground.png)の画像3枚と、効果音(car_se.mp3)です。Projectウィンドウにドラッグ&ドロップして取り込み、画像をSceneビューに配置します。

オブジェクトPositionメモ
地面(ground)(0, -5, 0)Scaleを(18, 1, 1)にして横に伸ばす
車(car)(-7, -3.7, 0)画面の左端。スクリプトを付ける
旗(flag)(7.5, -3.5, 0)画面の右端。ゴールの目印
Main Camera(0, 0, -10)背景色は好みで(サンプルは薄いベージュ)

画像をシーンにドラッグすると、オブジェクト名が car_0 のように「画像名_番号」になることがあります。後で出てくる GameObject.Find はこの名前で探すので、Hierarchyに表示されている名前をメモしておきましょう。

2. 車を走らせる(CarController)

ルーレットのときと同じように、3つのステップで少しずつ機能を足していきます。Projectウィンドウで Create → Scripting → MonoBehaviour Script を選んで CarController を作り、Hierarchyの車にドラッグ&ドロップして付けてください。

ステップ1:クリックしたら走り出し、だんだん止まる

using UnityEngine;
using UnityEngine.InputSystem;   // Mouse を使うために必要

public class CarController : MonoBehaviour
{
    float speed = 0;   // 1フレームあたりに進む距離

    void Start()
    {
        // Update が1秒間に60回を目安に呼ばれるようにする
        Application.targetFrameRate = 60;
    }

    void Update()
    {
        // クリックした瞬間に、速さを与える
        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            speed = 0.2f;
        }

        // X方向(右)へ speed だけ進める
        transform.Translate(speed, 0, 0);

        // 速さを少しずつ落とす(毎フレーム98%にする)
        speed *= 0.98f;
    }
}

ルーレットと見比べてみてください。transform.Rotate(回転)が transform.Translate(移動)に変わっただけで、「速さを与える → 動かす → 速さを減らす」という流れはまったく同じです。

ルーレットSwipeCar
動かし方transform.Rotate(0, 0, rotSpeed)transform.Translate(speed, 0, 0)
減速rotSpeed *= 0.96fspeed *= 0.98f
速さの決まり方クリックで常に10スワイプの長さで変わる(次のステップ)

ステップ2:スワイプの長さで速さを決める

次に、クリックではなくスワイプで走らせます。「マウスを押した位置」と「離した位置」を記録して、その差(スワイプの長さ)を速さに変えます。

using UnityEngine;
using UnityEngine.InputSystem;

public class CarController : MonoBehaviour
{
    float speed = 0;
    Vector2 startPos;   // マウスを押した位置

    void Start()
    {
        Application.targetFrameRate = 60;
    }

    void Update()
    {
        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            // 押した瞬間の位置を覚えておく
            startPos = Mouse.current.position.value;
        }
        else if (Mouse.current.leftButton.wasReleasedThisFrame)
        {
            // 離した瞬間の位置との差が、スワイプの長さ
            Vector2 endPos = Mouse.current.position.value;
            float swipeLength = endPos.x - startPos.x;

            // スワイプの長さを速さに変換する
            speed = swipeLength / 500.0f;
        }

        transform.Translate(speed, 0, 0);
        speed *= 0.98f;
    }
}
書き方意味
wasPressedThisFrameボタンを押した瞬間だけ true
wasReleasedThisFrameボタンを離した瞬間だけ true
Mouse.current.position.value今のマウスの位置(画面のピクセル座標)

endPos.x - startPos.x は横方向の差なので、右へスワイプすればプラス、左へスワイプすればマイナスになります。マイナスの速さになると、車は左へバックします。

ステップ3:効果音を鳴らす(完成)

最後に、走り出すときに効果音を鳴らします。スクリプトを書く前に、車にAudioSourceコンポーネントを追加しておきます。

  1. Hierarchyで車を選び、Inspectorの Add Component から AudioSource を追加する
  2. AudioSourceの Audio Resource 欄に car_se.mp3 をドラッグ&ドロップする
  3. Play On Awake のチェックを外す(外さないと、ゲーム開始と同時に音が鳴ってしまう)
using UnityEngine;
using UnityEngine.InputSystem;   // Mouse を使うために必要

public class CarController : MonoBehaviour
{
    float speed = 0;     // 1フレームあたりに進む距離
    Vector2 startPos;    // マウスを押した位置

    void Start()
    {
        // Update が1秒間に60回を目安に呼ばれるようにする
        Application.targetFrameRate = 60;
    }

    void Update()
    {
        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            // 押した瞬間の位置を覚えておく
            startPos = Mouse.current.position.value;
        }
        else if (Mouse.current.leftButton.wasReleasedThisFrame)
        {
            // 離した瞬間の位置との差が、スワイプの長さ
            Vector2 endPos = Mouse.current.position.value;
            float swipeLength = endPos.x - startPos.x;

            // スワイプの長さを速さに変換する
            speed = swipeLength / 500.0f;

            // 同じオブジェクトに付いている AudioSource で効果音を鳴らす
            GetComponent<AudioSource>().Play();
        }

        // X方向へ進めて、少しずつ減速する
        transform.Translate(speed, 0, 0);
        speed *= 0.98f;
    }
}

GetComponent<AudioSource>() は「このスクリプトと同じオブジェクトに付いているAudioSourceを取ってくる」という意味です。車にAudioSourceを付けたのはこのためです。

3. スワイプの長さと止まる位置の関係

では、どれくらいスワイプすれば旗の前で止まるのでしょうか。ルーレットの記事と同じように計算してみます。

速さに0.98を掛け続けたとき、止まるまでに進む距離の合計は「最初の速さ ÷ 0.02」、つまり最初の速さの50倍になります。最初の速さは「スワイプの長さ ÷ 500」なので、まとめると次のようになります。

進む距離 = (スワイプの長さ ÷ 500) × 50 = スワイプの長さ ÷ 10
スワイプの長さ(ピクセル)最初の速さ止まるまでに進む距離の目安
1000.2約10
1450.29約14.5(ちょうど旗の位置)
3000.6約30(画面の外まで行ってしまう)

車は x = -7、旗は x = 7.5 にあるので、その差は14.5です。つまり約145ピクセルのスワイプで、ちょうど旗のあたりに止まる計算です。

ただし、マウスの位置は画面のピクセルで測っているので、Gameビューの大きさ(解像度)が変わると、同じ手の動きでもピクセル数が変わります。モニターによって難しさが変わるのはこのためです。

スワイプの長さで止まる位置が決まる:100pxで約10、145pxで旗の位置、300pxで画面の外まで進む

4. 残り距離を表示する(GameDirector)

次は、画面に「旗まであと何メートルか」を表示します。文字の表示にはTextMeshProを使います。

テキストを配置する

  1. Hierarchyで右クリックし、UI → Text – TextMeshPro を選ぶ
  2. 初めて使うときは「TMP Importer」ウィンドウが出るので、Import TMP Essentials を押す
  3. 作られたテキストの名前を distance に変える
  4. Inspectorで文字の大きさ(Font Size)や幅を見やすく調整する(サンプルはFont Size 64、幅900 × 高さ80)

テキストを作ると、親として Canvas、一緒に EventSystem も自動で作られます。これらは消さないでください。

監督役のスクリプトを作る

距離の計算と表示は、車でも旗でもない「監督役」のオブジェクトに任せます。Hierarchyで Create Empty を選んで空のオブジェクトを作り、名前を GameDirector にします。そこに次のスクリプトを付けます。

using UnityEngine;
using TMPro;   // TextMeshPro を使うために必要

public class GameDirector : MonoBehaviour
{
    GameObject car;
    GameObject flag;
    GameObject distance;

    void Start()
    {
        // シーンの中から、名前でオブジェクトを探す
        car = GameObject.Find("car_0");
        flag = GameObject.Find("flag_0");
        distance = GameObject.Find("distance");
    }

    void Update()
    {
        // 旗と車のX座標の差が、残りの距離
        float length = flag.transform.position.x - car.transform.position.x;

        // 小数点以下2桁にそろえて表示する
        distance.GetComponent<TextMeshProUGUI>().text = "Distance:" + length.ToString("F2") + "m";
    }
}

GameObject.Find("名前") は、シーンの中から指定した名前のオブジェクトを探してくれる命令です。Start で一度だけ探しておき、Update で毎フレーム距離を計算して表示します。

GameObject.Find は、Unity 6でも使える書き方です。ただし、次のような注意点があります。

  • 名前を1文字でも間違えると見つからず、null(空っぽ)になる
  • Hierarchyで名前を変えると、スクリプトも直さないと動かなくなる
  • 非表示(非アクティブ)のオブジェクトは見つけられない
  • シーン全体を探すので、Update の中で毎フレーム呼ぶのは避ける

ToString(“F2") で小数点以下をそろえる

length は float 型なので、そのまま文字列にすると 3.456789 のように桁がバラバラになり、数字がチラチラして読みにくくなります。そこで ToString("F2") を使います。

書き方3.456789 の表示意味
length.ToString()3.456789そのまま(桁数がそろわない)
length.ToString("F0")3小数点以下0桁(四捨五入)
length.ToString("F1")3.5小数点以下1桁
length.ToString("F2")3.46小数点以下2桁(今回の書き方)

F は「Fixed-point(固定小数点)」の頭文字で、後ろの数字が小数点以下の桁数です。車が旗を通り過ぎると、距離はマイナス(例:-1.25m)になります。

5. もう一つの書き方:[SerializeField] でInspectorから設定する

GameObject.Find は手軽ですが、名前の間違いに弱いのが弱点です。そこで、Unityでよく使われるもう一つの書き方を紹介します。Inspectorで直接オブジェクトをドラッグ&ドロップして設定する方法です。

using UnityEngine;
using TMPro;

public class GameDirector : MonoBehaviour
{
    // [SerializeField] を付けると、private のままInspectorに表示される
    [SerializeField] GameObject car;
    [SerializeField] GameObject flag;
    [SerializeField] TextMeshProUGUI distance;

    void Update()
    {
        float length = flag.transform.position.x - car.transform.position.x;
        distance.text = "Distance:" + length.ToString("F2") + "m";
    }
}

スクリプトを保存したら、GameDirectorを選んでInspectorを見てください。Car、Flag、Distance の3つの欄が表示されるので、Hierarchyから車・旗・テキストをそれぞれドラッグ&ドロップします。

GameObject.Find[SerializeField]
設定のしかたスクリプトに名前を書くInspectorでドラッグ&ドロップ
名前を変えたときスクリプトも直す必要があるそのまま動く
設定忘れ名前の間違いに気づきにくいInspectorの欄が空(None)なので気づきやすい
テキストの取り出しGetComponent<TextMeshProUGUI>() が必要型を TextMeshProUGUI にすれば直接使える
GameObject.Findは名前で探し、[SerializeField]はInspectorでドラッグ&ドロップして設定する

教科書どおり GameObject.Find で作ったあとに、[SerializeField] 版に書き換えてみると、両方の良さがよく分かります。[SerializeField] については次の記事でも詳しく解説しています。

Unity 6でプロパティはインスペクタに表示される?

6. よくあるつまずき

距離が表示されず、NullReferenceException が出る

NullReferenceException: Object reference not set to an instance of an object

GameObject.Find で指定した名前と、Hierarchyのオブジェクト名が一致していない可能性が高いです。car_0 なのか car なのか、大文字・小文字も含めて確認しましょう。[SerializeField] 版の場合は、Inspectorの欄に設定し忘れていないか確認します。

NullReferenceExceptionを感覚で直さない

効果音のところでエラーが出る

MissingComponentException: There is no 'AudioSource' attached to the "car_0" game object, but a script is trying to access it.

車にAudioSourceが付いていません。車を選んで Add Component → AudioSource を追加してください。GameDirectorなど、別のオブジェクトに付けてしまっていないかも確認しましょう。

TextMeshProUGUI が見つからないというエラーが出る

error CS0246: The type or namespace name 'TextMeshProUGUI' could not be found (are you missing a using directive or an assembly reference?)

ファイルの先頭に using TMPro; を書き忘れています。

その他のチェックポイント

  • ゲームを始めた瞬間に音が鳴る → AudioSourceの Play On Awake のチェックを外す
  • スワイプしても車が動かない → CarController が車に付いているか、Gameビューの中でスワイプしているか確認する
  • テキストに日本語を入れると□(豆腐)になる → TextMeshProの標準フォントには日本語が入っていないため。日本語フォントの設定が必要

TextMeshProのテキストで全ての日本語を表示したい場合

7. 次のステップ

SwipeCarがひととおり動いたら、次のような改造に挑戦してみましょう。

  • / 500.0f や 0.98f の数値を変えて、難しさを調整する
  • 旗の前で止まったら「GOOD!」、通り過ぎたら「GAME OVER」と表示する
  • 止まった位置に応じて点数を出す

止まったときの得点を出す方法や、シーンのオブジェクトとコンポーネントの関係は、次の記事で解説しています。

SwipeCar:止まったときの得点を出そう

SwipeCar:シーンのオブジェクトとコンポーネント関連

まとめ

SwipeCarの作り方の全体像:スクリプト作成・スプライト配置・CarController・TextMeshPro・スワイプの仕組み・2つのスクリプトのコード・実行確認
  • transform.Translate(speed, 0, 0) と speed *= 0.98f で、「走ってだんだん止まる」動きが作れる(ルーレットと同じ考え方)
  • wasPressedThisFrame と wasReleasedThisFrame で押した位置と離した位置を取り、その差でスワイプの長さが分かる
  • 効果音は、AudioSourceを付けて GetComponent<AudioSource>().Play() で鳴らす
  • GameObject.Find は名前でオブジェクトを探す。手軽だが名前の間違いに弱い
  • [SerializeField] を使うと、Inspectorから直接オブジェクトを設定できる
  • ToString("F2") で小数点以下の桁数をそろえると、数字が読みやすくなる

ルーレットで学んだ「毎フレーム少しずつ値を変える」考え方が、そのまま車の移動にも使えることが分かったと思います。同じ仕組みを別のゲームで使い回せるようになると、ゲーム作りは一気に楽しくなります。

訪問数 6 回, 今日の訪問数 6回

広告