Unity 6でCatEscapeを作る① ― 猫の移動と、Prefabで矢を降らせる仕組み(PC版)
落ちてくる矢を、猫が左右に動いてよける――今回からは「CatEscape」というゲームを作ります。
これまでのルーレットやSwipeCarは、画面の中のオブジェクトを1つ動かすゲームでした。CatEscapeでは、ゲームの途中で矢がどんどん生まれて、落ちて、消えていきます。この「たくさん作って、いらなくなったら消す」仕組みが、今回のいちばんの学びどころです。
CatEscapeは内容が多いので、2回に分けて解説します。
| 回 | 内容 |
|---|---|
| 今回(①) | 猫を矢印キーで動かす/Prefabで矢を作る/矢を一定間隔で降らせる/画面の外に出た矢を消す |
| 次回(②) | 矢と猫の当たり判定/HPゲージを減らす/スクリプト同士で連絡を取り合う |
この記事で作るもの
- 画面の下に猫、背景に空の画像を置く
- 左右の矢印キーを押すと、猫が左右に動く
- 画面の上から、1秒ごとに矢が1本ずつ降ってくる
- 矢が出てくる横の位置は毎回ランダム
- 画面の下まで落ちた矢は、自動で消える
| 項目 | 内容 |
|---|---|
| Unity | Unity 6(サンプルは6000.1で作成) |
| テンプレート | Universal 2D(URP) |
| 入力 | Input System(UnityEngine.InputSystem) |
| 操作 | PC版:キーボードの左右の矢印キー |
今回作るスクリプトは3つです。
| スクリプト | 付ける場所 | 役割 |
|---|---|---|
PlayerController | 猫(player) | 矢印キーで猫を左右に動かす |
ArrowController | 矢(arrowPrefab) | 矢を落とし、画面の外に出たら消す |
ArrowGenerator | 空のオブジェクト(ArrowGenerator) | 1秒ごとに矢を作って、ランダムな位置に置く |
1. シーンを準備する
使う画像は、猫(player.png)、矢(arrow.png)、背景(background.png)の3枚です。Projectウィンドウに取り込み、猫と背景をSceneビューに配置します。矢はあとでPrefabにするので、まだ置かなくて大丈夫です。
| オブジェクト | Position | Scale | Order in Layer |
|---|---|---|---|
| 背景(background) | (0, 0, 0) | (4.5, 2, 1) | 0 |
| 猫(player) | (0, -3.6, 0) | (1, 1, 1) | 1 |
ポイントはOrder in Layerです。2Dの画像は、この数字が大きいものほど手前に表示されます。背景を0、猫を1にしておくと、猫が背景の後ろに隠れてしまうことを防げます。Order in Layerは、InspectorのSprite Rendererの中にあります。
画面の各ウィンドウ(Scene・Hierarchy・Inspectorなど)の役割があいまいな場合は、先に次の記事を読んでおくと迷いにくくなります。
Unity 6の画面構成を理解する ― Game・Scene・Hierarchy・Inspector・Project
2. 猫を矢印キーで動かす(PlayerController)
Projectウィンドウで Create → Scripting → MonoBehaviour Script を選んで PlayerController を作り、猫にドラッグ&ドロップして付けます。
using UnityEngine;
using UnityEngine.InputSystem; // Keyboard を使うために必要
public class PlayerController : MonoBehaviour
{
void Start()
{
// Update が1秒間に60回を目安に呼ばれるようにする
Application.targetFrameRate = 60;
}
void Update()
{
// 左矢印キーを押した瞬間
if (Keyboard.current.leftArrowKey.wasPressedThisFrame)
{
transform.Translate(-3, 0, 0); // 左に3動かす
}
// 右矢印キーを押した瞬間
if (Keyboard.current.rightArrowKey.wasPressedThisFrame)
{
transform.Translate(3, 0, 0); // 右に3動かす
}
}
}
ルーレットやSwipeCarでは Mouse.current でマウスを調べましたが、今回は Keyboard.current でキーボードを調べます。書き方の形はそっくりです。
| 書き方 | 調べる入力 |
|---|---|
Mouse.current.leftButton | マウスの左ボタン |
Keyboard.current.leftArrowKey | キーボードの左矢印キー |
Keyboard.current.rightArrowKey | キーボードの右矢印キー |
Keyboard.current.spaceKey | スペースキー(参考) |
wasPressedThisFrame は「押した瞬間の1フレームだけ true」なので、キーを1回押すごとに3ずつ動きます。押しっぱなしで動き続けてほしい場合は、「押している間ずっと true」になる isPressed を使います。
| 書き方 | true になるタイミング | 向いている動き |
|---|---|---|
wasPressedThisFrame | 押した瞬間だけ | 1マスずつ動く(今回) |
isPressed | 押している間ずっと | なめらかに動き続ける |
wasReleasedThisFrame | 離した瞬間だけ | SwipeCarのスワイプ終了 |
キー入力でなめらかに動かす方法や、ネットでよく見る Input.GetKeyDown との違いは、次の記事で解説しています。
Unity 6でキー入力による移動を実装する ― Input Systemの基本
Unityの入力方法が教科書とネットで違う?― Input ManagerとInput Systemの違い
3. 矢を落とす(ArrowController)
次は矢です。まず、矢の画像を1本だけシーンにドラッグして配置し、Order in Layerを1にします。そして、次のスクリプト ArrowController を作って矢に付けます。
using UnityEngine;
public class ArrowController : MonoBehaviour
{
void Update()
{
// 毎フレーム、下に0.1ずつ動かす
transform.Translate(0, -0.1f, 0);
// 画面の下の外に出たら、この矢を消す
if (transform.position.y < -5.0f)
{
Destroy(gameObject);
}
}
}
Translate(0, -0.1f, 0) のY方向がマイナスなので、矢は下へ落ちていきます。SwipeCarと違って減速はしないので、ずっと同じ速さで落ち続けます。
Destroy で「いらなくなった矢」を消す
カメラの表示範囲は、上下が y = 5 〜 -5 です。transform.position.y が -5 より小さくなった矢は、もう画面に映っていません。そのまま放っておくと、見えない矢がどんどんたまって、パソコンの処理が重くなってしまいます。
そこで Destroy(gameObject) で矢そのものを消します。gameObject は「このスクリプトが付いているオブジェクト自身」を表します。
| 書き方 | 消えるもの |
|---|---|
Destroy(gameObject) | 矢のオブジェクトごと消える(今回はこちら) |
Destroy(this) | このスクリプトだけが外れる。矢は画面に残ったまま止まる |
this と書いてしまうと、スクリプトだけが消えて、止まった矢がその場に残ります。よくある間違いなので注意しましょう。
4. 矢をPrefabにする
矢は1秒ごとに何本も作りたいので、今置いた矢をPrefab(プレハブ)にします。Prefabは、オブジェクトの「型」や「設計図」のようなものです。クッキーの型が1つあれば同じ形のクッキーを何枚でも作れるように、Prefabが1つあれば同じ矢をいくつでも作れます。
- Hierarchyの矢の名前を
arrowPrefabに変える - Hierarchyの矢を、Projectウィンドウへドラッグ&ドロップする(青い箱のアイコンのPrefabができる)
- Hierarchyに残っている矢は、もう使わないので削除する
Prefabには、画像だけでなく付けたスクリプト(ArrowController)やOrder in Layerの設定もまるごと保存されます。だから、Prefabから作った矢は、どれも最初から「落ちて、画面外で消える」動きをします。
| シーンに置いたオブジェクト | Prefab | |
|---|---|---|
| ある場所 | Hierarchy(シーンの中) | Projectウィンドウ(ファイル) |
| たとえると | 焼き上がったクッキー | クッキーの型 |
| 数 | 作った数だけある | 1つあればよい |

