色々なジャンプの処理を実装する

この記事ではUnityで複数のジャンプ方法を実装します。具体的には壁キック、2段ジャンプ、ヒップドロップを実装していきます。過去に書いた以下のURLの記事の続きから進めていきます。
https://qiita.com/holy_r_e9620202/items/dd4a21031295d7b88c08

# 壁キック
アクションゲームで時々見かける、壁の方向に向かってジャンプして、キャラクターが壁と接触した瞬間にタイミングよくジャンプボタンを押すと、壁を蹴って反対方向へジャンプする処理です。この壁キックを繰り返して2つの壁の間を登っていけるようにします。
Hierarchyウィンドウを右クリックして3D ObjectからCubeを2つ作ります。2つのCubeを壁として、サイズや間隔を調整して以下の画像のように配置します。
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644510/b1eb1e13-0dff-c430-3986-358703f5912b.png)
さらに、Inspectorビューから両方の壁のTagの名前をWallに設定しておきます。
壁を配置したらキャラクターにアタッチされているスクリプトPlayerController.csを以下のように書き変えます。
```C#:PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    public float moveSpeed;
    public float dashSpeed;
    public float jumpPower;
    private bool isWallJumping = false; //壁キック判定の変数
    private CharacterController controller;
    private Vector3 moveVelocity;

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        if (!isWallJumping) //壁キック中でない場合に操作を受け付ける
        {
            if (Input.GetKey(KeyCode.Z))
            {
                moveVelocity.x = Input.GetAxis("Horizontal") * dashSpeed;
                moveVelocity.z = Input.GetAxis("Vertical") * dashSpeed;
            }
            else
            {
                moveVelocity.x = Input.GetAxis("Horizontal") * moveSpeed;
                moveVelocity.z = Input.GetAxis("Vertical") * moveSpeed;
            }
        }

        transform.LookAt(this.transform.position + new Vector3(moveVelocity.x, 0, moveVelocity.z));

        if (controller.isGrounded) 
        {
            isWallJumping = false; //着地したら壁キック判定をfalseに戻す

            if (Input.GetKeyDown(KeyCode.Space))
            {
                moveVelocity.y = jumpPower;
            }
        }
        else
        {
            moveVelocity.y += Physics.gravity.y * Time.deltaTime;
        }

        controller.Move(moveVelocity * Time.deltaTime);
    }

    //壁キックの処理
    void OnControllerColliderHit(ControllerColliderHit collider)
    {
        if (collider.gameObject.tag == "Wall" && !controller.isGrounded) //壁に接触していてかつ地面に設置していない場合
        {
            if (Input.GetKeyDown(KeyCode.Space)) //スペースキー入力で壁キック実行
            {
                var direction = transform.forward; //キャラクターの向きを取得
                isWallJumping = true; //壁キック判定をtrueにする
                moveVelocity.x = -direction.x * 5; //壁とはx軸逆向きに移動
                moveVelocity.y = jumpPower;        //y軸方向はジャンプの場合と同じように移動
                moveVelocity.z = -direction.z * 5; //壁とはz軸逆向きに移動
            }
        }
    }
}
```
実行すると、壁に向かってジャンプして当たった時にスペースキーを押すと、キャラクターが壁を蹴ったように壁と反対方向にジャンプします。壁キックを繰り返すと2つの壁の間を登っていくことが出来るので確認してください。

# 2段ジャンプ
空中でもう1回ジャンプできるやつです。
jumpCountという変数を追加してジャンプした回数を管理することによって、空中で1回だけジャンプできるようにしました。PlayerController.csを以下のように修正しました。
```C#:PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    public float moveSpeed;
    public float dashSpeed;
    public float jumpPower;
    private bool isWallJumping = false;
    private CharacterController controller;
    private Vector3 moveVelocity;
    private byte jumpCount = 0; //ジャンプした回数

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        if (!isWallJumping)
        {
            if (Input.GetKey(KeyCode.Z))
            {
                moveVelocity.x = Input.GetAxis("Horizontal") * dashSpeed;
                moveVelocity.z = Input.GetAxis("Vertical") * dashSpeed;
            }
            else
            {
                moveVelocity.x = Input.GetAxis("Horizontal") * moveSpeed;
                moveVelocity.z = Input.GetAxis("Vertical") * moveSpeed;
            }
        }

        transform.LookAt(this.transform.position + new Vector3(moveVelocity.x, 0, moveVelocity.z));

        if (controller.isGrounded) 
        {
            isWallJumping = false;
            jumpCount = 0; //着地したら0に戻す

            if (Input.GetKeyDown(KeyCode.Space))
            {
                moveVelocity.y = jumpPower;
                jumpCount++; //ジャンプしたら1増やす
            }
        }
        else
        {
            if (jumpCount < 2) //接地していなくてもジャンプ回数が2より小さい場合
            {
                if (Input.GetKeyDown(KeyCode.Space))
                {
                    moveVelocity.y = jumpPower;
                    jumpCount++; //ジャンプしたら1増やす
                }
                else
                {
                    moveVelocity.y += Physics.gravity.y * Time.deltaTime;
                }
            }
            else
            {
                moveVelocity.y += Physics.gravity.y * Time.deltaTime;
            }
        }

        controller.Move(moveVelocity * Time.deltaTime);
    }

    void OnControllerColliderHit(ControllerColliderHit collider)
    {
        if (collider.gameObject.tag == "Wall" && !controller.isGrounded)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                var direction = transform.forward;
                isWallJumping = true;
                moveVelocity.x = -direction.x * 5;
                moveVelocity.y = jumpPower;
                moveVelocity.z = -direction.z * 5;
                jumpCount++; //壁キックしたら1増やす
            }
        }
    }
}
```
修正出来たらジャンプして空中でもう1回スペースキーを押してジャンプできることを確かめてください。

