学習記事一覧 · Unity教科書_Unity6対応

Unity入門:スワイプカーゲームを作りながら学ぼう!

対象読者:Unity学び始めの方・C#をこれから覚えたい方 このブログでは、実際のスワイプカーゲームのコードを読み解きながら、Unityの基本を学びます。


記事の目次

この記事はやや長めです。目的に合わせてジャンプして読み分けてください。

おすすめの読み方

セクション一覧

ポイント一覧


このゲームで何ができるの?

今回題材にするのは、マウスをスワイプするとその勢いで車が動き、フラグに近づけることを目指すゲームです。

  • 🖱️ マウスを左右にドラッグ(スワイプ)すると車が動く
  • 🚗 スワイプの長さ・方向によってスピードが変わる
  • 🏁 フラグとの距離がリアルタイムで画面に表示される

マウスをスワイプして車を動かし、フラグへ近づける様子(イメージ)

たった 2つのスクリプト(合計約40行) でこれが実現できます。シンプルながら、Unityの重要な概念がしっかり詰まっています。一緒に読み解いていきましょう!


プロジェクトの構成

このプロジェクトのファイル構成はこうなっています。

Assets/
  ├── car.png              # 車の画像
  ├── flag.png             # フラグの画像
  ├── ground.png           # 地面の画像
  ├── car_se.mp3           # 走行効果音
  ├── CarController.cs     # 車の動きを制御するスクリプト
  ├── GameDirector.cs      # ゲーム全体を管理するスクリプト
  └── GameScene.unity      # ゲームのシーン

Unityでは、画像・音声・スクリプトなどすべてのファイルを Assets フォルダに入れて管理します。今回は2つのスクリプトが役割分担をしているのがポイントです。


コードを全部見てみよう

CarController.cs(車の操作)

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)
        {
            // マウスをクリックした座標
            this.startPos = Mouse.current.position.value;
        }
        else if (Mouse.current.leftButton.wasReleasedThisFrame)
        {
            // マウスを離した座標
            Vector2 endPos = Mouse.current.position.value;
            float swipeLength = endPos.x - this.startPos.x;

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

            // 効果音再生
            GetComponent<AudioSource>().Play();
        }

        transform.Translate(this.speed, 0, 0);  // 移動
        this.speed *= 0.98f;                    // 減速
    }
}

GameDirector.cs(距離の表示)

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

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

    void Start()
    {
        this.car = GameObject.Find("car_0");
        this.flag = GameObject.Find("flag_0");
        this.distance = GameObject.Find("distance");
    }

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

この2つのスクリプトには、合計6つの学びポイントがあります。順番に見ていきましょう!


ポイント①:2種類のマウス入力を使い分ける

if (Mouse.current.leftButton.wasPressedThisFrame)
{
    this.startPos = Mouse.current.position.value;
}
else if (Mouse.current.leftButton.wasReleasedThisFrame)
{
    // ...
}

スワイプを検知するには「押した瞬間」と「離した瞬間」の2つのタイミングが必要です。

プロパティ いつ true になる?
wasPressedThisFrame 押した瞬間のフレームだけ
isPressed 押している間ずっと
wasReleasedThisFrame 離した瞬間のフレームだけ

「押した時点の座標」と「離した時点の座標」の差がスワイプの長さになります。ルーレットゲームでは「押した瞬間だけ」でしたが、今回はペアで使うのがポイントです。

💡 if / else if で使い分けることで、同じフレームに両方の処理が走らないようにしています。


ポイント②:Vector2 で座標を記憶する

Vector2 startPos;
// ...
this.startPos = Mouse.current.position.value;

Vector2 はX・Yの2つの値をまとめて持てる型です。画面上の座標(位置)を扱うときによく使います。

何を表す?
float 1つの小数値 速度、距離など
Vector2 X・Y の2つの値 2D座標、スワイプの位置など
Vector3 X・Y・Z の3つの値 3D空間の座標など

startPos をクラスのフィールド(Update() の外)に置くことで、「押した瞬間」から「離した瞬間」までの間、座標を記憶し続けることができます。これがフレームをまたいだデータ保持の基本パターンです。


ポイント③:スワイプの長さを速度に変換する

Vector2 endPos = Mouse.current.position.value;
float swipeLength = endPos.x - this.startPos.x;

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

スワイプの長さはピクセル単位の大きな値(例:300px)になるため、ゲーム内の速度としてはそのまま使えません。500.0f で割ることで、ちょうどよいスケールの値(例:0.6)に変換しています。

スワイプ長さ 300px ÷ 500.0f = 速度 0.6(ゲーム内単位)
スワイプ長さ -200px ÷ 500.0f = 速度 -0.4(左方向に動く!)

マイナスの値になると左方向に動くので、右スワイプで右へ・左スワイプで左へ、という直感的な操作が自然に実現できています。

💡 500.0f の値を変えると感度が変わります。小さくすると速くなり、大きくすると遅くなります。


ポイント④:transform.Translate() で移動する

