視点を移動できるようになったら、次はキャラクターの移動ができるようにしたいと思います。ここではCharacterControllerを使った制御を用います。地面との当たり判定も個別に実装を行いますよ。
今回できること
ここではキャラの移動だけでなく、スクリプトの継承という機能を使って今後の実装を楽にします。慣れないうちは少し難しいかもしれませんが、他でも使える技術ですのでしっかり覚えていきましょう!
完成動画
高いところから落下したり、移動入力(WASD or 上下左右キー)でキャラクターが移動できるようになります!
覚えられること・技術
前述したとおり、ここでは継承を使って今後の実装を楽にします。Unityならではの初期化方法もありますので是非身につけてください!
- CharacterControllerの移動処理
- ベースクラスによる初期化処理の共通化(継承)
- GetComponentsによる同系統のクラスを収集する方法
移動処理実装
FPMovementスクリプト作成
前回同様、機能するスクリプトを作成します。
using UnityEngine;
public class FPMovement : MonoBehaviour
{
[Header("Movement")]
[SerializeField] private float movementSpeed = 5.0f;
[SerializeField] private float gravity = -9.81f;
public float Gravity => gravity;
private Vector3 velocity= Vector3.zero;
public Vector3 Velocity
{
get => velocity;
set => velocity= value;
}
private FirstPersonController controller;
public void Initialize(FirstPersonController controller)
{
this.controller = controller;
}
private void Update()
{
var currentInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
Vector3 horizontalMovementVelocity = transform.TransformDirection(new Vector3(currentInput.x, 0, currentInput.y));
horizontalMovementVelocity *= movementSpeed;
Vector3 verticalMovementVelocity = new Vector3(0, velocity.y, 0);
if (!controller.CharacterController.isGrounded || 0f < verticalMovementVelocity.y)
{
verticalMovementVelocity.y += gravity * Time.deltaTime;
}
else
{
verticalMovementVelocity.y = gravity * 0.1f;
}
velocity = horizontalMovementVelocity + verticalMovementVelocity;
controller.CharacterController.Move(velocity * Time.deltaTime);
}
}
- CharacterControllerの移動処理について
- Moveメソッドに移動するベクトルを渡してあげることで移動します
- 地面に足がついていない場合は落下方向にも速度を計算する必要があります
- 地面に足がついている場合もちょっと下に速度を入れてあげないと接地判定が抜けることがあります
- これは私のテスト中に起こったことなので、別の回避方法があるかもしれません
FirstPersonControllerに組み込む
CameraLook同様に、FirstPersonControllerから初期化処理を行う必要があります。変更点としてはAwakeの最後にFPMovementの初期化処理が追加されているだけです。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class FirstPersonController : MonoBehaviour
{
private CharacterController characterController;
public CharacterController CharacterController => characterController;
private Camera playerCamera;
public Camera PlayerCamera => playerCamera;
private void Awake()
{
characterController = GetComponent<CharacterController>();
playerCamera = GetComponentInChildren<Camera>();
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
FPCameraLook cameraLook = GetComponent<FPCameraLook>();
cameraLook?.Initialize(this);
FPMovement movement = GetComponent<FPMovement>();
movement?.Initialize(this);
}
}
セットアップ
準備が出来たらPlayerゲームオブジェクトに設定を追加しましょう。Movementコンポーネントを追加すると、地面まで落下する機能も追加されます。検証のために少し高い位置に移動させましょう
- Playerゲームオブジェクト選択
- Transform
- Position:0 , 2 , 0
- 落下が確認できる高さであればOK
- FPMovementコンポーネントを追加
- こちらは追加するだけ!
- Transform
動作確認ッ!!
落下が少し確認しづらいですが、下図のようなものが作成出来ます。
- 落下ができることを確認
- 移動できることを確認
- 前後
- 左右
継承を使ってFP系のベースクラスを作る
今回の内容的にはここで終わっても大丈夫なんですが、FirstPersonControllerに対して初期化処理を毎回追加するのは面倒ですね。ということで継承を使って今後の開発を楽にしましょう。
改善したい点とアプローチ
問題になる点は、「各機能のコンポーネントを追加するたびに初期化処理を追加する必要がある」ということです。開発を進めていくうえで、各コードを都度編集するのは大変です。
これらの問題を解決するために、次の機能を使いたいと思います。
- 継承を使って初期化処理を共通化
- 必要なものは個別に初期化処理を行う
- GetComponentsを使って同インスペクター内の継承クラスを集めて一括初期化!
今回に限らず、色んな場面で使える処理となりますので是非マスターしてください!
ここからの作業では、一時的にエラーが発生することがあります。処理の載せ替え作業中にはむしろエラーを利用して更新するべき場所の確認を行ったりします。不安な部分もあるかもしれませんがしっかりと確認しながらコード修正をしましょう!
FPComponentBase
まずは一人称コントローラーのコンポーネントのベースになるクラスを作成します。
using UnityEngine;
public class FPComponentBase : MonoBehaviour
{
private FirstPersonController controller;
protected FirstPersonController Controller => controller;
public void Initialize(FirstPersonController controller)
{
this.controller = controller;
initialize();
}
public virtual void initialize()
{
// 継承先で必要があれば実装
}
}
- 各機能ごとのコンポーネントはFPComponentBaseを継承する
- FirstPersonControllerにアクセスしたい場合、プロパティのController経由でアクセスする
- 初期化処理はInitializeメソッドを利用
- コンポーネントごとになにか初期化したい場合はinitizlizeメソッドをオーバーライドして実装する
各コンポーネントを作り直し
ベースクラスが作成できたらこれまで作成してきた2つのコンポーネントを変更しましょう。変更点は大きく以下
- 継承しているクラスが変更
- MonoBehaviour > FPComponentBase
- メンバ変数の変更
- FirstPersonControllerの変数が、ベースクラスのプロパティに置き換わります
FPCameraLook
こちらはオーバーライドした初期化メソッドの実装を行っています。
using UnityEngine;
public class FPCameraLook : FPComponentBase
{
private Transform cameraRoot;
[Header("Look")]
[SerializeField, Range(1, 10)] private float lookSpeed = 2.0f;
[SerializeField, Range(1, 100)] private float upperLookLimit = 50.0f;
[SerializeField, Range(1, 100)] private float lowerLookLimit = 50.0f;
public override void initialize()
{
cameraRoot = Controller.PlayerCamera.transform.parent;
}
private void Update()
{
Vector2 delta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
Vector3 euler = transform.eulerAngles;
euler.y += delta.x * lookSpeed;
transform.eulerAngles = euler;
float cameraEulerX = cameraRoot.localEulerAngles.x;
cameraEulerX -= delta.y * lookSpeed;
cameraEulerX = 180 < cameraEulerX ? cameraEulerX - 360 : cameraEulerX;
cameraEulerX = Mathf.Clamp(cameraEulerX, -upperLookLimit, lowerLookLimit);
cameraRoot.localRotation = Quaternion.Euler(cameraEulerX, 0, 0);
}
}
FPMovement
controllerが大文字のプロパティに変わっていることに注意してください
using UnityEngine;
public class FPMovement : FPComponentBase
{
[Header("Movement")]
[SerializeField] private float movementSpeed = 5.0f;
public float MovementSpeed => movementSpeed;
[SerializeField] private float gravity = -9.81f;
public float Gravity => gravity;
public Vector3 velocity = Vector3.zero;
public Vector3 Velocity
{
get => velocity;
set => velocity = value;
}
private void Update()
{
var currentInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
Vector3 horizontalMovementVelocity = transform.TransformDirection(new Vector3(currentInput.x, 0, currentInput.y));
horizontalMovementVelocity *= movementSpeed;
Vector3 verticalMovementVelocity = new Vector3(0, velocity.y, 0);
if (!Controller.CharacterController.isGrounded || 0f < verticalMovementVelocity.y)
{
verticalMovementVelocity.y += gravity * Time.deltaTime;
}
else
{
verticalMovementVelocity.y = gravity * 0.1f;
}
velocity = horizontalMovementVelocity + verticalMovementVelocity;
Controller.CharacterController.Move(velocity * Time.deltaTime);
}
}
FirstPersonControllerの変更
仕上げにFirstPersonControllerの初期化部分を変更します。
GetComponentsは同インスペクター内の指定したコンポーネントを全て集めてくることが出来ます。foreachで各要素を初期化することが出来ます。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class FirstPersonController : MonoBehaviour
{
private CharacterController characterController;
public CharacterController CharacterController => characterController;
private Camera playerCamera;
public Camera PlayerCamera => playerCamera;
private void Awake()
{
characterController = GetComponent<CharacterController>();
playerCamera = GetComponentInChildren<Camera>();
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
foreach (var component in GetComponents<FPComponentBase>())
{
component.Initialize(this);
}
}
}
コメント