# ヒップドロップ
次にヒップドロップを実装します。キャラクターを勢いよく落下させて敵とかブロックを踏み潰せるあれです。PlayerController.csを以下のように書き変えて実装しました。
```C#:PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    public float moveSpeed;
    public float dashSpeed;
    public float jumpPower;
    private bool isWallJumping = false;
    private bool isHipdrop = false; //ヒップドロップ判定
    private CharacterController controller;
    private Vector3 moveVelocity;
    private byte jumpCount = 0;

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        if (!isWallJumping)
        {
            if (Input.GetKey(KeyCode.Z))
            {
                moveVelocity.x = Input.GetAxis("Horizontal") * dashSpeed;
                moveVelocity.z = Input.GetAxis("Vertical") * dashSpeed;
            }
            else
            {
                moveVelocity.x = Input.GetAxis("Horizontal") * moveSpeed;
                moveVelocity.z = Input.GetAxis("Vertical") * moveSpeed;
            }
        }

        transform.LookAt(this.transform.position + new Vector3(moveVelocity.x, 0, moveVelocity.z));

        if (controller.isGrounded) 
        {
            isWallJumping = false;
            isHipdrop = false; //着地したらヒップドロップ判定をfalseに戻す
            jumpCount = 0;

            if (Input.GetKeyDown(KeyCode.Space))
            {
                moveVelocity.y = jumpPower;
                jumpCount++;
            }
        }
        else
        {
            if (jumpCount < 2)
            {
                if (Input.GetKeyDown(KeyCode.Space))
                {
                    moveVelocity.y = jumpPower;
                    jumpCount++;
                }
                else
                {
                    moveVelocity.y += Physics.gravity.y * Time.deltaTime;
                }
            }
            else
            {
                moveVelocity.y += Physics.gravity.y * Time.deltaTime;
            }

            //Bキーを押すとヒップドロップ実行
            if (Input.GetKeyDown(KeyCode.B))
            {
                Hipdrop();
            }
        }

        controller.Move(moveVelocity * Time.deltaTime);
    }

    void OnControllerColliderHit(ControllerColliderHit collider)
    {
        if (collider.gameObject.tag == "Wall" && !controller.isGrounded)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                var direction = transform.forward;
                isWallJumping = true;
                moveVelocity.x = -direction.x * 5;
                moveVelocity.y = jumpPower;
                moveVelocity.z = -direction.z * 5;
                jumpCount++;
            }
        }

        //ヒップドロップ中にブロックと衝突した場合
        if (collider.gameObject.tag == "Block" && isHipdrop)
        {
            Destroy(collider.gameObject); //衝突したオブジェクトを消す
        }
    }

    void Hipdrop()
    {
        isHipdrop = true; //ヒップドロップ判定をtrueにする
        moveVelocity.y += Physics.gravity.y * Time.deltaTime * 100; //落下スピードを通常時よりも早くする
    }
}
```
これでキャラクターがヒップドロップできるようになりました。空中でBキーを押すとヒップドロップが実行され、普段より落下速度が速くなります。またTagの名前をBlockに設定したオブジェクトを設置して、そのオブジェクトに対してヒップドロップを当ててみてください。Blockオブジェクトが消滅すれば成功です。

# 参考文献
https://papyrustaro.hatenablog.jp/entry/2020/06/17/121100
https://futabazemi.net/unity/jump-wall-up