Last Updated 2024.12.13
『バックステップ』『バックダッシュ』等、呼び方は色々ありますが──
格闘ゲームではお馴染みの、後ろに素早く二回キーを入力したら、一気に後退する機能をUnityで実装してみます。 ※改修中のモデルが素体の状態でスミマセン。後日修正予定です。
宙返りモーションを作る
モーションに関してはblenderで自作します。
(現在のプロジェクトの)後方移動は30∼34フレームを目安に作っているので、それを基準に作成します。通常移動よりは早いほうがいいので、今回は68フレーム以内に収めました。
※画像だと総フレームが76になってますが、実際に使用するフレーム数は67です
今回の作業時間は手打ちで8~9時間ほど。
横の移動値に関してはUnity側の物理演算で動かすので設定していません。
モーションがある程度カタチになったら、一旦エクスポートします。
バックステップ用のコードを作る
キー入力受付と一連の流れをスクリプトで作成します。
今回は後ろ方向に素早く2回入力で受け付け開始。キーをそのまま入れっぱなしだと2回目以降の後退モーションが変化し、通常より素早く距離をあけられる──といった仕様で実装していきます。
普通の格ゲーと微妙に違う挙動ですが、意図した設計なのでご了承下さい。
※操作サイドやプレイヤー情報を格納している《GameManager》スクリプトや、Animatorに設定しているParameters名が独自のものなので、そのままコピペしても使用できません。その辺の解説まで含めるとかなりの追記が必要になるので、この記事内では省略させて頂きます。
using UnityEngine;
using UnityEngine.InputSystem;// ※InputSystemを使用
// 前後のキー入力、移動に関するスクリプトのサンプル
public class InputMove : MonoBehaviour
// 方向キーの入力関係
private string keyDirection;// 押したキーの方向
private int inputCount;// 方向キーが押された回数
private int timeCount;// 1回目の方向キーが押されてからのフレーム数
[SerializeField]
private int receptionFrame = 22;// キー受付の猶予フレーム
private bool quickInput;// キーの2回入力が成立しているかのチェック
// バックステップ関連
[SerializeField]
private bool backStep;// バックステップのフラグ
[SerializeField]
private float backStepTransitionTime = 0.74f;// バックステップに推移するタイミング
[SerializeField]
[Range(0, 3)]// intを0∼3に制限
private int backStepMove;// バックステップの推移段階
[SerializeField]
private float backStepVelocity1st = -5.0f;// 1回目の速度係数
[SerializeField]
private float backStepVelocity2nd = -4.6f;// 2回目の速度係数
// Animator関連
private Animator animator;
private float normalizedtime = 0;
// コライダーとリジッドボディ
private CharacterController characterController;
private Rigidbody rb;
// 操作側の判定に使用。※独自スプリクト
private GameManager gameManager;
void Start()
{
animator = GetComponent();
characterController = GetComponent();
rb = GetComponent();
// 操作側やプレイヤー情報に関するスクリプトを習得
gameManager = GameObject.Find("Canvas").GetComponent();
}
void Update()
{
// 後ろ移動
if (animator.GetCurrentAnimatorStateInfo(0).IsName("BackStep"))
{
normalizedtime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
if (0.96 <= normalizedtime)
{
animator.applyRootMotion = false;
return;
}
// バックステップの受け付け
// backStepTransitionTimeの値を超えたら判定する
else if (backStepTransitionTime <= normalizedtime && backStep)
{
if (animator.GetBool("BackStep"))// キーが入れっぱなしの状態なら
{
// バックステップに移行
animator.SetBool("BackStep2", true);// バックステップ2のフラグを入れる
backStep = false;// フラグのboolを切る
animator.applyRootMotion = false;// Animatorの移動値を切る
}
}
else if (normalizedtime > 0)
{
animator.applyRootMotion = true;
}
if (backStep && !animator.GetBool("BackStep"))
{
backStep = false;// フラグのboolを切る
}
}
// Animatorのバックステップboolの処理
if (animator.GetBool("BackStep2"))
{
if (!animator.GetBool("BackStep"))// キー入力が離れたら
{
animator.SetBool("BackStep2", false);// Animatorのboolを解除
}
}
// バックステップモーションの処理
if (animator.GetCurrentAnimatorStateInfo(0).IsName("BackStep2"))
{
normalizedtime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
if (normalizedtime >= 1)// モーション終了時
{
// boolを解除
animator.SetBool("BackStep2", false);
backStepMove = 0;
}
// 移動一段階目
else if (backStepMove == 0 && normalizedtime >= 0.06)
{
// 物理で後方に飛ばす
rb.AddForce(gameManager.SidePlayer1.transform.forward * backStepVelocity1st, ForceMode.VelocityChange);
backStepMove = 1;
}
// 移動二段階目
else if (backStepMove == 1 && normalizedtime >= 0.52)
{
rb.AddForce(gameManager.SidePlayer1.transform.forward * backStepVelocity2nd, ForceMode.VelocityChange);
backStepMove = 2;
}
// 着地時の処理
else if (backStepMove == 2 && normalizedtime >= 0.86)
{
// 接地した瞬間に逆側から少し力を加える(滑りの抑制)
rb.AddForce(gameManager.SidePlayer1.transform.forward * 1.8f, ForceMode.VelocityChange);
Debug.Log("着地音を鳴らす");// ※暫定。SEの処理はここ
backStepMove = 3;
}
}
}
// 前入力に関するメソッド(InputSystemを使用)
public void OnForward(InputAction.CallbackContext context)
{
if (context.started && inputCount == 1)
{
quickInput = true;
if (keyDirection == "Forward")
{
inputCount++;
}
else if (keyDirection == "Back")
{
inputCount = 0;
}
}
if (gameManager._1POperatingSide == GameManager.operatingSide._1P)// 1P側なら
{
if (context.performed) FrontStep();// 前進メソッドヘ
if (context.canceled) animator.SetBool("FrontStep", false);
}
else //2P側なら
{
if (context.performed) BackStep();// 後退メソッドヘ
if (context.canceled) animator.SetBool("BackStep", false);
}
}
// 後ろ入力に関するメソッド(InputSystemを使用)
public void OnBackward(InputAction.CallbackContext context)
{
if (context.started && InputCount == 1)
{
quickInput = true;
if (keyDirection == "Forward")
{
inputCount++;
}
else if (keyDirection == "Back")
{
inputCount = 0;
}
}
if (gameManager._1POperatingSide == GameManager.operatingSide._1P)//1P側なら
{
if (context.performed) BackStep();
if (context.canceled) animator.SetBool("BackStep", false);
}
else //2P側なら前進
{
if (context.performed) FrontStep();
if (context.canceled) animator.SetBool("FrontStep", false);
}
}
// 前進メソッド
private void FrontStep()
{
// ※本題ではないので今回は省略
}
// 後退メソッド
private void BackStep()
{
if (inputCount == 0)
{
inputCount++;
keyDirection = "Back";
timeCount = 0;
}
else if (keyDirection != "Back")
{
inputCount = 1;
keyDirection = "Back";
}
// 既に後退モーション中で
if (animator.GetCurrentAnimatorStateInfo(0).IsName("BackStep"))
{
if (quickInput)// 素早く二回入力されていたら
{
Debug.Log("バックステップ");
backStep = true;// フラグを入れる
}
}
// 通常の後退モーション開始
animator.SetBool("BackStep", true);
}
private void FixedUpdate()
{
//素早く2回入力用のタイマー
if (inputCount == 1)
{
timeCount++;
if (timeCount > receptionFrame)//受付時間を過ぎたらカウントを初期化
{
inputCount = 0;
}
}
}
}前移動に関しては、今回の本題ではないのでオミット。
[quickInput] と [backStep] のboolは、一見役割が重複してるように見えますが、quickInputの方はオミットした前ダッシュや特殊行動などに関わっており、backStepはあくまでバックステップのフラグ立てのみに使用されます。
1P側と2P側でキー受付の入れ替えがあるので、『前移動』と『後ろ移動』のメソッドを個別に設定。モーションに関する細かい処理をUpdate内に記入し、方向キーを素早く2回入力の受け取りに関しては、Fixed内で処理しています。
原則、物理演算を扱う場合はFixedUpdate内に記入しますが(Update内だとハードの性能によって結果が変わってしまうため)、Animator内のリアルタイムに変化する厳密な値を参照する処理は、なるべくUpdate内に記入したいのでこうなっています。
AddForce()内のフォースモードに《VelocityChange》を使えば、この問題は解決できる筈です。
Animator Parametersのbool関係が把握しづらいので注釈を入れると
- 通常の後退モーション時は 《BuckStep》 boolを使用
- バックステップ入力が確認されたら 《BuckStep2》 boolを起動
どちらも入ってる状態になったら、バックステップのモーションに遷移します。
※現在の仕様だと、通常の前後の移動はAnimatorの移動値(applyRootMotion)で処理していますが、今後、物理演算で動かす仕様に変更する予定なので、もう少し最適化できればと思います。
動作確認
Animator SpeedやAddForceに使う係数や抵抗値など、いい感じになるまで数字調整してたらバックステップしてるだけで数時間溶けていきましたが……
個人的には、かなり意図に近い挙動に仕上がってくれました。
おわりに
バックステップの出だしに打撃や投げ無敵とか、バックステップ中は空中判定だとか、そういった細かい仕様も挑戦はしたいのですが、今回はここまでになります。
