【Unity】打ち上げ攻撃→空中コンボ攻撃を実装

##はじめに
初めてQiitaに記事を投稿します。

現在Unityで個人開発している2.5Dのアクションゲームで打ち上げ攻撃からの空中コンボという
ハイスピードなアクションゲームによくみられる挙動を実装したのですが、
実装するにあたって海外の記事や動画でもそれらしい情報がほとんど見つけられず
苦戦したのでこの度自分なりの実装方法を記事として残すことにしました。

本記事ではRigidBodyを用いてキャラクターを操作する方法を用いています。
CharacterControllerを使った場合でも応用は効くかと思います。
アニメーションの再生やアニメーターを使ったアニメーションの繋ぎ方等は省略しておりますので
下記のサイトを参考にしていただければと思います。
[Unityのアクションゲームで連続攻撃を実現する方法]（https://gametukurikata.com/program/continuityattack）

それでは早速本題に入っていきましょう！

#目次
・打ち上げ攻撃の実装
・空中コンボの制御
・最後に

#打ち上げ攻撃の実装
まずは一連の動作の起点となる打ち上げ攻撃の実装になります。
これがなきゃ始まりません。

と言ってもこの時点では複雑なことはなく、打ち上げ攻撃のアニメ―ションイベントで
操作キャラと敵を打ち上げる関数を呼び出すだけです。
打ち上げ動作に移るイイ感じのフレームで呼び出してあげましょう。
（アニメーションイベントに関してはこちらを）
（https://docs.unity3d.com/ja/2018.4/Manual/animeditor-AnimationEvents.html）

以下、打ち上げ用の関数です。Dotweenを用いています。
打ち上げる高さや速度に関してはゲームバランスにうまくハマるように各々調整していただければと思います。

```C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;


public class MeleeAttackManager : MonoBehaviour
    {
        Rigidbody rb;

        void Start()
        {
            rb = GetComponent<Rigidbody>();
        }
        
        void Launch()　//打ち上げ用の関数
        {
            rb.DOMoveY(7f, 0.5f);　//Y方向に0.5秒かけて上昇
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().Launch();　//攻撃を受けた敵を打ち上げます。
            }
        }
```

同様に敵キャラにも打ち上げ用の関数の書かれたスクリプトをアタッチしましょう。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EnemyScript : MonoBehaviour
{
    Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
    }


    public void Launch()
    {
        rb.DOMoveY(7f, 0.5f);　//攻撃を受けた敵を打ち上げます。
    }
}
```

打ちあがる高さと時間に関してはプレイヤー側と同じ数値に揃えましょう。

これで打ち上げ処理は実装できました！
ところがこのままでは。。。
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/903352/45efc54b-8bc3-4442-5493-57ae04c88c17.gif)
空中でコンボを決めようにも落下してしまっています。
カッコよさもへったくれもありません。
なので次は空中コンボの制御をしていきましょう。

#空中コンボの制御
打ち上げ攻撃を実装できましたがこのままでは空中コンボに落下してしまいみっともないです。
ケレン味のあるアクションを実現するためにも重力に逆らってもらいましょう。

方法としては
打ち上げ→RigidBodyのUseGravityをオフにしたり同じくRigidBodyのDragの数値を弄ることで一定時間空中に留まる→空中コンボが敵に当たれば滞空時間を延長→攻撃が当たらなかったりコンボが途切れると落下を始める

という実装方法に落ち着きました。
今回はDragの値を弄ることにします。
早速詳しくやっていきましょう。

まずは滞空するためにDragの値を弄る関数を用意してこの関数を打ち上げ用の関数実行時に呼び出します。

```C#
　　public void OffGrvity()
        {
            rb.drag = 40;　//RigidBodyのDragの数値を弄る
        }

    void Launch()
        {
            OffGrvity();　//OffGrvityを実行
            rb.DOMoveY(7f, 0.5f);
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().Launch();　//敵を打ち上げる
                enemy.GetComponent<EnemyScript>().OffGrvity();　//敵側のOffGravityを実行
            }
        }
