Unityで3Dゲームの素手攻撃の攻撃当たり判定を作る

# はじめに
個人製作で初めての3Dゲームを制作しているのですが、パンチやキックなどの攻撃の際の当たり判定をどうつけるかで悩み、かなり時間を費やしてしまったので忘備録として書き残しておきます。





# １．攻撃の種類を決める
制作するゲームによって攻撃の種類は様々あると思います。
今回は素手の格闘の場合なので、武器等のオブジェクトをキャラクターに持たせて作る場合は、この記事は参考にならないかもしれないのであしからず…

まずは操作するplayerのモーションを決めます。
今回はこちらのアセットのモーションを使用させていただいております。
[Hand to Hand Set](https://assetstore.unity.com/packages/3d/animations/hand-to-hand-set-227377)
![Desktop Screenshot 2024.12.14 - 15.47.17.89.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/10894b3c-da9e-e3ac-ac23-ac85ea184fe5.png)


# ２．攻撃モーションを動かす
使用するモーションが決まったら、好きな方法でplayerにアタックをさせる処理を書いていきます。
今回はInputSystemを使用して、左クリックで攻撃を行えるようにしました。

![Desktop Screenshot 2024.12.14 - 15.53.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/3c94a056-aca7-a3bc-75e9-3a5fa0cb0903.png)

```csharp:PlayerAttackController.cs
        public class PlayerAttackController : MonoBehaviour
        {
            Animator playerAnim;
            InputSystem_Actions inputAction;
            
            void Start()
            {
                playerAnim = GetComponent<Animator>();
            
                inputAction = new InputSystem_Actions();
                inputAction.Player.Attack.started += OnAttack;
                inputAction.Enable();
            }

            void OnAttack(InputAction.CallbackContext context)
            {
               // ここに必要な処理
                playerAnim.SetTrigger("isAttack");// 今回はトリガーで制御する
            }

            private void OnDestroy()
            {
                inputAction?.Dispose();
            }
        }

```

:::note info
インプットシステムに関する処理は、以下の動画とサイトを参考にしました。
[AnimatorControllerを使ってコンボ攻撃する方法 ＃２](https://youtu.be/oF-nby5JBSw?si=gpRkaTwnQG4b9mOs)

[【Unity】Input Actionの基本的な使い方](https://nekojara.city/unity-input-system-actions#Binding%E3%81%AE%E5%AE%9A%E7%BE%A9)
:::

 <details><summary>参考程度にAnimatorの画面を貼っておきます。</summary>
 
![Desktop Screenshot 2024.12.14 - 16.25.41.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/d3d5cb0e-805f-f20f-74a5-d26e3f935065.png)
![Desktop Screenshot 2024.12.14 - 16.25.49.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/df8ce20d-9d38-458c-e803-9e810bc39ae9.png)
![Desktop Screenshot 2024.12.14 - 16.25.57.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/75ec57d2-ad7b-4b58-1ff9-17fbede37c2a.png)

:::note info
2枚目にAttackが2つあるのは右クリックで別の攻撃ができるようにしたかったからです。今回はAttack0のほうに絞って解説していきます。
:::

</details>

# ３．攻撃判定を作る
playerの攻撃モーションに合わせてコライダーを設置していきます。
設置方法は、まずplayerオブジェクトを選択し、Animationタブを開きます。その後、攻撃モーションを選んでコマ送りで再生し、ちょうどいいと思う場所に当たり判定となるコライダーを設置します。
コライダーの形については何でも大丈夫だと思います。
![DsotYjX55wxUEjSuL5Vs1734162824-1734162876.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/255da7ef-bbb7-366e-ab2c-e2bd66b0d3c7.gif)

↓コライダーを設置した図

![rGfT7b8OdyeZOvMCHuTV1734162563-1734162675.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/eb9313fc-f980-4776-74eb-2c6c6ca03da2.gif)

このような形で、当たり判定をすべての攻撃モーションに対してつけていきます。

:::note warn
 当たり判定を設定し終わったらコライダーのチェックボックスを外しておいてください。次の項目で必要なときにだけ発生させるようにします。
:::

# ４．攻撃判定の発生処理
スクリプトに処理を追加していきます。
```csharp:PlayerAttackController.cs
        public class PlayerAttackController : MonoBehaviour
        {
             InputSystem_Actions inputAction;
             AnimatorClipInfo[] clipInfo;

             [SerializeField] List<Collider> attackColliders = new List<Collider>();
             [SerializeField] SerializableDictionary<string, int> attackColliderDictionary = null;
            void Start()
            {
                inputAction = new InputSystem_Actions();
                inputAction.Player.Attack.started += OnAttack;
                
                inputAction.Enable();
            }

            void OnAttack(InputAction.CallbackContext context)
            {
               // ここに必要な処理
                playerAnim.SetTrigger("isAttack");// 今回はトリガーで制御する
            }

             /// <summary>
             /// 現在のアニメーションの名前を参照して、それぞれの当たり判定を出すメソッド
             /// </summary>
             public void ColliderSet()
             {
                 string animName = clipInfo[0].clip.name;
            
                 if (attackColliderDictionary.ContainsKey(animName))
                 {
                     attackColliders[attackColliderDictionary[animName]].enabled = true;
                 }
             }

              /// <summary>
              /// 当たり判定を削除するメソッド
              /// </summary>
              public void ColliderRemove()
              {
                  string animName = clipInfo[0].clip.name;
            
                  if (attackColliderDictionary.ContainsKey(animName))
                  {
                      attackColliders[attackColliderDictionary[animName]].enabled = false;
                  }
              }
        }
```

追加した内容は以下の通りです。

```csharp
 // 今動いているAnimationClipを取得する変数
 AnimatorClipInfo[] clipInfo;
 
 // 攻撃の当たり判定を格納するList
 [SerializeField] List<Collider> attackColliders = new List<Collider>(); 
 
 // AnimationClipの名前と対応する数字を格納するDictionary
 [SerializeField] SerializableDictionary<string, int> attackColliderDictionary = null;
 
 /// 現在のアニメーションの名前を参照して、それぞれの当たり判定を出すメソッド
 public void ColliderSet()
 {
     string animName = clipInfo[0].clip.name;
            
     if (attackColliderDictionary.ContainsKey(animName))
     {
           attackColliders[attackColliderDictionary[animName]].enabled = true;
     }
 }

 // 当たり判定を削除するメソッド
 public void ColliderRemove()
 {
      string animName = clipInfo[0].clip.name;
            
      if (attackColliderDictionary.ContainsKey(animName))
      {
            attackColliders[attackColliderDictionary[animName]].enabled = false;
      } 
 }
```

:::note info
Dictionaryは公式にあるクラスだけで行うとInspector上に表示するだけで編集はできないのですが、こちらのサイトを参考させていただき、新しいクラスを作成し、編集もできるようにしました。
[【Unity】Inspectorで設定できるDictionaryを作りたい](https://zenn.dev/tmb/articles/9b4c532da8d467)
:::


これらを記述したら、Unityに戻ります。
まずplayerオブジェクトのインスペクターを開き、このような状態になっていることを確認します。

![Desktop Screenshot 2024.12.14 - 18.08.17.75.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/ece0e512-9d97-2da7-1832-2ab0e61428d5.png)


AttackCollidersを展開し、先ほど設定した当たり判定のコライダーを攻撃順に格納していきます。

![Desktop Screenshot 2024.12.14 - 18.11.09.76.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/81d12abc-9f5f-c4f8-051c-ba8ae77a0495.png)

AttackColliderDictionaryを展開し、AnimationClipの名前と、それに対応するAttackCollidersの番号を入力します。


![Desktop Screenshot 2024.12.14 - 18.11.14.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/54ae17bf-5878-c41f-d844-ec0692dd590a.png)




次に、先ほどコライダーの位置調整のために触っていたAnimationタブを開きます。
そして、攻撃が当たるだろうというタイミングで ColliderSet() を、攻撃が終わるだろうというタイミングで ColliderRemove() をAnimationEventで登録しておきます。

:::note info
設定する場所に関してはモーションや好みによって変わるので納得いくまで調整してください。
:::

![Desktop Screenshot 2024.12.14 - 16.53.45.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/ef8f09c2-a9e9-071a-9cd0-c8b818d4d9e6.png)

Animatorタブを開き、攻撃モーションのトランジションの調整を行っていきます。
今回のようなやり方でアニメーションの遷移を行っていると、AnimationEventが発生する前に別のモーションに移動してしまったり（当たり判定が発生しない）、最初のAnimationEventだけ発生して後のイベントは発生しないまま（当たり判定が消えない）などの現象が起きてしまうのでここは必ず行います。

![Desktop Screenshot 2024.12.14 - 18.25.23.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961293/75e08fcc-f899-b378-d999-53547d0ac27d.png)

:::note info
赤い丸でかこってある部分に注目。アニメーションが遷移するまでの間にAnimationEventが発生するだけの余裕があるか確認。
:::

ここまで出来たらplayerの攻撃の当たり判定は完成したはずです。
後は敵側にプレイヤーの攻撃判定が当たった時の処理を追加すれば戦闘の基礎の部分はできると思います。

ここまで読んでいただきありがとうございました。