5. 1秒ごとに矢を降らせる(ArrowGenerator)
最後に、Prefabから矢を作る「矢の工場」を用意します。Hierarchyで Create Empty を選んで空のオブジェクトを作り、名前を ArrowGenerator にします。そこに次のスクリプトを付けます。
using UnityEngine;
public class ArrowGenerator : MonoBehaviour
{
public GameObject arrowPrefab; // Inspectorで矢のPrefabを設定する
float span = 1.0f; // 矢を作る間隔(秒)
float delta = 0; // 前回矢を作ってからの経過時間(秒)
void Update()
{
// 経過時間をためていく
delta += Time.deltaTime;
// 1秒たったら矢を1本作る
if (delta > span)
{
delta = 0;
// Prefabから矢を作る
GameObject go = Instantiate(arrowPrefab);
// 横の位置をランダムにして、画面の上に置く
int px = Random.Range(-6, 7);
go.transform.position = new Vector3(px, 7, 0);
}
}
}
スクリプトを保存したら、ArrowGeneratorを選んでInspectorを見てください。Arrow Prefab という欄ができているので、Projectウィンドウの arrowPrefab をドラッグ&ドロップして設定します。
Time.deltaTime で「1秒」を数える
Time.deltaTime は「前のフレームから何秒たったか」です。60fpsなら1フレームあたり約0.0167秒なので、これを毎フレーム delta に足していくと、ちょうどストップウォッチのように経過時間がたまっていきます。delta が1秒を超えたら矢を作り、delta を0に戻して、また数え直します。
| フレーム | delta(60fpsの場合) | 矢を作る? |
|---|---|---|
| 1 | 0.0167 | |
| 2 | 0.0333 | |
| … | … | |
| 約60 | 約1.0 | 1秒を超えたフレームで矢を作り、delta を0に戻す |

