Unityを触り始めた頃は、つい「とりあえず全部Updateに書いてしまう」ことが多いですよね。
プレイヤーの移動、入力処理、UI更新、ゲームオーバー判定、そしてチェックポイント処理まで…全部1つのスクリプトに詰め込んでしまうと、数百行を超えたあたりで「どこを触ればいいのか分からない巨大スクリプト」になってしまいます。
特に「復活地点(Checkpoint)」のような仕組みをプレイヤーのスクリプトに直書きすると、
- プレイヤー以外(敵、動く床など)では再利用できない
- シーンごとに挙動を変えたいときに、プレイヤースクリプトを毎回編集する羽目になる
- レベルデザイナーがプレハブをポン置きしても動かない
といった問題が出てきます。
そこでこの記事では、「Checkpoint(復活地点)」専用の小さなコンポーネントを用意して、
プレイヤーとは独立した形で復活地点を管理できるようにしていきます。
【Unity】シンプルに復活地点を管理!「Checkpoint」コンポーネント
今回作るのは、
- プレイヤーが触れたら
- その地点の座標を「グローバルな復活地点」として保存する
という役割だけに絞った Checkpoint コンポーネントです。
Godクラス化を防ぐため、「復活地点の保存」だけを担当させます。
あわせて、復活地点を管理するための超シンプルな RespawnManager も用意します。
(「GameManager等のグローバル変数」の具体例として使います。)
Checkpoint & RespawnManager フルコード
この記事のコードは、このままコピペして使えるようにしてあります。
最低限必要なのはこの2つのスクリプトです。
RespawnManager.cs(復活地点を管理するグローバルクラス)
using UnityEngine;
/// <summary>
/// シーン内で1つだけ存在することを想定した
/// 「復活地点」を管理するシンプルなマネージャー。
///
/// - 最後に有効化されたチェックポイントの座標を保存
/// - プレイヤーを復活地点にワープさせる
///
/// ※本来はGameManagerの一部にしてもOKですが、
/// 責務を分けるためにあえて分離しています。
/// </summary>
public class RespawnManager : MonoBehaviour
{
// シングルトン的にアクセスするための静的インスタンス
public static RespawnManager Instance { get; private set; }
// 現在の復活地点のワールド座標
public Vector3 CurrentRespawnPosition { get; private set; }
// デフォルトの復活地点(シーン開始時の初期値)
[SerializeField] private Transform defaultRespawnPoint;
private void Awake()
{
// シーン内に複数存在した場合は警告
if (Instance != null && Instance != this)
{
Debug.LogWarning("RespawnManager が複数存在しています。余分なオブジェクトを削除してください。");
Destroy(gameObject);
return;
}
Instance = this;
// デフォルトの復活地点を初期設定
if (defaultRespawnPoint != null)
{
CurrentRespawnPosition = defaultRespawnPoint.position;
}
else
{
// 指定がなければ、このオブジェクトの位置をデフォルトにする
CurrentRespawnPosition = transform.position;
}
}
/// <summary>
/// 復活地点を更新する(Checkpoint から呼ばれる想定)。
/// </summary>
public void SetRespawnPoint(Vector3 worldPosition)
{
CurrentRespawnPosition = worldPosition;
Debug.Log($"RespawnManager: 復活地点を {worldPosition} に更新しました。");
}
/// <summary>
/// 指定されたTransform(主にプレイヤー)を復活地点へワープさせる。
/// Rigidbodyが付いている場合は速度もリセットする。
/// </summary>
public void Respawn(Transform target)
{
if (target == null)
{
Debug.LogWarning("RespawnManager.Respawn: target が null です。");
return;
}
target.position = CurrentRespawnPosition;
// Rigidbody があれば速度もリセット
var rb = target.GetComponent<Rigidbody>();
if (rb != null)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
Debug.Log($"RespawnManager: {target.name} を {CurrentRespawnPosition} に復活させました。");
}
}
Checkpoint.cs(プレイヤーが触れたら復活地点を更新するコンポーネント)
using UnityEngine;
/// <summary>
/// プレイヤーが触れたら、その地点を復活地点として RespawnManager に登録するコンポーネント。
///
/// - Trigger付きColliderが必要
/// - プレイヤーをタグで判定
/// - 任意で「一度だけ有効」「SE再生」「見た目の変化」などを制御可能
/// </summary>
[RequireComponent(typeof(Collider))]
public class Checkpoint : MonoBehaviour
{
[Header("プレイヤー判定")]
[SerializeField] private string playerTag = "Player";
[Header("1度だけ有効にするか?")]
[SerializeField] private bool activateOnlyOnce = true;
[Header("視覚的なフィードバック(任意)")]
[SerializeField] private Renderer indicatorRenderer; // 色を変える対象
[SerializeField] private Color inactiveColor = Color.gray;
[SerializeField] private Color activeColor = Color.green;
[Header("サウンド(任意)")]
[SerializeField] private AudioSource audioSource;
[SerializeField] private AudioClip activateClip;
// すでに有効化済みかどうか
private bool _isActivated;
private void Reset()
{
// Collider を Trigger にしておく(エディタ上で自動設定される)
var col = GetComponent<Collider>();
col.isTrigger = true;
}
private void Start()
{
// 見た目の初期色を設定
if (indicatorRenderer != null)
{
indicatorRenderer.material.color = inactiveColor;
}
}
private void OnTriggerEnter(Collider other)
{
// すでに有効化済みで、かつ一度きり設定なら何もしない
if (_isActivated && activateOnlyOnce) return;
// タグでプレイヤーかどうか判定
if (!other.CompareTag(playerTag)) return;
// RespawnManager が存在するかチェック
if (RespawnManager.Instance == null)
{
Debug.LogWarning("Checkpoint: RespawnManager がシーン内に存在しません。復活地点は保存されません。");
return;
}
// この Checkpoint の位置を復活地点として登録
RespawnManager.Instance.SetRespawnPoint(transform.position);
// 視覚的なフィードバック
if (indicatorRenderer != null)
{
indicatorRenderer.material.color = activeColor;
}
// サウンド再生
if (audioSource != null && activateClip != null)
{
audioSource.PlayOneShot(activateClip);
}
_isActivated = true;
}
/// <summary>
/// エディタや他スクリプトから強制的にアクティブ状態を変更したい場合用。
/// </summary>
public void SetActivated(bool activated)
{
_isActivated = activated;
if (indicatorRenderer != null)
{
indicatorRenderer.material.color = activated ? activeColor : inactiveColor;
}
}
}
使い方の手順
ここでは「プレイヤーが落下死したら最後のチェックポイントに復活する」3Dアクションゲームを例にします。
手順①:RespawnManager をシーンに配置する
- 空のGameObjectを作成し、名前を
RespawnManagerにします。 - 上記の
RespawnManager.csをアタッチします。 - 任意で「デフォルト復活地点」を指定したい場合:
- プレイヤーの初期位置に Empty を置き、名前を
DefaultRespawnPointなどにする - その Transform を
defaultRespawnPointにドラッグ&ドロップ
- プレイヤーの初期位置に Empty を置き、名前を
これで RespawnManager.Instance から、どこからでも復活地点にアクセスできるようになります。
手順②:Checkpoint プレハブを作る
- Cube などの3Dオブジェクトを作成し、名前を
Checkpointにします。 Checkpoint.csをアタッチします。- アタッチしたオブジェクトの
Colliderを Is Trigger にチェック(Reset()で自動設定されますが、念のため確認)。 - 見た目を変えたい場合:
- Checkpoint オブジェクトに
MeshRendererが付いていることを確認 indicatorRendererにその Renderer をドラッグinactiveColor/activeColorを好きな色に設定
- Checkpoint オブジェクトに
- 効果音を鳴らしたい場合:
- 同じオブジェクトに
AudioSourceを追加 audioSourceにその AudioSource をドラッグactivateClipにSE(AudioClip)を設定
- 同じオブジェクトに
- このオブジェクトを Prefab にしておくと、シーン内に量産しやすくなります。
手順③:プレイヤーのタグと死亡処理を用意する
- プレイヤーの GameObject に
Playerタグを設定します。- 別の名前にしたい場合は、
CheckpointのplayerTagを同じ名前に変更してください。
- 別の名前にしたい場合は、
- プレイヤーが落下死したときなどに
RespawnManager.Instance.Respawn(transform)を呼び出します。
例:プレイヤーが一定高度より下に落ちたら復活する簡単なコンポーネント:
using UnityEngine;
/// <summary>
/// 一定の高さより下に落ちたら、RespawnManager によって復活させるサンプル。
/// プレイヤーにアタッチして使います。
/// </summary>
public class SimpleFallDeath : MonoBehaviour
{
[SerializeField] private float fallThresholdY = -10f;
private void Update()
{
if (transform.position.y < fallThresholdY)
{
if (RespawnManager.Instance != null)
{
RespawnManager.Instance.Respawn(transform);
}
else
{
Debug.LogWarning("SimpleFallDeath: RespawnManager が見つかりません。");
}
}
}
}
手順④:Checkpoint をシーンに配置してテスト
- 作成した Checkpoint プレハブをシーン上に複数配置します(ステージの各所)。
- プレイして、プレイヤーが Checkpoint に触れたとき:
- コンソールに「復活地点を更新しました」とログが出る
- 見た目の色が変わる / SEが鳴る(設定していれば)
- プレイヤーを落下させて死亡判定を通すと、最後に通過した Checkpoint に復活することを確認します。
メリットと応用
Checkpoint コンポーネントを分離しておくことで、以下のメリットがあります。
- プレイヤーのスクリプトがスリムになる
復活地点の保存ロジックをプレイヤーから完全に切り離せます。
プレイヤー側は「死んだら RespawnManager にお願いする」だけでOKです。 - レベルデザインが楽になる
レベルデザイナーは「Checkpoint プレハブを置くだけ」で復活地点を追加できます。
スクリプトを触らなくても、シーン上の配置だけで調整できるのが大きな利点です。 - Prefab 管理がシンプル
Checkpoint の見た目・SE・一度だけ有効かどうかなどは、
すべてプレハブのインスペクターで完結します。
シーンごとに振る舞いを変えたい場合でも、プレハブのバリアントを作るだけで済みます。 - 再利用性が高い
2Dでも3Dでも使えますし、プレイヤー以外のキャラクターでも
タグさえ合わせれば同じ仕組みを流用できます。
改造案:チェックポイントに「ID」を付けて特定の地点へワープする
例えばデバッグ用に「特定のチェックポイントへジャンプしたい」とき、
Checkpoint に ID を付けておくと便利です。
以下は RespawnManager に追加できる、ID指定でワープする簡単な改造案です。
using System.Collections.Generic;
using UnityEngine;
public class RespawnManagerWithId : MonoBehaviour
{
// CheckpointID と位置の対応表
private Dictionary<string, Vector3> _respawnPointsById = new Dictionary<string, Vector3>();
public void RegisterRespawnPoint(string id, Vector3 position)
{
_respawnPointsById[id] = position;
}
public void RespawnById(Transform target, string id)
{
if (!_respawnPointsById.TryGetValue(id, out var pos))
{
Debug.LogWarning($"RespawnManagerWithId: ID '{id}' の復活地点が登録されていません。");
return;
}
target.position = pos;
var rb = target.GetComponent<Rigidbody>();
if (rb != null)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
}
}
これに合わせて Checkpoint 側に string checkpointId を追加し、
有効化時に RegisterRespawnPoint を呼ぶようにすると、
「ステージ開始時に全チェックポイントを登録しておき、メニューからID指定で飛ぶ」などの応用ができます。
このように、Checkpoint(復活地点)という1つの責務に絞ったコンポーネントを用意しておくと、
ゲーム全体の設計がすっきりして、後からの改造もしやすくなります。
巨大な Update 関数に書き足していくのではなく、小さなコンポーネントを積み重ねていく形で設計していきましょう。
