Unityを触り始めた頃って、つい何でもかんでも Update() に書きがちですよね。
プレイヤーの移動も、カメラの追従も、攻撃も、エフェクト制御も、全部ひとつのスクリプトに押し込んでしまう…。
そうすると、
- コード量が増えすぎてバグの原因が追えない
- 一部の機能だけ別のキャラでも使い回したいのに、切り出せない
- ちょっとした仕様変更で巨大スクリプト全体を触る必要が出てくる
といった「Godクラス問題」にハマりがちです。
そこでこの記事では、「瞬間的に進行方向へワープする」機能だけを切り出したコンポーネント
TeleportDash(瞬間移動ダッシュ) を作ってみましょう。
移動ロジックとは分離して、
「進行方向」と「ボタン入力」さえ渡してあげれば、
キャラクターでも敵でも、動く床でも、どんなオブジェクトにもポン付けできるようにします。
【Unity】一瞬でスッと消えるダッシュ!「TeleportDash」コンポーネント
以下が TeleportDash コンポーネントのフルコードです。
進行方向ベクトルをもとに、一定距離先に瞬間移動させる仕組みになっています。
using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 進行方向へ瞬間移動(ダッシュ)させるコンポーネント。
/// ・「どの方向へ進むか」は外部からベクトルを渡す
/// ・「いつ発動するか」は InputAction または 外部スクリプトから呼び出す
///
/// 単一責任: 「瞬間移動ダッシュ」だけを担当する
/// 移動処理やアニメーションは別コンポーネントに任せる前提
/// </summary>
[DisallowMultipleComponent]
public class TeleportDash : MonoBehaviour
{
// --- 設定項目(インスペクタで調整) ---
[Header("ダッシュ設定")]
[Tooltip("瞬間移動する距離(ワールド座標ベース)")]
[SerializeField] private float dashDistance = 5f;
[Tooltip("ダッシュのクールタイム(秒)。0なら連打可能")]
[SerializeField] private float cooldownSeconds = 0.5f;
[Tooltip("ダッシュ方向を正規化するかどうか(true推奨)")]
[SerializeField] private bool normalizeDirection = true;
[Header("衝突判定(壁抜け防止)")]
[Tooltip("レイキャストで当たり判定を行うか")]
[SerializeField] private bool useObstacleCheck = true;
[Tooltip("ヒットを検出するレイヤー(壁・床など)")]
[SerializeField] private LayerMask obstacleLayer = ~0;
[Tooltip("レイキャストの開始位置オフセット(キャラの中心から少し上げたいときなど)")]
[SerializeField] private Vector3 raycastOffset = Vector3.up * 0.5f;
[Tooltip("壁の手前にどれくらい余裕を残すか")]
[SerializeField] private float safeMargin = 0.05f;
[Header("入力(新Input Systemを使う場合)")]
[Tooltip("ダッシュをトリガーする InputAction(Button)")]
[SerializeField] private InputActionReference dashAction;
[Header("デバッグ表示")]
[Tooltip("Sceneビューにレイキャスト線を表示するか")]
[SerializeField] private bool drawGizmos = true;
[Tooltip("Gizmo線の色")]
[SerializeField] private Color gizmoColor = Color.cyan;
// --- 内部状態 ---
// 最後にダッシュした時間
private float lastDashTime = -999f;
// 現在の進行方向(外部から更新してもらう)
private Vector3 currentDirection = Vector3.forward;
// Transformキャッシュ
private Transform cachedTransform;
// --- ライフサイクル ---
private void Awake()
{
cachedTransform = transform;
}
private void OnEnable()
{
// InputActionが設定されていれば購読
if (dashAction != null && dashAction.action != null)
{
dashAction.action.performed += OnDashActionPerformed;
if (!dashAction.action.enabled)
{
dashAction.action.Enable();
}
}
}
private void OnDisable()
{
// 購読解除
if (dashAction != null && dashAction.action != null)
{
dashAction.action.performed -= OnDashActionPerformed;
}
}
// --- 公開API ---
/// <summary>
/// 進行方向を外部から設定する。
/// 例:キャラクターの移動入力や向いている方向を渡す。
/// </summary>
/// <param name="direction">ワールド座標系の方向ベクトル</param>
public void SetDirection(Vector3 direction)
{
// ゼロベクトルは無視
if (direction == Vector3.zero) return;
currentDirection = direction;
}
/// <summary>
/// 外部スクリプトから明示的にダッシュを実行したいときに呼ぶ。
/// 入力システムを使わず、独自のトリガー(敵AIなど)でも利用可能。
/// </summary>
public void TryDash()
{
// クールタイムチェック
if (!IsCooldownFinished())
{
return;
}
// ゼロ方向なら何もしない
if (currentDirection == Vector3.zero)
{
return;
}
Vector3 dashDir = currentDirection;
if (normalizeDirection)
{
dashDir = dashDir.normalized;
}
// 実際にテレポート先を決める
Vector3 start = cachedTransform.position + raycastOffset;
Vector3 target = start + dashDir * dashDistance;
// 壁などに当たる場合は、手前で止める
if (useObstacleCheck)
{
if (Physics.Raycast(start, dashDir, out RaycastHit hitInfo, dashDistance, obstacleLayer,
QueryTriggerInteraction.Ignore))
{
// 安全マージン分だけ手前にとどめる
float safeDistance = Mathf.Max(0f, hitInfo.distance - safeMargin);
target = start + dashDir * safeDistance;
}
}
// 実際にテレポートさせる(Y座標などはそのまま)
cachedTransform.position = new Vector3(
target.x,
target.y,
target.z
);
lastDashTime = Time.time;
}
// --- 内部処理 ---
/// <summary>
/// クールタイムが終了しているかどうか
/// </summary>
private bool IsCooldownFinished()
{
if (cooldownSeconds <= 0f) return true;
return Time.time - lastDashTime >= cooldownSeconds;
}
/// <summary>
/// InputActionの performed イベントから呼ばれる
/// </summary>
private void OnDashActionPerformed(InputAction.CallbackContext context)
{
// ボタン押下時だけ反応(押しっぱなしの連続発火などを防ぎたい場合)
if (context.performed)
{
TryDash();
}
}
// --- デバッグ用Gizmo表示 ---
private void OnDrawGizmosSelected()
{
if (!drawGizmos) return;
// Sceneビューでレイキャストの様子を可視化する
Gizmos.color = gizmoColor;
Vector3 start = transform.position + raycastOffset;
Vector3 dir = currentDirection;
if (normalizeDirection && dir != Vector3.zero)
{
dir = dir.normalized;
}
Vector3 end = start + dir * dashDistance;
Gizmos.DrawLine(start, end);
Gizmos.DrawSphere(end, 0.1f);
}
}
使い方の手順
ここでは「プレイヤーの瞬間移動ダッシュ」を例に、Unity6での具体的な使い方を見ていきます。
敵AIや動く床でも基本は同じです。
手順①:プレイヤー用のGameObjectにアタッチ
- シーン内のプレイヤー(例:
Player)オブジェクトを選択します。 Add ComponentからTeleportDashを追加します。- インスペクタで以下を調整します:
- Dash Distance:5~8あたりから試す
- Cooldown Seconds:0.3~0.7秒くらい
- Use Obstacle Check:壁抜けを防ぎたいならチェックを入れる
- Obstacle Layer:
DefaultやGroundなど、壁・床のレイヤーを設定
手順②:進行方向を渡すコンポーネントを用意する
TeleportDash 自体は「移動方向」を知りません。
そこで、プレイヤーの移動入力から進行方向を算出し、それを SetDirection() に渡す小さなコンポーネントを用意します。
例:新Input SystemでWASD入力を受け取り、進行方向を更新するスクリプト
using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// プレイヤーの移動入力から進行方向を TeleportDash に渡すだけのコンポーネント。
/// 移動そのもの(Rigidbody.MovePosition など)は別コンポーネントに任せる想定。
/// </summary>
[RequireComponent(typeof(TeleportDash))]
public class PlayerDashDirection : MonoBehaviour
{
[Header("入力アクション")]
[Tooltip("移動入力(Vector2)を受け取る InputAction")]
[SerializeField] private InputActionReference moveAction;
[Header("カメラ基準の移動にするか")]
[SerializeField] private bool useCameraForward = true;
private TeleportDash teleportDash;
private Camera mainCamera;
private void Awake()
{
teleportDash = GetComponent<TeleportDash>();
mainCamera = Camera.main;
}
private void OnEnable()
{
if (moveAction != null && moveAction.action != null && !moveAction.action.enabled)
{
moveAction.action.Enable();
}
}
private void OnDisable()
{
if (moveAction != null && moveAction.action != null)
{
moveAction.action.Disable();
}
}
private void Update()
{
if (moveAction == null || moveAction.action == null) return;
Vector2 input = moveAction.action.ReadValue<Vector2>();
if (input == Vector2.zero)
{
return;
}
Vector3 moveDir = new Vector3(input.x, 0f, input.y);
if (useCameraForward && mainCamera != null)
{
// カメラの向きに合わせてXZ平面上の方向を作る
Vector3 camForward = mainCamera.transform.forward;
camForward.y = 0f;
camForward.Normalize();
Vector3 camRight = mainCamera.transform.right;
camRight.y = 0f;
camRight.Normalize();
moveDir = camForward * input.y + camRight * input.x;
}
// TeleportDash に進行方向を渡す
teleportDash.SetDirection(moveDir);
}
}
このコンポーネントを Player に追加し、
Move Action に Vector2 タイプの移動InputAction(例:WASD)を割り当てればOKです。
手順③:ダッシュボタンをInputActionに割り当てる
- Input Actions アセット(例:
PlayerControls.inputactions)を開きます。 - プレイヤーマップに
Dashという Button アクションを追加します。 - Binding にキーボードの
Left ShiftやゲームパッドのBボタンなどを設定します。 TeleportDashコンポーネントの Dash Action に、このDashアクションを割り当てます。
これで、ダッシュボタンを押したときに TryDash() が呼ばれ、
現在の進行方向へ瞬間移動するようになります。
手順④:敵や動く床での応用例
- 敵キャラに瞬間移動アタックさせる
- 敵オブジェクトに
TeleportDashを追加 - AIスクリプトからプレイヤー方向
(player.position - enemy.position)をSetDirection()で渡す - 攻撃タイミングで
TryDash()を呼ぶ
- 敵オブジェクトに
- 動く床が一定間隔でワープするギミック
- 床オブジェクトに
TeleportDashを追加 - 進行方向をステージのレールに沿ったベクトルに固定
CoroutineやInvokeRepeatingなどで一定間隔ごとにTryDash()を呼ぶ
- 床オブジェクトに
メリットと応用
TeleportDash を分離コンポーネントにしておくと、プレハブ管理やレベルデザインがかなり楽になります。
- プレイヤーと敵で同じダッシュロジックを共有できる
進行方向の決め方だけ別コンポーネントにしておけば、
実際の瞬間移動処理は使い回し可能です。 - パラメータ調整がプレハブ単位で簡単
距離・クールタイム・壁チェック有無などをコンポーネント単位で調整できるので、
「この敵だけクールタイム長め」「このギミックだけ距離2倍」などが簡単です。 - レベルデザイナーがノーコードで挙動を変えられる
スクリプトをいじらず、インスペクタの値をいじるだけで挙動を変えられるため、
シーン上で試行錯誤しやすくなります。 - 将来の差し替えが楽
「テレポート時にエフェクトを出したい」「画面を一瞬暗転させたい」などの要望が出ても、
TeleportDashにイベントを追加したり、専用のエフェクトコンポーネントを横に生やすだけで対応できます。
応用として、例えば「ダッシュ終了位置にパーティクルを出す」処理を追加したい場合、
責務を増やしすぎないように、別コンポーネントでフックするのがおすすめです。
例:TeleportDash の TryDash() を呼ぶ前後でイベントを発火させる拡張(イメージ)
using UnityEngine;
/// <summary>
/// TeleportDash 実行時にパーティクルを再生するサンプル。
/// TeleportDash の TryDash() をラップして使う想定。
/// </summary>
[RequireComponent(typeof(TeleportDash))]
public class TeleportDashEffect : MonoBehaviour
{
[SerializeField] private ParticleSystem dashEffect;
private TeleportDash teleportDash;
private void Awake()
{
teleportDash = GetComponent<TeleportDash>();
}
public void DashWithEffect()
{
if (dashEffect != null)
{
dashEffect.Play();
}
// 実際の瞬間移動
teleportDash.TryDash();
}
}
このように、「瞬間移動のロジック」「入力」「エフェクト」を小さなコンポーネントに分割しておくと、
後からの改造・差し替えがとても楽になります。
ぜひ自分のプロジェクトでも、TeleportDash をベースにいろいろ遊んでみてください。
