Unityを触り始めた頃にありがちなのが、Update() の中に「移動」「入力」「アニメーション」「重力」など、あらゆる処理を詰め込んでしまうパターンです。
動きは一応するのですが、少し仕様が変わるたびに巨大なスクリプトを開いてスクロール地獄…どこを直せばいいのか分からなくなりますよね。
そこで今回は、「重力だけ」を担当するシンプルなコンポーネントとして、GravityComponent を作ってみましょう。
「親が空中にいる場合、毎フレーム velocity.y に重力を加算する」という、責務がはっきりした小さな部品に分解することで、プレイヤー・敵・動く足場など、どのオブジェクトにもコピペで簡単に重力を付けられるようにします。
【Unity】どのオブジェクトにも簡単重力付与!「GravityComponent」コンポーネント
この記事では、Transform の位置を直接動かす系のオブジェクト向けに、自前の velocity を持ち、velocity.y に重力を加算するコンポーネントを実装します。
Rigidbody を使わずに、キャラクターや敵 AI を「手動物理」で制御したいケースにぴったりです。
フルソースコード
using UnityEngine;
/// <summary>
/// シンプルな重力コンポーネント。
///
/// - 外部から渡された velocity(速度ベクトル)の y 成分に、
/// 毎フレーム gravity を加算していきます。
/// - 「地面についているかどうか」は IsGrounded のフラグで管理します。
/// - Transform.position を直接動かすキャラクター制御と相性が良いです。
///
/// 想定される使い方:
/// - ほかのスクリプトで velocity を計算(移動入力など)
/// - このコンポーネントで重力だけを担当
/// - 最後に Transform.position += velocity * deltaTime; で反映
///
/// 責務を「重力の付与」に限定することで、Godクラス化を防ぎます。
/// </summary>
public class GravityComponent : MonoBehaviour
{
[Header("重力設定")]
[SerializeField]
[Tooltip("下向きの加速度(マイナス値推奨)。例: -9.81 や -20 など")]
private float gravity = -20f;
[SerializeField]
[Tooltip("最大落下速度(絶対値)。0 以下なら無制限")]
private float maxFallSpeed = 50f;
[Header("状態")]
[SerializeField]
[Tooltip("現在の速度。外部スクリプトから読み書き可能にしておく")]
private Vector3 velocity = Vector3.zero;
[SerializeField]
[Tooltip("地面に接地しているかどうか。他コンポーネントから更新する想定")]
private bool isGrounded = false;
/// <summary>
/// 現在の速度を取得・設定するためのプロパティ。
/// ほかのコンポーネントから velocity を操作できるようにします。
/// </summary>
public Vector3 Velocity
{
get => velocity;
set => velocity = value;
}
/// <summary>
/// 接地状態を取得・設定するためのプロパティ。
/// たとえばフットコライダーのスクリプトから更新します。
/// </summary>
public bool IsGrounded
{
get => isGrounded;
set => isGrounded = value;
}
/// <summary>
/// 重力値のプロパティ(必要に応じてランタイムで変更可能)。
/// </summary>
public float Gravity
{
get => gravity;
set => gravity = value;
}
/// <summary>
/// 最大落下速度のプロパティ。
/// </summary>
public float MaxFallSpeed
{
get => maxFallSpeed;
set => maxFallSpeed = value;
}
private void Reset()
{
// Reset はインスペクターから「Reset」を押したときなどに呼ばれます。
// デフォルト値を少しだけ分かりやすく設定しておきます。
gravity = -20f;
maxFallSpeed = 50f;
velocity = Vector3.zero;
isGrounded = false;
}
private void Update()
{
ApplyGravity(Time.deltaTime);
ApplyMovement(Time.deltaTime);
}
/// <summary>
/// 重力を velocity.y に加算する処理。
/// 親(このコンポーネントが付いているオブジェクト)が空中にいる場合のみ適用します。
/// </summary>
/// <param name="deltaTime">Time.deltaTime を渡すことを想定</param>
private void ApplyGravity(float deltaTime)
{
// 地面にいる場合は、落下させない(velocity.y をリセット)するのが一般的です。
if (isGrounded)
{
if (velocity.y < 0f)
{
velocity.y = 0f;
}
return;
}
// 空中にいるときだけ重力を加算
velocity.y += gravity * deltaTime;
// 最大落下速度を制限したい場合
if (maxFallSpeed > 0f)
{
// gravity がマイナス想定なので、下方向はマイナス値が大きくなりすぎないように Clamp
float minY = -Mathf.Abs(maxFallSpeed);
if (velocity.y < minY)
{
velocity.y = minY;
}
}
}
/// <summary>
/// velocity を Transform.position に反映する処理。
///
/// 「重力付与」と「移動反映」を分けておくと、
/// 物理挙動を別のコンポーネントに差し替えたいときなどに柔軟になりますが、
/// 今回はシンプルさ優先で同じクラスにまとめています。
/// </summary>
/// <param name="deltaTime">Time.deltaTime を渡すことを想定</param>
private void ApplyMovement(float deltaTime)
{
// 位置を更新(Transform 直書き)
transform.position += velocity * deltaTime;
}
/// <summary>
/// 外部から明示的に「上方向に速度を与える」ためのヘルパー。
/// 例: ジャンプ開始時に呼ぶ。
/// </summary>
/// <param name="jumpSpeed">上向きの初速度(正の値)</param>
public void AddJumpVelocity(float jumpSpeed)
{
// 接地しているときだけジャンプを許可する例
if (!isGrounded)
{
return;
}
// y 方向の速度を上書き
velocity.y = Mathf.Max(velocity.y, jumpSpeed);
// 空中に飛び出すので接地フラグをオフ
isGrounded = false;
}
/// <summary>
/// デバッグ用に現在の状態をインスペクターに分かりやすく表示する。
/// (エディタ上だけで動くメソッド。ビルドには影響しません)
/// </summary>
private void OnValidate()
{
// maxFallSpeed は負の値だと意味が分かりづらいので、強制的に正に寄せておく
if (maxFallSpeed < 0f)
{
maxFallSpeed = Mathf.Abs(maxFallSpeed);
}
}
}
使い方の手順
ここからは、実際にシーン内で GravityComponent を使う手順を見ていきましょう。
手順① 空のオブジェクト(またはプレイヤー)にアタッチする
- シーン内に Player という名前の GameObject を作成します(3D Object > Capsule などでもOK)。
- 上記の
GravityComponent.csをプロジェクトに作成し、Player にアタッチします。 - インスペクターで
GravityやMax Fall Speedを調整しておきましょう(例: Gravity = -25, MaxFallSpeed = 40)。
手順② 水平移動用の簡単なスクリプトを追加する
GravityComponent は「重力」と「位置反映」だけを担当しているので、左右の移動などは別コンポーネントに切り出すときれいです。
例えば、こんなシンプルな移動コンポーネントを別ファイルに用意します。
using UnityEngine;
/// <summary>
/// 矢印キー / WASD で左右移動するだけのシンプルなコンポーネント。
/// 垂直方向の速度(重力やジャンプ)は GravityComponent に任せます。
/// </summary>
[RequireComponent(typeof(GravityComponent))]
public class SimpleHorizontalMover : MonoBehaviour
{
[SerializeField]
[Tooltip("水平方向の移動速度")]
private float moveSpeed = 5f;
private GravityComponent gravityComponent;
private void Awake()
{
// 同じ GameObject 上に GravityComponent がある前提
gravityComponent = GetComponent<GravityComponent>();
}
private void Update()
{
// 入力取得(A/D, 左右キーなど)
float inputX = Input.GetAxisRaw("Horizontal");
// 現在の velocity を取得
Vector3 velocity = gravityComponent.Velocity;
// x 方向の速度だけ更新(y は GravityComponent に任せる)
velocity.x = inputX * moveSpeed;
// 更新した velocity を戻す
gravityComponent.Velocity = velocity;
}
}
- この
SimpleHorizontalMoverを Player にアタッチします。 [RequireComponent]によって、GravityComponent が必須であることを保証しています。
手順③ 接地判定をざっくり入れてみる(例:床より下に行ったら接地とみなす)
本格的な接地判定(Raycast や CharacterController)は長くなるので、ここでは一番シンプルな例を示します。
「Y=0 を地面とみなし、それより下に行ったら接地」とするだけのコンポーネントです。
using UnityEngine;
/// <summary>
/// 非常にシンプルな接地判定コンポーネント。
/// Y=0 を地面とみなし、それより下に行ったら位置を補正して接地扱いにします。
/// デモ用の簡易実装です。
/// </summary>
[RequireComponent(typeof(GravityComponent))]
public class SimpleGroundChecker : MonoBehaviour
{
[SerializeField]
[Tooltip("この Y 高さを地面とみなす")]
private float groundHeight = 0f;
private GravityComponent gravityComponent;
private void Awake()
{
gravityComponent = GetComponent<GravityComponent>();
}
private void Update()
{
Vector3 pos = transform.position;
// 地面より下に行っていたら補正して接地扱い
if (pos.y <= groundHeight)
{
pos.y = groundHeight;
transform.position = pos;
gravityComponent.IsGrounded = true;
}
else
{
gravityComponent.IsGrounded = false;
}
}
}
- この
SimpleGroundCheckerを Player にアタッチします。 - シーンビューで
groundHeightを 0 にしておき、Y=0 に Plane などの床を置くと分かりやすいです。
手順④ 実行してみる
- シーンを再生すると、Player が Y=0 の高さから落下し、床で止まります。
- 左右キー(または A/D)で水平方向に移動できます。
- GravityComponent が velocity.y に重力を加算し、SimpleHorizontalMover が velocity.x を制御し、SimpleGroundChecker が 接地フラグを管理する、というきれいな分業になっています。
同じ GravityComponent を、敵キャラや動く足場にもアタッチすれば、重力挙動を簡単に統一できます。
「このオブジェクトは重力いらないな」と思ったらコンポーネントを外すだけなので、プレハブの挙動管理もかなり楽になります。
メリットと応用
メリット① プレハブごとの挙動をコンポーネント単位で切り替えられる
GravityComponent は「重力を付与する」という単一責務に絞っているため、
- プレイヤー:GravityComponent + 入力コンポーネント + アニメーションコンポーネント
- 敵:GravityComponent + AI コンポーネント
- 動く足場:GravityComponent なし + 移動パターンコンポーネント
といった形で、プレハブの組み合わせだけで挙動を切り替えられます。
巨大な PlayerController.cs をコピペして改造するようなやり方と比べて、保守性が段違いですね。
メリット② 「重力の仕様変更」が一箇所で済む
ゲームの途中で「重力をもう少し重くしたい」「最大落下速度を制限したい」「月面ステージ用に重力を半分にしたい」といった要望はよく出ます。
GravityComponent に重力のロジックを集約しておけば、このコンポーネントだけを修正すれば、すべてのオブジェクトに一括適用できます。
メリット③ テストやデバッグがしやすい
重力だけをオフにしたいときは、インスペクターでコンポーネントを無効にするか、gravity = 0 にするだけです。
また、velocity をインスペクターで表示しているので、「今どれくらい落下しているか?」を簡単に確認できます。
応用:二段ジャンプを追加する改造案
最後に、GravityComponent を少し拡張して、二段ジャンプを実装する場合の一例を示します。
以下のようなメソッドを GravityComponent に追加することで、ジャンプ回数の管理もこのコンポーネントに任せられます。
// --- GravityComponent 内に追加する例 ---
[Header("ジャンプ設定")]
[SerializeField]
[Tooltip("最大ジャンプ回数(1なら通常ジャンプのみ、2なら二段ジャンプ)")]
private int maxJumpCount = 1;
[SerializeField]
[Tooltip("現在のジャンプ回数(デバッグ表示用)")]
private int currentJumpCount = 0;
/// <summary>
/// ジャンプ要求を処理するメソッド。
/// 入力コンポーネントなどから呼び出して使います。
/// </summary>
/// <param name="jumpSpeed">上向きの初速度</param>
public void TryJump(float jumpSpeed)
{
// 接地していたらジャンプ回数をリセット
if (isGrounded)
{
currentJumpCount = 0;
}
// まだジャンプ可能回数が残っているか?
if (currentJumpCount >= maxJumpCount)
{
return;
}
// 実際にジャンプさせる
velocity.y = Mathf.Max(velocity.y, jumpSpeed);
isGrounded = false;
currentJumpCount++;
}
入力側では、gravityComponent.TryJump(jumpSpeed); を呼ぶだけで、一段ジャンプ・二段ジャンプ・三段ジャンプなどを簡単に切り替えられます。
このように、重力とジャンプ周りのロジックを一つのコンポーネントに閉じ込めておくと、後からの仕様変更や拡張がとてもやりやすくなりますね。
ぜひ、自分のプロジェクトでも「重力」「移動」「入力」「アニメーション」などを小さなコンポーネントに分割して、Godクラスから卒業していきましょう。
