手動で重力を加える
CoddyのUnityジャーニー「本格的なゲームを作る」セクションの一部。レッスン 38/56。
CharacterControllerは、スクリプトが落下させた場合にのみ落下し、実際の落下では速度が増していきます。重力は毎秒、落下速度に約9.81単位を加えます。そのため、スクリプトでは垂直速度をフィールドに保持し、毎フレームgravity * Time.deltaTimeを加算して、歩行と同じMoveでその速度分だけ移動します。
public float gravity = -9.81f;
float verticalSpeed;
void Update()
{
if (controller.isGrounded && verticalSpeed < 0f)
{
verticalSpeed = -2f; // 接地中:落下を止め、下向きの弱い力をかけ続ける
}
verticalSpeed += gravity * Time.deltaTime;
Vector3 move = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")) * speed;
move.y = verticalSpeed;
controller.Move(move * Time.deltaTime);
}Time.deltaTimeは2つの役割で使われます。gravity * Time.deltaTimeはこのフレームで増加する速度で、速度にTime.deltaTimeを掛けた値はこのフレームで移動する距離です。速度はフレーム間で保持する必要があるため、フィールドにします。ローカル変数では毎フレーム0から始まってしまいます。静止状態から落下すると、半秒後のコントローラーの速度は毎秒-4.9単位、1秒後には-9.81単位です。
落下と同じくらい、リセットも重要です。床の上ではMoveがコントローラーを停止させますが、フィールドの値は止まりません。リセットしないと、プレイヤーが立っている間も値が増え続け、次に崖から一歩踏み出したとき、最大速度で落下します。0ではなく小さな負の値にリセットするのは、毎回のMoveで床に押しつけるためです。フレームレートが高い場合、1フレーム分の重力による押しつけがMin Move Distanceより短くなり、Moveが何もせず、isGroundedがfalseになります。isGroundedはMoveの前に読み取られるため、前のフレームのMoveの状態を表します。
チャレンジ
中級ダイバーは高い塔の上に立っていて、矢印キーで歩きます(すでに実装済みです)。Diver.cs に重力を追加しましょう。Inspector で gravity を -12、maxFall を 10 に設定します。スクリプトには verticalSpeed フィールドがあります。
- 毎フレーム、
verticalSpeedに1秒あたりのgravityが加算され、歩行と同じ Move でその値だけダイバーが下に移動します。 - 空気抵抗によって長い落下は遅くなります。ダイバーの落下速度は、1秒あたり
maxFallユニットを超えません。 - コントローラーが接地していて、
verticalSpeedが 0 未満の場合は、verticalSpeedを -1 に設定します。
Play を押し、塔から歩いて降りてから、Check を押してください。
自分で試してみよう
using UnityEngine;
public class Diver : MonoBehaviour
{
public float speed = 4f;
public float gravity = -12f;
public float maxFall = 10f;
CharacterController controller;
float verticalSpeed;
void Start()
{
controller = GetComponent<CharacterController>();
}
void Update()
{
// 重力: 地面ではverticalSpeedをリセットし、重力を加え、落下速度を制限して、move.yに代入する
Vector3 move = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")) * speed;
controller.Move(move * Time.deltaTime);
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。