FPSキャラに付けるしゃがみ等の機能を別のクラスに定義して、FPSキャラのクラスにオブジェクトとして持たせてみました。
まず、Projectウィンドウで右クリックして、C#スクリプトを新規作成します。
MonoBehaviourクラスは継承しないようにします。
using UnityEngine;
public class Crouch
{
float crouchCameraHeight; // しゃがんだときの顔の高さ
float cameraHeight; // 現在の顔の高さ
// 現在の顔の高さを取得
public float GetCameraHeight()
{
return cameraHeight;
}
State[] states; // 状態の配列
int currentIndex; // 現在の状態のインデックス
// 初期化のメソッド
public void Init(float crouchCameraHeight = -0.8f)
{
this.crouchCameraHeight = crouchCameraHeight;
// 状態をインスタンス化して配列に入れる
states = new State[6];
states[0] = new Standing();
states[1] = new Down();
states[2] = new Crouching();
states[3] = new Up();
states[4] = new Locking();
states[5] = new Locked();
// 状態を変える
SetState(0);
}
// しゃがんでいるかどうかを取得
public bool GetIsCrouching()
{
return currentIndex == 2;
}
// 立っているかどうかを取得
public bool GetIsStanding()
{
return currentIndex == 0;
}
// 状態を変える
void SetState(int index)
{
if (states.Length > currentIndex && currentIndex >= 0)
{
currentIndex = index;
states[currentIndex].EnterState(this);
}
}
// 現在の状態を取得
public int GetState()
{
return currentIndex;
}
// しゃがみのロックを設定する
public void SetLock(bool locked = true)
{
if (locked)
{
SetState(4);
}
else
{
if (GetState() == 4)
{
SetState(3);
}
else if (GetState() == 5)
{
SetState(0);
}
}
}
// ロックされているかどうかを取得
public bool GetLocked()
{
return currentIndex == 4 || currentIndex == 5;
}
public void Update()
{
states[currentIndex].Update(this);
}
// 状態の基底クラス
class State
{
public virtual void Update(Crouch crouch) { }
public virtual void EnterState(Crouch crouch) { }
}
// 立っている状態
class Standing : State
{
public override void Update(Crouch crouch)
{
// キー入力でしゃがみはじめる
if (Input.GetKeyDown(KeyCode.C))
{
// 姿勢を下げている状態へ遷移
crouch.SetState(1);
}
}
}
// 姿勢を下げている状態
class Down : State
{
public override void Update(Crouch crouch)
{
// キー入力で立ち上がる状態へ遷移
if (Input.GetKeyDown(KeyCode.C))
{
crouch.SetState(3);
}
// キー入力が無いとき
else
{
// 設定した高さ以下になると
if (crouch.cameraHeight <= crouch.crouchCameraHeight)
{
// しゃがんでいる状態に遷移
crouch.cameraHeight = crouch.crouchCameraHeight;
crouch.SetState(2);
}
// 設定した高さより高いとき
else
{
// 現在の顔の高さを下げる
crouch.cameraHeight -= 0.05f;
}
}
}
}
// しゃがんでいる状態
class Crouching : State
{
public override void Update(Crouch crouch)
{
// キー入力で立ち上がる状態へ遷移
if (Input.GetKeyDown(KeyCode.C))
{
crouch.SetState(3);
}
}
}
// 立ち上がる状態
class Up : State
{
public override void Update(Crouch crouch)
{
// キー入力でしゃがみはじめる
if (Input.GetKeyDown(KeyCode.C))
{
crouch.SetState(1);
}
// キー入力が無いとき
else
{
// 顔の高さが0以上のとき
if (crouch.cameraHeight >= 0)
{
// 立っている状態へ遷移
crouch.cameraHeight = 0f;
crouch.SetState(0);
}
// 0未満のとき
else
{
// 顔の高さを上げる
crouch.cameraHeight += 0.08f;
}
}
}
}
// ロックするために立ち上がる状態
class Locking : State
{
public override void Update(Crouch crouch)
{
if (crouch.cameraHeight >= 0)
{
crouch.cameraHeight = 0f;
crouch.SetState(5); // ロック状態へ遷移
}
else
{
crouch.cameraHeight += 0.14f;
}
}
}
// しゃがみをロック中の状態
class Locked : State
{
// 何もしない
}
}
このクラスでは、FirstPersonControllerクラスで使うための、FPSカメラの高さを更新するための処理をします。なので、カメラの高さを取得するメソッドを公開しています。
float crouchCameraHeight; // しゃがんだときの顔の高さ
float cameraHeight; // 現在の顔の高さ
// 現在の顔の高さを取得
public float GetCameraHeight()
{
return cameraHeight;
}
しゃがむ機能には、立っている、姿勢を下げている、しゃがんでいる、立ち上がっている等の状態があるので、キャラの状態をオブジェクトにすると同様に、これらの状態を内部クラスのオブジェクトにしてみました。
なので、状態オブジェクトを入れる配列と、現在の状態のインデックスを定義しています。初期化のメソッドで、フィールドを初期化するときに、各状態オブジェクトをインスタンス化して配列に入れます。
State[] states; // 状態の配列
int currentIndex; // 現在の状態のインデックス
// 初期化のメソッド
public void Init(float crouchCameraHeight = -0.8f)
{
this.crouchCameraHeight = crouchCameraHeight;
// 状態をインスタンス化して配列に入れる
states = new State[6];
states[0] = new Standing();
states[1] = new Down();
states[2] = new Crouching();
states[3] = new Up();
states[4] = new Locking();
states[5] = new Locked();
// 状態を変える
SetState(0);
}
初期化はコンストラクタを使っても良いかもしれません。
各状態のクラスは、状態の基底クラスを継承しています。
// 状態の基底クラス
class State
{
public virtual void Update(Crouch crouch) { }
public virtual void EnterState(Crouch crouch) { }
}
// 立っている状態
class Standing : State
{
public override void Update(Crouch crouch)
{
// ...
}
基底クラスには、毎フレームの更新メソッドと、状態が切り替わったときに呼ばれるメソッドがあります。各状態のクラスでは必要なメソッドをオーバーライドします。
しゃがみクラスの、状態を変えるメソッドでは、引数の整数値を、現在のインデックスのフィールドに入れた後、状態を変えたときのメソッドを呼びます。
// 状態を変える
void SetState(int index)
{
if (states.Length > currentIndex && currentIndex >= 0)
{
currentIndex = index;
states[currentIndex].EnterState(this);
}
}
状態の基底クラスではこのメソッドに何も書かれていないので、各状態のクラスがこのメソッドをオーバーライドしていないときは何もしません。
FPSキャラのクラスで、今のしゃがみの状態によって歩く速さなどを変えるときのために、立っている状態かどうか等を確認するメソッドも公開しています。
// しゃがんでいるかどうかを取得
public bool GetIsCrouching()
{
return currentIndex == 2;
}
// 立っているかどうかを取得
public bool GetIsStanding()
{
return currentIndex == 0;
}
しゃがみがロックされている状態も作って、外部からロックをかけられるようにしています。ロックを解除するときに、現在の状態によって遷移させる状態を変えていますが、このときも整数値を確認するだけで簡単です。
// 現在の状態を取得
public int GetState()
{
return currentIndex;
}
// しゃがみのロックを設定する
public void SetLock(bool locked = true)
{
// ロックをかけるとき
if (locked)
{
// ロックをかけるために立ち上がる状態へ遷移
SetState(4);
}
// ロックを解除するとき
else
{
// 立ち上がっているときは、通常の立ち上がる状態へ遷移
if (GetState() == 4)
{
SetState(3);
}
// ロック中のときは立っている状態へ遷移
else if (GetState() == 5)
{
SetState(0);
}
}
}
// ロックされているかどうかを取得
public bool GetLocked()
{
return currentIndex == 4 || currentIndex == 5;
}
FirstPersonControllerクラスのUpdateメソッドの中で呼ぶためのメソッドもあります。この中で、現在の状態オブジェクトのメソッドを呼んでいます。状態が変われば処理も変わります。
public void Update()
{
states[currentIndex].Update(this);
}
状態のクラス
例えば、デフォルトの立っている状態では、更新メソッドのなかで、キー入力によってしゃがみはじめる処理だけをしています。
// 状態の基底クラス
class State
{
public virtual void Update(Crouch crouch) { }
public virtual void EnterState(Crouch crouch) { }
}
// 立っている状態
class Standing : State
{
public override void Update(Crouch crouch)
{
// キー入力でしゃがみはじめる
if (Input.GetKeyDown(KeyCode.C))
{
// 姿勢を下げている状態へ遷移
crouch.SetState(1);
}
}
}
キー入力で状態が遷移すると、カメラの高さを下げていき、設定した高さ以下になると、しゃがみ状態へ遷移する処理をします。その前にキー入力をすると立ち上がる状態へ遷移します。
// 姿勢を下げている状態
class Down : State
{
public override void Update(Crouch crouch)
{
// キー入力で立ち上がる状態へ遷移
if (Input.GetKeyDown(KeyCode.C))
{
crouch.SetState(3);
}
// キー入力が無いとき
else
{
// 設定した高さ以下になると
if (crouch.cameraHeight <= crouch.crouchCameraHeight)
{
// しゃがんでいる状態に遷移
crouch.cameraHeight = crouch.crouchCameraHeight;
crouch.SetState(2);
}
// 設定した高さより高いとき
else
{
// 現在の顔の高さを下げる
crouch.cameraHeight -= 0.05f;
}
}
}
}
同様に、しゃがんでいる状態ではキー入力で立ち上がりはじめ、立ち上がる状態ではカメラの高さを上げていきます。
// しゃがんでいる状態
class Crouching : State
{
public override void Update(Crouch crouch)
{
// キー入力で立ち上がる状態へ遷移
if (Input.GetKeyDown(KeyCode.C))
{
crouch.SetState(3);
}
}
}
// 立ち上がる状態
class Up : State
{
public override void Update(Crouch crouch)
{
// キー入力でしゃがみはじめる
if (Input.GetKeyDown(KeyCode.C))
{
crouch.SetState(1);
}
// キー入力が無いとき
else
{
// 顔の高さが0以上のとき
if (crouch.cameraHeight >= 0)
{
// 立っている状態へ遷移
crouch.cameraHeight = 0f;
crouch.SetState(0);
}
// 0未満のとき
else
{
// 顔の高さを上げる
crouch.cameraHeight += 0.08f;
}
}
}
}
しゃがみができないようにロックするメソッドが呼ばれたら、まず立ち上がります。カメラの高さがはじめの値まで戻ったら、ロック中の状態に遷移します。
ロック状態のクラスではどのメソッドもオーバーライドされていないので、キー入力でしゃがみはじめる等の処理をしません。
// ロックするために立ち上がる状態
class Locking : State
{
public override void Update(Crouch crouch)
{
if (crouch.cameraHeight >= 0)
{
crouch.cameraHeight = 0f;
crouch.SetState(5); // ロック状態へ遷移
}
else
{
crouch.cameraHeight += 0.14f;
}
}
}
// しゃがみをロック中の状態
class Locked : State
{
// 何もしない
}
FirstPersonController
FirstPersonControllerクラスでは、しゃがみクラスのオブジェクトを持っておきます。まずAwakeメソッドで初期化のメソッドを呼んでいます。
Crouch crouch = new Crouch();
private void Awake()
{
crouch.Init();
// ...
Updateメソッドでは、しゃがみオブジェクトの更新メソッドを呼びます。
// Update is called once per frame
private void Update()
{
crouch.Update();
// ...
}
また、FirstPersonControllerでは、FixedUpdateメソッドで呼ばれるUpdateCameraPositionメソッドの中で、頭のカメラのローカル位置を変えているので、その時に、カメラの位置の上下移動を適用します。
private void UpdateCameraPosition(float speed)
{
// ...
newCameraPosition.y = m_Camera.transform.localPosition.y /* ... */ + crouch.GetCameraHeight();
// ...
}
カメラ位置のローカル位置のY座標を変えるところが二箇所あるので、しゃがみオブジェクトのカメラ位置を取得するメソッドの値を両方に足しています。
他にも、FPSキャラの移動キー入力を取得したり、スピードを計算するメソッドがあるので、しゃがみオブジェクトの立っている状態か確認するメソッドを使って、立っていないときにスピードを下げるようにしました。
private void GetInput(out float speed)
{
// ...
speed = (m_IsWalking ? m_WalkSpeed : m_RunSpeed) * (crouch.GetIsStanding() ? 1f : 0.5f);
// ...
}
これで簡単にしゃがみ機能を追加できました。
同じ方法で、体を傾けたり、指定の位置回転へワープするような機能をオブジェクトにして持つようにすると、機能の追加や管理が簡単になりました。