```
Dragの値は40にして殆ど落下しないようになっています。
同様に敵側にも

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EnemyScript : MonoBehaviour
{
    Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
    }
　　
　　public void OffGrvity()
    {
        rb.drag = 40;　//RigidBodyのDragの数値を弄る
    }

    public void Launch()
    {
        rb.DOMoveY(7f, 0.5f);　//攻撃を受けた敵を打ち上げます。
    }
}
```
Dragの値を弄る関数を追加して攻撃を受けた際に実行されるようにします。
これで落下せずに空中コンボを決められますがこのままでは宙に浮いたまま降りてこないので

```C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;


public class MeleeAttackManager : MonoBehaviour
{
        Rigidbody rb;
        private float airStayTime = 1;　//滞空可能時間

        void Start()
        {
            rb = GetComponent<Rigidbody>();
        }
        
　　　　void Update()
        {

            if (rb.drag != 0)
            {
                airStayTime -= Time.deltaTime;　//Dragの値が0でなければairStayTimeの値を減らしていく
            }

            if (airStayTime < 0)
            {
                OnGrvity();　　//落下
                airStayTime = 1;　//airStayTimeの値をリセット
            }
        }

　　　　public void OnGrvity()
        {
            if(rb.drag != 0)
            {
                rb.drag = 0;　//Dragの値を0に戻して落下
            }
        }

　　　　public void OffGrvity()
        {
            rb.drag = 40;　//RigidBodyのDragの数値を弄る
        }

        void Launch()　//打ち上げ用の関数
        {
            rb.DOMoveY(7f, 0.5f);　//Y方向に0.5秒かけて上昇
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().Launch();　//攻撃を受けた敵を打ち上げます。
            }
        }

        public void airStayExtend()　//滞空時間を延長
        {
            Collider[] hitEnemies = Physics.OverlapSphere(AttackPoint.position, AttackRange, enemyLayers); //コライダー出現
            foreach (Collider enemy in hitEnemies)
            {
                enemy.GetComponent<EnemyScript>().airStay();　//敵のairStayTimeの値を更新して滞空時間を延長
            }
            airStayTime= 0.7f; //airStayTimeの値を更新して滞空時間を延長
        }
}
```
変数airStayTimeを新たに用意します。
Dragの値が0でなかった場合Update内でairStayTimeの値を減らし、
airStayTimeの値が0を下回った時点でOnGravity関数を実行して落下しています。
さらに、空中で攻撃を当てるたびにairStayTimeの値を増やしていかないと
コンボ中にやっぱり落下してしまうので
airStayExtendという関数を用意しています。
このairStayExtendを空中コンボのアニメーションイベントで実行することで
敵に攻撃を当てた際に滞空時間を延長しています。

例によって例の如く敵のスクリプトにも同じ処理を

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EnemyScript : MonoBehaviour
{
    Rigidbody rb;
　　private float airStayTime = 1;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
　　　　if(rb.drag != 0)
        {
            airStayTime -= Time.deltaTime;
        }

        if(airStayTime < 0)
        {
            OnGrvity();
            airStayTime = 1;
        }
    }

　　public void OnGrvity()
        {
            if(rb.drag != 0)
            {
                rb.drag = 0;　//Dragの値を0に戻して落下
            }
        }
　　
　　public void OffGrvity()
    {
        rb.drag = 40;　//RigidBodyのDragの数値を弄る
    }

    public void Launch()
    {
        rb.DOMoveY(7f, 0.5f);　//攻撃を受けた敵を打ち上げます。
    }

　　public void airStayExtend()
    {
        airStayTime = 0.7f;　//滞空時間を延長
    }
}
```
これで空中コンボは完成です！

![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/903352/b0818a15-5dcd-40d6-fd85-a20df76b38a2.gif)

#最後に

突貫工事で実装をしたので粗がある気はしますが、また改善され次第更新していこうと思います。
Qiita初投稿で読みづらい部分等あったかもしれませんが最後まで読んでいただきありがとうございました！