transform.Translate(this.speed, 0, 0);  // 移動
this.speed *= 0.98f;                    // 減速

transform.Translate(x, y, z) はGameObjectを指定した量だけ平行移動させます。ルーレットの Rotate() と対になる操作です。

メソッド 何をする?
transform.Translate(x, y, z) 指定量だけ平行移動
transform.Rotate(x, y, z) 指定量だけ回転

毎フレーム speed ぶん移動しつつ、speed *= 0.98f でだんだん遅くなります。ルーレットの 0.96f に比べて 0.98f と1.0fに近いため、より長い距離を走り続ける感覚になります。

1フレーム目: 0.6
2フレーム目: 0.6 × 0.98 = 0.588
3フレーム目: 0.588 × 0.98 = 0.576
…(だんだん止まる)

ポイント⑤:GameObject.Find() でオブジェクトを取得する

void Start()
{
    this.car = GameObject.Find("car_0");
    this.flag = GameObject.Find("flag_0");
    this.distance = GameObject.Find("distance");
}

GameObject.Find("名前") はシーン内のGameObjectを名前で検索して取得するメソッドです。GameDirector は車・フラグ・テキストの3つのオブジェクトを参照する必要があるので、Start() で一度だけ取得しておきます。

💡 Find() は毎フレーム呼ぶと処理が重くなります。Start() で一度だけ取得して変数に保存しておくのがベストプラクティスです。


ポイント⑥:リアルタイムで距離を表示する

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

距離の計算

transform.position.x でGameObjectのX座標を取得できます。フラグのX座標から車のX座標を引いた値が「残り距離」です。車がフラグに近づくほどこの値は小さくなります。

テキストの更新

GetComponent<TextMeshProUGUI>() でテキストコンポーネントを取得し、.text プロパティに文字列を代入することで表示を更新します。

length.ToString("F2")"F2" は「小数点以下2桁で表示」という書式指定です。

書式 表示例(12.3456の場合)
"F0" 12
"F1" 12.3
"F2" 12.35
"F3" 12.346

2つのスクリプトの役割分担

このゲームには2つのスクリプトが登場します。それぞれの役割を整理しましょう。

【CarController】           【GameDirector】
車にアタッチ               空のGameObjectにアタッチ
 ↓                          ↓
スワイプ入力を検知          車・フラグを監視
速度を計算・更新            距離を計算・テキスト更新
車を動かす・減速する
効果音を再生する

1つのスクリプトに全部書くこともできますが、「車の操作」と「ゲーム管理」を分けることで、それぞれのコードがシンプルになり、後から修正しやすくなります。これがゲーム開発でよく使われる「役割分担」の考え方です。


コードの流れを整理しよう

【CarController の毎フレーム処理フロー】

① マウスが押された?
   └─ YES → startPos(開始座標)を記録
② マウスが離された?
   └─ YES → endPos - startPos でスワイプ長さを計算
            → speed に変換して効果音再生
③ transform.Translate(speed, 0, 0) で車を動かす
④ speed *= 0.98f で少し遅くする

【GameDirector の毎フレーム処理フロー】

① フラグのX座標 - 車のX座標 = 距離を計算
② テキストに「Distance: XX.XXm」と表示

自分でカスタマイズしてみよう!

挑戦①:感度を変える

// 小さくすると速くなる、大きくすると遅くなる
this.speed = swipeLength / 500.0f;

挑戦②:止まるまでの距離を変える

// 1.0f に近いほど長く走る、0.9f に近いほどすぐ止まる
this.speed *= 0.98f;

挑戦③:キーボードでも動かせるようにする

// Update() 内に追加してみよう
if (Keyboard.current.rightArrowKey.isPressed)
{
    this.speed = 0.1f;
}

挑戦④:車がフラグを超えたら「ゴール!」を表示する

// GameDirector の Update() 内に追加
if (length < 0)
{
    this.distance.GetComponent<TextMeshProUGUI>().text = "GOAL!";
}

まとめ

今回のスワイプカーゲームから学んだことを整理しましょう。

  • wasPressedThisFramewasReleasedThisFrame をペアで使ってスワイプを検知する
  • Vector2 で2D座標を保持し、フレームをまたいで値を記憶できる
  • スワイプ長さ ÷ 定数で、ピクセル値をゲーム内速度に変換する
  • transform.Translate() で平行移動、*= 0.98f で自然な減速を実現
  • GameObject.Find()Start() で一度だけ呼んで変数に保存する
  • GetComponent<TextMeshProUGUI>().text でリアルタイムにテキストを更新できる
  • スクリプトを役割ごとに分けるとコードが見通しよくなる

2つのスクリプトが連携することで、「操作」と「表示」をきれいに分離できました。「動く→コードを読む→カスタマイズする」 のサイクルを繰り返すことが、Unity上達の近道です。

次は、車がフラグに到着したら「ゴール演出」を追加したり、試行回数をカウントするスコア機能を実装してみるのはどうでしょうか?


最終更新:2026年4月