Unityを触り始めた頃って、つい何でもかんでも Update() の中に書いてしまいがちですよね。プレイヤーの移動、カメラの追従、UIの更新、当たり判定のチェック……全部ひとつのスクリプトに詰め込んでしまうと、最初は動いていても、あとから仕様変更やバグ修正をしようとしたときに「どこを触ればいいのか分からない巨大スクリプト」が出来上がってしまいます。
特に「2Dの部屋を画面ごと切り替える」ような仕組みを作るとき、
- プレイヤーの移動スクリプトの中でカメラも動かす
- シーン全体の管理スクリプトにカメラ遷移ロジックを全部書く
といった実装をしがちですが、これも立派な God クラス化の第一歩です。
そこでこの記事では、「部屋単位でカメラをスライドさせる」機能だけに責務を絞ったコンポーネント RoomTransition を作ってみます。プレイヤーが画面端に到達したら、カメラを隣の部屋へスッとスライドさせる仕組みを、ひとつのコンポーネントとして分離しておくことで、
- プレイヤーの移動ロジックとはきれいに分離できる
- カメラの動きだけを個別に調整・再利用しやすい
- レベルデザイン時にプレハブをポンポン置くだけで部屋遷移を構築できる
といったメリットが得られます。
【Unity】2Dゼルダ風の画面切り替えをシンプル実装!「RoomTransition」コンポーネント
ここでは「部屋はグリッド状に並んでいる」という前提で、プレイヤーが部屋の端にあるトリガーに触れたら、カメラを次の部屋位置までスライドさせるコンポーネントを実装していきます。
- カメラの移動はスムーズな補間(Lerp)で行う
- プレイヤーは一時的に入力をロックして、部屋の中央にワープさせる
- プレイヤーは
Transformだけ持っていればよい(入力システムに依存しない)
という方針で作っていきます。
RoomTransition コンポーネントのフルコード
using UnityEngine;
[RequireComponent(typeof(BoxCollider2D))]
public class RoomTransition : MonoBehaviour
{
// カメラを動かす対象。通常はメインカメラを指定
[SerializeField] private Camera targetCamera;
// プレイヤーの Transform。プレイヤーを部屋中央にワープさせるために使用
[SerializeField] private Transform playerTransform;
// 次の部屋の中心座標(ワールド座標)
[SerializeField] private Vector2 nextRoomCenter = new Vector2(16f, 0f);
// プレイヤーを次の部屋のどこに配置するか(ワールド座標)
[SerializeField] private Vector2 playerSpawnPositionInNextRoom = new Vector2(16f, 0f);
// カメラ移動にかかる時間(秒)
[SerializeField] private float transitionDuration = 0.8f;
// カメラ移動中にプレイヤーの入力をロックするかどうか
[SerializeField] private bool lockPlayerDuringTransition = true;
// プレイヤーの入力をロックするためのコンポーネント(任意)
// ここでは「有効/無効で入力を止められるコンポーネント」を想定
[SerializeField] private Behaviour playerInputComponent;
// トリガーが有効かどうか(レベルデザイン上、特定のトリガーを無効化したい場合に使用)
[SerializeField] private bool isEnabled = true;
// 内部状態管理
private bool _isTransitioning = false;
private Vector3 _cameraStartPos;
private Vector3 _cameraTargetPos;
private float _transitionTimer = 0f;
private BoxCollider2D _triggerCollider;
private void Reset()
{
// コンポーネント追加時に呼ばれる。トリガー設定などの初期化を行う
_triggerCollider = GetComponent();
_triggerCollider.isTrigger = true;
// デフォルトでメインカメラを自動設定
if (Camera.main != null)
{
targetCamera = Camera.main;
}
}
private void Awake()
{
_triggerCollider = GetComponent();
// 念のためトリガーにしておく
_triggerCollider.isTrigger = true;
if (targetCamera == null && Camera.main != null)
{
targetCamera = Camera.main;
}
}
private void Update()
{
// カメラ遷移中の補間処理
if (_isTransitioning)
{
UpdateCameraTransition();
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (!isEnabled) return;
if (_isTransitioning) return;
// プレイヤーかどうかをタグで判定する実装例
// 必要に応じて Layer 判定などに変えてもOK
if (!other.CompareTag("Player")) return;
if (targetCamera == null)
{
Debug.LogWarning("[RoomTransition] targetCamera が設定されていません。");
return;
}
if (playerTransform == null)
{
// トリガーに入ってきたオブジェクトをプレイヤーとみなす簡易実装
playerTransform = other.transform;
}
StartTransition();
}
/// <summary>
/// カメラ遷移の開始処理
/// </summary>
private void StartTransition()
{
_isTransitioning = true;
_transitionTimer = 0f;
// カメラの開始位置と目標位置をセット
_cameraStartPos = targetCamera.transform.position;
_cameraTargetPos = new Vector3(
nextRoomCenter.x,
nextRoomCenter.y,
targetCamera.transform.position.z // Z はそのまま
);
// プレイヤーを次の部屋のスポーン位置に即座に移動
if (playerTransform != null)
{
playerTransform.position = new Vector3(
playerSpawnPositionInNextRoom.x,
playerSpawnPositionInNextRoom.y,
playerTransform.position.z
);
}
// プレイヤー入力をロック(任意)
if (lockPlayerDuringTransition && playerInputComponent != null)
{
playerInputComponent.enabled = false;
}
}
/// <summary>
/// カメラ遷移の補間処理
/// </summary>
private void UpdateCameraTransition()
{
if (transitionDuration <= 0f)
{
// 0秒以下の場合は即座に移動
targetCamera.transform.position = _cameraTargetPos;
EndTransition();
return;
}
_transitionTimer += Time.deltaTime;
float t = Mathf.Clamp01(_transitionTimer / transitionDuration);
// イージングをかけたい場合はここで曲線を適用しても良い
// 例: t = t * t * (3f - 2f * t); // SmoothStep 風
targetCamera.transform.position = Vector3.Lerp(_cameraStartPos, _cameraTargetPos, t);
if (t >= 1f)
{
EndTransition();
}
}
/// <summary>
/// カメラ遷移の終了処理
/// </summary>
private void EndTransition()
{
_isTransitioning = false;
// 念のため最終位置を補正
targetCamera.transform.position = _cameraTargetPos;
// プレイヤー入力を解除(任意)
if (lockPlayerDuringTransition && playerInputComponent != null)
{
playerInputComponent.enabled = true;
}
}
/// <summary>
/// 外部からこのトリガーを有効/無効にしたいとき用のメソッド
/// </summary>
/// <param name="enabled">true なら有効、false なら無効</param>
public void SetEnabled(bool enabled)
{
isEnabled = enabled;
}
/// <summary>
/// シーンビュー上でトリガーの向きや次の部屋位置を確認しやすくするための Gizmo 描画
/// </summary>
private void OnDrawGizmosSelected()
{
// トリガーの位置
Gizmos.color = Color.cyan;
Gizmos.DrawWireCube(transform.position, GetTriggerSize());
// 次の部屋の中心
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(nextRoomCenter, 0.3f);
// カメラの移動方向を矢印で描画
Gizmos.color = Color.magenta;
Gizmos.DrawLine(transform.position, nextRoomCenter);
}
/// <summary>
/// BoxCollider2D のサイズを取得(Gizmo 用)
/// </summary>
private Vector3 GetTriggerSize()
{
if (_triggerCollider == null)
{
_triggerCollider = GetComponent<BoxCollider2D>();
}
if (_triggerCollider != null)
{
// BoxCollider2D のサイズは 2D 空間なので、Z は 0 にしておく
return new Vector3(_triggerCollider.size.x, _triggerCollider.size.y, 0f);
}
return Vector3.one;
}
}
使い方の手順
ここでは 2D の横スクロール or 上下左右移動ゲームを想定して、「画面右端に到達したら次の部屋へ」「画面左端に戻ったら前の部屋へ」といった例で説明します。
手順①:シーンにカメラとプレイヤーを用意する
- シーンに メインカメラ を配置します(通常は
MainCameraタグ付き)。 - プレイヤーの GameObject を用意し、
Rigidbody2D+Collider2D+ 自前の移動スクリプトなどを付けておきます。 - プレイヤーには Tag を “Player” に設定しておきましょう(
OnTriggerEnter2Dで判定に使います)。
もし新しい Input System や独自の入力スクリプトを使っている場合は、「有効/無効で入力を止められるコンポーネント」(例:PlayerInput、自作の PlayerController など)を用意しておくと、カメラ移動中に操作をロックできて気持ちよくなります。
手順②:部屋のサイズと位置を決める
- たとえば 1 部屋あたりの幅を 16 ユニット と決めるとします。
- 最初の部屋の中心を
(0, 0)、次の部屋の中心を(16, 0)、その次を(32, 0)…といった感じで決めておきます。 - 縦方向にも部屋を作りたい場合は、
(0, 9)、(0, 18)といったようにグリッド状に並べていくイメージです。
カメラの Orthographic Size(2D カメラの表示サイズ)とあわせて、1 画面にちょうど収まるように部屋のサイズを調整しておくと綺麗に切り替わります。
手順③:RoomTransition トリガーを作成する
- 空の GameObject を作成し、名前を
RightExitTriggerなどにします。 - この GameObject に
BoxCollider2Dを追加し、Is Trigger にチェックを入れます。 - 同じ GameObject に、先ほどの
RoomTransitionコンポーネントを追加します。 - インスペクターで以下の項目を設定します:
- Target Camera:メインカメラをドラッグ&ドロップ(空なら自動で
Camera.mainを使います)。 - Player Transform:プレイヤーの Transform をドラッグ&ドロップ(未設定でも、最初に入ってきた “Player” タグのオブジェクトを使います)。
- Next Room Center:次の部屋の中心座標(例:
(16, 0))。 - Player Spawn Position In Next Room:次の部屋でプレイヤーを出現させたい位置(例:
(16 - 6, 0)など、少し左寄り)。 - Transition Duration:カメラ移動にかける時間(例:
0.8秒)。 - Lock Player During Transition:カメラ移動中にプレイヤー操作を止めたい場合は ON。
- Player Input Component:入力を制御しているコンポーネントをドラッグ&ドロップ(例:
PlayerInputや自作のPlayerController)。
- Target Camera:メインカメラをドラッグ&ドロップ(空なら自動で
BoxCollider2Dのサイズと位置を調整して、「画面右端」に細長いトリガーゾーンを作ります。プレイヤーがそこに触れたら部屋遷移が発生するイメージです。
同様にして、左端に戻る用のトリガーも作れます。
LeftExitTriggerを作成し、Next Room Centerを(0, 0)に、Player Spawn Position In Next Roomを(0 + 6, 0)などに設定すれば、「右の部屋から左の部屋へ戻る」遷移が簡単に作れます。
手順④:敵や動く床にも応用する
この RoomTransition は「プレイヤーがトリガーに入ったときにカメラを動かす」だけのコンポーネントなので、
- 敵キャラの AI や動きとは完全に独立しています。
- 動く床やリフト、ギミックとも関係なく動作します。
たとえば、次のようなレベルデザインができます。
- 敵が大量にいる部屋:部屋の左右に
RoomTransitionトリガーを置くだけで、カメラは自動で部屋単位で切り替わります。敵の出現・消滅は別のコンポーネントに任せましょう。 - 動く床のある部屋:プレイヤーが動く床に乗って右端まで運ばれたとき、床ごとトリガーに入ればカメラが次の部屋に移動します。床の挙動は床コンポーネントに責務を分けておくと、RoomTransition は一切知らなくて済みます。
このように、「カメラを部屋単位で動かす」という責務だけを切り出すことで、他のゲームロジックときれいに分離できます。
メリットと応用
メリット①:プレハブ化してレベルデザインが楽になる
RoomTransition を含んだトリガー GameObject をプレハブ化しておけば、
- 「右の部屋に行くトリガー」「左の部屋に戻るトリガー」「上の部屋に行く梯子の出口」などをプレハブとして用意
- レベルデザイナーはシーン上にプレハブをポンポン置いて、
Next Room CenterとPlayer Spawn Positionだけを調整
という運用ができます。カメラの移動アニメーションや入力ロックなどは、コンポーネント側で一括管理されるので、シーンごとのバラつきが減り、調整もしやすくなります。
メリット②:プレイヤーの移動ロジックと完全に分離できる
プレイヤーの移動スクリプトは「入力に応じて移動する」だけに集中させ、
- カメラを追従させるかどうか
- どのタイミングで部屋を切り替えるか
といったことは RoomTransition に任せてしまいましょう。
こうすることで、
- プレイヤーの移動周りのバグを追いやすくなる
- カメラ演出だけを個別に差し替えたり、バージョン違いを作りやすくなる
といったメリットが得られます。
メリット③:カメラ演出のバリエーションを増やしやすい
RoomTransition はカメラの開始位置と終了位置、所要時間を持っているので、
- 縦方向だけの部屋遷移(上の階、地下など)
- 斜め方向の部屋遷移(斜め通路など)
- 超高速移動・スローモーション風の演出
といったバリエーションを、コンポーネントのパラメータを変えるだけで作れます。
改造案:カメラ移動中にフェード演出を入れる
もう少しリッチな演出を入れたい場合は、画面をフェードアウトしてからカメラを動かし、フェードインするような処理を足してみるのも良いですね。
以下は、CanvasGroup を使った簡易フェード用の関数例です(別コンポーネントとして用意して、RoomTransition から呼び出すと責務分離も保てます)。
using System.Collections;
using UnityEngine;
public class SimpleScreenFader : MonoBehaviour
{
[SerializeField] private CanvasGroup fadeCanvasGroup;
[SerializeField] private float fadeDuration = 0.5f;
// 画面をフェードアウト → フェードイン するコルーチン
public IEnumerator FadeOutIn(System.Action middleAction)
{
// フェードアウト
yield return Fade(0f, 1f);
// 中間処理(カメラ移動など)を実行
middleAction?.Invoke();
// フェードイン
yield return Fade(1f, 0f);
}
private IEnumerator Fade(float from, float to)
{
float timer = 0f;
while (timer < fadeDuration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / fadeDuration);
fadeCanvasGroup.alpha = Mathf.Lerp(from, to, t);
yield return null;
}
fadeCanvasGroup.alpha = to;
}
}
RoomTransition 側では、カメラの Lerp を直接行う代わりに、このフェーダーの FadeOutIn を呼び出して、その中の middleAction でカメラ位置やプレイヤー位置を一気に切り替える、という構成にすると、「スライド移動版」「フェード版」を簡単に差し替えられるようになります。
このように、小さなコンポーネントに責務を分けておくと、演出の差し替えや拡張がとてもやりやすくなります。ぜひ自分のプロジェクトに合わせて、RoomTransition を育てていってみてください。
