Unityでのゲーム開発 〜落下処理、着地処理とおまけ編〜
今回の実装は前回落下時のMotion中に空中を歩いてしまう状態だった為、そちらの変更と着地時のMotionを設定したいと思います。
落下モーションの設定
早速Animatorを設定していきます。
- FallのStateを新規で作成
- Locomotion⇨FallにMake Transitionで繋ぐ
- FallStateのMotion(Jump_Up_A_Loop)に設定
- Parametersの設定にBool型を作成して名前を「Fall」に変更
- Transitionの設定にHas Exit Timeのチェックを外す
- Intrruption Sourceを「Next State」に変更
- ConditionsをFallを選択してtrueに変更
着地モーションの設定
- LandingのStateを新規で作成
- Fall⇨LandingにMake Transitionで繋ぐ
- LandingStateのMotion(Land_Spawn_Wait)に設定
- Foot IKにチェックを入れる
- Parametersの設定にBool型を作成して名前を「Landing」に変更
- Transitionの設定にHas Exit Timeのチェックを外す
- Intrruption Sourceを「Next State」に変更
- ConditionsをLandingを選択してtrueに変更
- Landing⇨ExitにMake Transitionで繋ぐ
- Transitionの設定にHas Exit Timeのチェックをしたままにする
- Intrruption Sourceを「Next State」に変更
- ConditionsをLandingを選択してfalseに変更
- Landingスクリプトを取り付ける(下に記述する物)
Landing用のビヘイビアを作成
StateMachineのビヘイビアを作成してLandingに貼り付ける。
これをつける理由としては着地アニメーションをある程度再生してからLocomotionに派生させたいからです
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Landing : StateMachineBehaviour { override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // 動かしていない時はある程度再生してからLandingをfalseにする if (animator.GetCurrentAnimatorStateInfo (0).normalizedTime >= 0.9f) { animator.SetBool ("Landing", false); } } }
normalizedTime >= 0.9f・・・normalizedTimeはアニメーション開始時を0・再生後を1とするようにアニメーションの長さを正規化したものな為、今回の設定ではアニメーションが9割再生されたらLandingを終了させる様な設定になっています。
⇩参考記事
Unity5 MecanimのStateMachineBehaviourと戯れる - テラシュールブログ
Stateビヘイビアの適用
Stateの適用の仕方は上記の様に付けたいStateを選択して[Add Behaviour]からScriptを選択すれば適用可能です。
スクリプトの変更
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { [SerializeField] private GameObject aimCamera; // GameObjectのエイムカメラを紐づけるField [SerializeField] private GameObject slideCamera; // GameObjectのスライドカメラを紐づけるField [SerializeField] private GameObject thirdPersonCamera; // GameObject三人称視点カメラを紐づけるField private float Horizontal; // ←・→、Aキー・Dキーの移動キー private float Vertical; // ↑・↓、Wキー・Sキーの移動キー private Animator animator; // animatorの取得 private Rigidbody rd; // rigidbodyの取得 private Vector3 velocity; // 移動速度 private Vector3 input; // 入力値を格納する private Vector3 moveForward; // 移動方向の単位ベクトル [SerializeField] private float walkSpeed = 4f; // 歩く速さ [SerializeField] private float jumpPower = 5f; // ジャンプ力 [SerializeField] private bool isGrounded; // 地面に接地しているかどうか [SerializeField] private Transform landRay; // 地面との距離を測るレイを飛ばす位置 [SerializeField] public float groundDistance = 1f; // 地面から離れたと検知する距離 [SerializeField] public float landDistance = 1f; // 着地モーションに移行する距離 [SerializeField] private Transform stepRay; // 段差を昇る為のレイを飛ばす位置 [SerializeField] private float stepDistance = 0.5f; // レイを飛ばす距離 [SerializeField] private float stepOffset = 0.3f; // 昇れる段差 [SerializeField] private float slopeLimit = 65f; // 昇れる角度 [SerializeField] private float slopeDistance = 1f; //昇れる段差の位置から飛ばすレイの距離 int layerMask; // Playerレイヤー以外のレイヤーマスク /// <summary> /// 開始時に呼び出される物 /// </summary> void Start() { animator = GetComponent<Animator>(); // Animatorコンポーネントの取得 rd = GetComponent<Rigidbody>(); // Rigidbodyコンポーネントの取得 layerMask = ~(1 << LayerMask.NameToLayer("Player")); } /// <summary> /// 毎フレーム呼び出される物 /// </summary> void Update() { Horizontal = Input.GetAxisRaw("Horizontal"); Vertical = Input.GetAxisRaw("Vertical"); /// <summary> キー入力でカメラ切り替え </summary> if (Input.GetKeyDown(KeyCode.B)) { aimCamera.SetActive(!aimCamera.activeInHierarchy); thirdPersonCamera.SetActive(!thirdPersonCamera.activeInHierarchy); } /// <summary> キャラクターが接地している場合の処理 </summary> if (isGrounded) { // 接地したので移動速度を0にする animator.SetBool ("Fall", false); velocity = Vector3.zero; input = new Vector3(Horizontal, 0f, Vertical); /// <remarks> 方向キーが押されている時 </remarks> if (input.magnitude > 0f) { // キーを押されている間のAnimator(parameter)の速度の変動によってのMotion変化 var speed = Mathf.Abs(input.magnitude * 2.5f); animator.SetFloat("Speed", speed, 2f, Time.deltaTime); /// <summary> キャラクターがスライディングした時の処理 </summary> if (Input.GetKeyDown(KeyCode.C)) { slideCamera.SetActive(!slideCamera.activeInHierarchy); thirdPersonCamera.SetActive(!thirdPersonCamera.activeInHierarchy); /// <remarks> Cキーが押された時にカメラを切り替えてanimatorを起動する </remarks> if(slideCamera.activeInHierarchy) { animator.SetBool("Slide", true); } else if(!slideCamera.activeInHierarchy){ animator.SetBool("Slide", false); } } /// <summary> ステップ用のレイが地面に接触しているか検知して処理を行う </summary> // コンソールに昇れる段差を表示 Debug.DrawLine(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, Color.green); /// <remarks> 進行方向の地面の角度が指定以下、または昇れる段差より下だった場合の移動処理 </remarks> if (Physics.Linecast(stepRay.position, stepRay.position + stepRay.forward * stepDistance, out var stepHit, LayerMask.GetMask("Field", "Block"))) { if (Vector3.Angle(transform.up, stepHit.normal) <= slopeLimit || (Vector3.Angle(transform.up, stepHit.normal) > slopeLimit && !Physics.Linecast(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, LayerMask.GetMask("Field", "Block")))) { velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * transform.forward * walkSpeed).y, 0f) + transform.forward * walkSpeed; Debug.Log(Vector3.Angle(transform.up, stepHit.normal)); } else { velocity += transform.forward * walkSpeed; } Debug.Log(Vector3.Angle(Vector3.up, stepHit.normal)); /// <remarks> 地面に接している状態でのaimCamera(active、非active)での移動 </remarks> } else if (!aimCamera.activeInHierarchy){ velocity += transform.forward * walkSpeed; } else if (aimCamera.activeInHierarchy){ Vector3 lookForwad = aimCamera.transform.forward; lookForwad.y = 0f; transform.rotation = Quaternion.LookRotation(moveForward); velocity += input * 2f; } /// <remarks> キーを押していない場合は移動しない </remarks> } else { animator.SetFloat("Speed", 0f); } /// <summary> ジャンプの処理 </summary> if (Input.GetButtonDown("Jump") && !animator.GetCurrentAnimatorStateInfo(0).IsName("Jump") && !animator.IsInTransition(0)) // 遷移途中にジャンプさせない条件 { animator.SetBool("Jump", true); isGrounded = false; // ジャンプしたら接地していない状態にする velocity.y += jumpPower; } } } /// <summary> /// 固定フレーム呼び出される物 /// </summary> void FixedUpdate() { // キャラクターを移動させる処理 rd.MovePosition(transform.position + velocity * Time.fixedDeltaTime); // カメラの方向から、X-Z平面の単位ベクトルを取得 Vector3 cameraForward = Vector3.Scale(thirdPersonCamera.transform.forward, new Vector3(1, 0, 1)).normalized; // 方向キーの入力値とカメラの向きから、移動方向を決定 Vector3 moveForward = cameraForward * Vertical + thirdPersonCamera.transform.right * Horizontal; // 移動方向にスピードを掛ける。ジャンプや落下がある場合は、別途Y軸方向の速度ベクトルを足す。 velocity = moveForward * walkSpeed + new Vector3(0, velocity.y, 0); // キャラクターの向きを進行方向にする if (moveForward != Vector3.zero) { transform.rotation = Quaternion.LookRotation(moveForward); } } private void OnCollisionEnter(Collision collision) { /// <summary> /// 地面に着地した時の処理 /// </summary> Debug.DrawLine(landRay.position, landRay.position + Vector3.down * landDistance, Color.blue); if (Physics.Raycast (landRay.position, landRay.position + Vector3.down * landDistance, LayerMask.GetMask ("Field","Block"))) { isGrounded = true; animator.SetBool ("Jump", false); velocity.y = 0f; animator.SetBool ("Landing", true); animator.SetBool ("Fall", false); } } private void OnCollisionExit(Collision collision) { /// <summary> /// 地面から降りた時の処理 /// </summary> if (1 << collision.gameObject.layer != layerMask) // Fieldレイヤーのゲームオブジェクトから離れた時 { Debug.DrawLine(landRay.position, landRay.position + Vector3.down * groundDistance, Color.red); /// <remarks>下向きにレイヤーを飛ばしFieldレイヤーと接触しなければ地面から離れたと判定する </remarks> if (!Physics.Linecast(transform.position + Vector3.up * 0.2f, transform.position + Vector3.down * 0.1f, LayerMask.GetMask("Field", "Block"))) { isGrounded = false; animator.SetBool ("Fall", true); animator.SetBool ("Landing", false); } } } }
追記部分
[SerializeField] private Transform landRay; // 地面との距離を測るレイを飛ばす位置 [SerializeField] public float groundDistance = 1f; // 地面から離れたと検知する距離 [SerializeField] public float landDistance = 1f; // 着地モーションに移行する距離
これは必須ではないのですが「Debug.DrawLine」で可視化する際に使用しています。
landRayは前の記事で作成したstepRayのポイントをコピーして名前を変えただけになります。
地面から離れた時の処理
private void OnCollisionExit(Collision collision) { /// <summary> /// 地面から降りた時の処理 /// </summary> if (1 << collision.gameObject.layer != layerMask) // Fieldレイヤーのゲームオブジェクトから離れた時 { Debug.DrawLine(landRay.position, landRay.position + Vector3.down * groundDistance, Color.red); /// <remarks>下向きにレイヤーを飛ばしFieldレイヤーと接触しなければ地面から離れたと判定する </remarks> if (!Physics.Linecast(transform.position + Vector3.up * 0.2f, transform.position + Vector3.down * 0.1f, LayerMask.GetMask("Field", "Block"))) { isGrounded = false; animator.SetBool ("Fall", true); animator.SetBool ("Landing", false); } } }
- 地面と離れたらisGroundedをオフにする
- AnimatorのFallをオンにしてアニメーションを再生させる
- AnimatorのLandingをオフにする(着地時にオンにさせる為)
地面に着地した時の処理
private void OnCollisionEnter(Collision collision) { /// <summary> /// 地面に着地した時の処理 /// </summary> Debug.DrawLine(landRay.position, landRay.position + Vector3.down * landDistance, Color.blue); if (Physics.Raycast (landRay.position, landRay.position + Vector3.down * landDistance, LayerMask.GetMask ("Field","Block"))) { isGrounded = true; animator.SetBool ("Jump", false); velocity.y = 0f; animator.SetBool ("Landing", true); animator.SetBool ("Fall", false); } }
- 地面に接触したらisGroundedをオンにする
- AnimatorのLandingをオンにしてアニメーションを再生させる
- AnimatorのFallをオフにする
出来上がったのがこちら⇩⇩⇩
ん?キャラが変わってるって(´-`).。oO気のせいです
キャラメイク画面とか作った時に使えそうだったので購入しました。
assetstore.unity.com
私が購入した時は9$くらいだったんですがセールの対象とかになってたのかな?
AssetStoreも定期的にチェックした方が良いですね:(;゙゚'ω゚'):
おまけ
スライディングを実装してみました・・・上のScriptでも記載はしていますがCキーを押してON・OFFの切り替えと共にスライディング用のカメラに切り換える処理をしているのですが、これは後々の実装で使うかどうか迷っているので本格的に必要になったらいじろうと思います。
カメラのTransformはこちら⇩