Instantiate でPrefabから矢を作る
Instantiate(arrowPrefab) は、Prefab(型)から新しいオブジェクトを1つ作ってシーンに置く命令です。作ったオブジェクトは go という変数で受け取れるので、そのあと go.transform.position で置く場所を決めています。
Random.Range の範囲に注意
Random.Range(-6, 7) は、-6 から 6 までの整数をランダムに返します。7は含まれません。整数版の Random.Range は「最後の数字は含まない」というルールがあるためです。
| 書き方 | 返ってくる値 |
|---|---|
Random.Range(-6, 7) | -6, -5, …, 5, 6 のどれか(整数。7は出ない) |
Random.Range(0, 3) | 0, 1, 2 のどれか(3は出ない) |
Random.Range(-6f, 6f) | -6.0 〜 6.0 の小数(float版は両端を含む) |
整数版で「-6から6まで」がほしいときは、最後の数字を1つ大きく書く、と覚えておきましょう。y = 7 は画面の上端(y = 5)より少し上なので、矢は画面の外から降ってくるように見えます。
6. 「フレーム」と「秒」がまざっている
ここで、ArrowControllerとArrowGeneratorを見比べてみてください。実は、2つのスクリプトは時間の数え方が違います。
| スクリプト | 書き方 | 基準 |
|---|---|---|
| ArrowController | transform.Translate(0, -0.1f, 0) | 1フレームごとに0.1落ちる |
| ArrowGenerator | delta += Time.deltaTime | 1秒ごとに1本作る |
矢は y = 7 から y = -5 まで、12進むと消えます。1フレームで0.1なので、消えるまでに120フレームかかります。60fpsなら2秒、144fpsなら約0.8秒です。一方、矢が出てくる間隔はどの環境でも1秒のままです。つまり、フレームレートが高いパソコンほど、矢の落ちる速さだけが速くなるのです。

PlayerControllerの Application.targetFrameRate = 60; は、この差が出にくいようにフレームレートをそろえる役割もしています。ルーレットの記事でも同じ話をしました。
Unity 6でルーレットを作る ― クリックで回して、だんだん止める仕組み(PC版)
矢の落下も「秒」で考えたい場合は、次のように Time.deltaTime を掛けます。「1秒に6進む」という書き方になるので、どのフレームレートでも同じ速さで落ちます(60fps × 0.1 = 1秒に6)。
// 1秒間に6ずつ落ちる(フレームレートに左右されない)
transform.Translate(0, -6f * Time.deltaTime, 0);
7. よくあるつまずき
矢が出てこず、UnassignedReferenceException が出る
UnassignedReferenceException: The variable arrowPrefab of ArrowGenerator has not been assigned.
ArrowGeneratorのInspectorで、Arrow Prefab 欄が空(None)のままです。Projectウィンドウの arrowPrefab をドラッグ&ドロップしてください。Hierarchyの矢ではなく、ProjectウィンドウのPrefabを入れるのがポイントです(Hierarchyの矢は削除済みのはずです)。
Keyboard が見つからないというエラーが出る
error CS0103: The name 'Keyboard' does not exist in the current context
PlayerControllerの先頭に using UnityEngine.InputSystem; がありません。
その他のチェックポイント
- 矢や猫が見えない → Order in Layerを確認する(背景0、猫と矢は1)
- 矢が途中で止まって画面に残る →
Destroy(this)になっていないか確認する - Hierarchyに「arrowPrefab(Clone)」がたまり続ける → Destroyの条件(
y < -5.0f)を確認する - キーを押しても猫が動かない → Gameビューをクリックして選択状態にしてから押す
8. もう一歩:猫を画面の外に出さない
今のままだと、右矢印キーを何回も押すと、猫が画面の外へ出ていってしまいます。Mathf.Clamp を使うと、値を決まった範囲におさめられます。PlayerControllerの Update の最後に、次の2行を足してみましょう。
// X座標を -6 〜 6 の範囲におさめる
float x = Mathf.Clamp(transform.position.x, -6f, 6f);
transform.position = new Vector3(x, transform.position.y, 0);
Mathf.Clamp(値, 最小, 最大) は、値が最小より小さければ最小を、最大より大きければ最大を返します。矢が出てくる範囲(-6〜6)と同じにしておくと、ゲームのバランスもちょうどよくなります。
まとめ
Keyboard.current.leftArrowKey.wasPressedThisFrameで、キーを押した瞬間を調べられるDestroy(gameObject)で、いらなくなったオブジェクトを消せる(thisと間違えない)- Prefabはオブジェクトの「型」。スクリプトや設定もまるごと保存される
Instantiate(Prefab)で、型から新しいオブジェクトを作れるTime.deltaTimeを足していくと、経過時間(秒)を数えられる- 整数版の
Random.Range(最小, 最大)は、最大の数字を含まない
次回は、矢と猫の当たり判定と、当たったときにHPゲージを減らす仕組みを作ります。CatEscapeの全体の構造は、次の記事でも解析しています。
続きの②はこちらです。
Unity 6でCatEscapeを作る② ― 当たり判定とHPゲージ、スクリプト同士の連携(PC版)











ディスカッション
コメント一覧
まだ、コメントがありません