【Unity2D】爆発を簡単に実装する方法

IPFactory Advent Calender 2023 11日目の記事です。

https://qiita.com/advent-calendar/2023/ipfactory

## はじめに
Unity2Dを用いた爆発の実装方法をまとめた記事が見当たらなかったので、かみ砕いて解説してみました。
ぜひお手元で実装してみてください！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/19ce8003-d3da-de07-1ff2-a03e4ecc9744.gif" width="500">

## 目次

- [用意するもの](#用意するもの)
- [下準備](#下準備)
- [爆発スクリプトの作成](#爆発スクリプトの作成)
- [コードの解説](#コードの解説)
- [爆弾の仕上げ](#爆弾の仕上げ)
- [動かす](#動かす)
- [さいごに](#さいごに)

## 用意するもの
### 1.爆弾の画像
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/07ad2681-3a4e-8bee-2df2-ee9c8f04b586.png" width="250" height="250">

[こちら](https://www.freepik.com/icon/bomb_8381044)の画像を使わせていただきました。

### 2.エフェクト

今回は[こちら](https://youtu.be/dOnQY0t3TBM?feature=shared)の動画をもとに`ParticleSystem`を用いて自作しました。
非常に分かりやすい動画ですので是非参考にしてみてください。

自作しない場合は[AssetStore](https://assetstore.unity.com/ja?locale=ja-JP)から持ってくるか、用意できない場合は無くても大丈夫です。

![download.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/123dd8b6-db3f-7622-5902-6f43e61b87ff.gif)

## 下準備

### 1.ステージ
舞台となるステージを用意します。
Hierarchy ビューで右クリック → 2D Object → Sprite から好きな形を選びScene上に配置してください。

配置できましたら当たり判定をつけるため Inspector ビューでAdd Componet → Physics2D → Boc Collider 2D を選びステージの大きさに合うように調整してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/ce3d4b79-ed06-a9bd-9658-01ae4c146f81.png" width="300">

![スクリーンショット 2023-12-11 000944.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/c0fe47f8-ea54-4df5-6a14-4b565adf7732.png)

### 2.箱
爆発の対象となる箱を用意します。
ステージと同じように、Scene上にbox配置し、コライダーをアタッチしてください。

![スクリーンショット 2023-12-11 001241.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/8c04c245-02c2-2d15-1c87-4fbe6827968e.png)

また、箱には重力を適用したいため Inspector ビューでAdd Componet → Physics2D → Rigidbody 2D を選んでください。

ここでゲームを開始すると箱がステージの上に落ちるかと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/fa3670b0-5260-e811-a41d-f7b123d4e0f1.gif" width="300">

### 3.爆弾

爆弾ですが、基本的には箱と同じですのでboxをコピーしてbombに名前を変更してください。

bombは分かりやすいよう見た目を変えます。用意した爆弾の画像を Inspector ビューのSprite Render → Sprite にアタッチしてください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/734e7fa3-c77f-d72d-3e48-6809508e590b.png" width="300">

爆弾は丸いのにboxをそのまま使っているためコライダーが BoxCollider2D になっています。

BoxCollider2Dを削除後、Inspector ビューでAdd Componet → Physics2D → Circle Collider 2D をアタッチし、画像に合わせて形を変更してください。

![スクリーンショット 2023-12-11 093409.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/7601ae59-1567-c203-ae97-d4d0e6710979.png)

これで下準備は完了です。
箱は多いほど爆発の影響を感じやすいのでお好きな数配置してください。

## 爆発スクリプトの作成

Assetsで右クリック → Create → C# Script でスクリプトを作成し、名前を**Bomb**に変更してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/da6a557a-5da2-2490-c6c8-cb9ec0dde69a.png" width="300">

スクリプトをダブルクリックで開き、以下のコードを張り付けてください。

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bomb : MonoBehaviour
{
    [SerializeField] private ParticleSystem explosionParticleSystemPrefab; // 爆発時のパーティクルシステム
    [SerializeField] private float explosionForce; // 爆発力
    [SerializeField] private float explosionRadius; // 爆発半径

    private void OnMouseDown()
    {
        Detonate();
    }

    // 爆発処理
    void Detonate()
    {
        // パーティクルシステムを生成して爆発エフェクトを再生
        ParticleSystem explosionParticleSystem = Instantiate(explosionParticleSystemPrefab, transform.position, Quaternion.identity);
        explosionParticleSystem.Play();

        // パーティクル再生時間が終了したらパーティクルシステムを破棄
        Destroy(explosionParticleSystem.gameObject, explosionParticleSystem.main.duration);

        // 爆風の範囲内のオブジェクトを検出
        Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, explosionRadius);
        foreach (Collider2D collider in colliders)
        {
            ApplyExplosionForce(collider);
        }

        // 爆弾オブジェクトを破棄
        Destroy(gameObject);
    }

    // 吹き飛ばしの処理
    void ApplyExplosionForce(Collider2D targetCollider)
    {
        Rigidbody2D targetRigidbody = targetCollider.GetComponent<Rigidbody2D>();

        if (targetRigidbody != null)
        {
            // 爆心からの距離に応じて力を計算
            Vector2 explosionDirection = targetCollider.transform.position - transform.position;
            float distance = explosionDirection.magnitude;
            float normalizedDistance = distance / explosionRadius;
            float force = Mathf.Lerp(explosionForce, 0f, normalizedDistance);

            // 力を加える
            targetRigidbody.AddForce(explosionDirection.normalized * force, ForceMode2D.Impulse);
        }
    }
}
```

## コードの解説

上記のコードをかみ砕いて解説していきます。
コピペで動きますので実装だけしたい方は飛ばしてください。[skip→](#爆弾の仕上げ)

### 1.変数定義

```csharp
[SerializeField] private ParticleSystem explosionParticleSystemPrefab; // 爆発時のパーティクルシステム
[SerializeField] private float explosionForce; // 爆発力
[SerializeField] private float explosionRadius; // 爆発半径
```

変数は上から順番に
- 爆発エフェクト
- 爆発力
- 爆発半径

になります。爆発力・爆発半径ともに`9`を代入しているという前提で進めていこうと思います。

※ 変数はスクリプト上で値を代入せず、後で Inspector 上で変更していきます。

### 2.クリックで爆発

```csharp
private void OnMouseDown()
{
    Detonate();
}
```

`OnMouseDown`メソッドはオブジェクトがクリックされたときに呼び出されます。

要するにクリックされたときに爆発するようになっています。

### ３エフェクトの生成

```csharp
    void Detonate()
    {
        // パーティクルシステムを生成して爆発エフェクトを再生
        ParticleSystem explosionParticleSystem = Instantiate(explosionParticleSystemPrefab, transform.position, Quaternion.identity);
        explosionParticleSystem.Play();

        // パーティクル再生時間が終了したらパーティクルシステムを破棄
        Destroy(explosionParticleSystem.gameObject, explosionParticleSystem.main.duration);

        // ----- 省略 -----
    }
```

`explosionParticleSystem`変数に`transform.position`（爆弾の位置）に生成した`explosionParticleSystemPrefab`（爆発のエフェクト）を代入しています。

生成したエフェクト`explosionParticleSystem`を再生し、再生が終わり次第破棄するという流れになっています。

**※ エフェクト（パーティクル）が用意できなかった方は上のコードは消してください。**

### 4.爆発範囲にいるオブジェクトの取得

```csharp
    void Detonate()
    {
        // ----- 省略 -----

        // 爆風の範囲内のオブジェクトを検出
        Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, explosionRadius);
        foreach (Collider2D collider in colliders)
        {
            ApplyExplosionForce(collider);
        }

        // 爆弾オブジェクトを破棄
        Destroy(gameObject);
    }
```

`Physics2D.OverlapCircleAll`で爆弾を中心とした半径`explosionRadius`の当たり判定を生成し、範囲内のオブジェクト全てが`ApplyExplosionForce`（爆風を加える）メソッドを実行させています。

![スクリーンショット 2023-12-11 105518.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/9fc48d95-e356-ef3b-c270-9a3f6a0773d7.png)

爆弾は爆発によってなくなりますので`Destroy(gameObject);`で破棄します。

### 5.Rigidbody2Dの取得

```csharp
    // 吹き飛ばしの処理
    void ApplyExplosionForce(Collider2D targetCollider)
    {
        Rigidbody2D targetRigidbody = targetCollider.GetComponent<Rigidbody2D>();

        if (targetRigidbody != null)
        {
            // ----- 省略 -----
        }
    }
```

`targetRigidbody`に取得したオブジェクトのRigidbody2Dを取得し、代入します。
このRigidbody2Dは後で爆風を与える時に使います。

基本的には`targetRigidbody`にRigidbody2Dが代入されているはずです。
しかしステージなどのコライダーを持っていながらRigidbody2Dを持っていないオブジェクトによりエラーが起きる可能性があるため`if (targetRigidbody != null)`でnull検知を忘れないでください。

![スクリーンショット 2023-12-11 112948.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/69f77792-41ba-e5eb-fe05-097d65903fd9.png)

### 6.オブジェクト間のベクトルと距離を求める

```csharp
// 爆心からの距離に応じて力を計算
Vector2 explosionDirection = targetCollider.transform.position - transform.position;
float distance = explosionDirection.magnitude;
```

boxが受ける爆発力を計算していきます。
ここではbombの座標を(1, 2)boxの座標を(8, 5)だと仮定して進めていきます。

最初に、爆弾から箱へのベクトルを
```csharp
Vector2 explosionDirection = targetCollider.transform.position - transform.position;
```
で計算します。
boxの座標 - bombの座標 → `(8, 5) - (1, 2) = (7, 3)`でオブジェクト間のベクトルが`(7, 3)`と求まりました。

![スクリーンショット 2023-12-11 123133.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/26d4ab7f-09b4-4c58-9dd1-8bc3005a6301.png)

ベクトルが求まりオブジェクト間の角度が分かるようになりましたが、肝心なオブジェクト間の距離が分かりません。

そこで先ほど求めたベクトルを用いてオブジェクト間の距離を
```csharp
float distance = explosionDirection.magnitude;
```
で計算します。
`magnitude`は、与えられたベクトルから自動的に距離に変換してくれるプロパティのため、`7.615773`と距離が求まりました。


```math
\text{magnitude} = \sqrt{x^2 + y^2}
```


![スクリーンショット 2023-12-11 131031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/38ba3301-e197-e96b-cb9b-6ea9759dedce.png)

### 7.爆風を計算し力を加える

```csharp
float normalizedDistance = distance / explosionRadius;
float force = Mathf.Lerp(explosionForce, 0f, normalizedDistance);

// 力を加える
targetRigidbody.AddForce(explosionDirection.normalized * force, ForceMode2D.Impulse);
```

最初に先ほど求めた`distance`（距離）を正規化していきます。
ここでの正規化とは`distance`が`explosionRadius`（爆発半径）の何倍になるかを指します。

```csharp
float normalizedDistance = distance / explosionRadius;
```

先ほど求めた距離`7.615773`から爆発半径である`9`で割ると正規化された距離である`0.8461975`が求まりました。

これにより、半径`9`を`1`としたときにboxは`0.8461975`の位置にいるということが分かりました。

![スクリーンショット 2023-12-11 133128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/2c3950f3-a4da-6f15-47bf-7e2064c9b58c.png)

次にboxが受ける爆発力を求めます。

爆発力`explosionForce`は`9`と定義したので爆発力と位置の関係を表すと以下のようになります。

![スクリーンショット 2023-12-11 140205.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/f5c274a0-b6ca-248b-799e-ec8e45a81924.png)

爆発の威力は爆発の中心が一番が高いので、爆発力`9`になります。
また、爆発半径は`9`なので、爆発を中心とした半径`9`を超えると爆発力が`0`になることが分かります。

この関係から

```csharp
float force = Mathf.Lerp(explosionForce, 0f, normalizedDistance);
```

を用いて最大値`9`を始点、最小値`0`を終点として爆発の中心からの距離である`0.8461975`で線形補完することで、box位置の爆発力である`1.384223`という値が求まりました！

最後に、対象オブジェクトの`Rigidbody2D`に今求めた力を加えます。

```csharp
targetRigidbody.AddForce(explosionDirection.normalized * force, ForceMode2D.Impulse);
```

加える力は`ForceMode2D.Impulse`にすることで爆発のような瞬間的な力がかかるようになります。
力を加える角度は最初に求めたオブジェクト間のベクトル（bombからboxの角度）に設定し、その角度にいま求めた`fornce`（box位置の爆発力）を加えると...

![スクリーンショット 2023-12-11 141206.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/1040bb0f-9022-960c-0930-faecbd9a0dea.png)

/
ドカーーーン！！
\

![スクリーンショット 2023-12-11 142238.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/018f6119-78e8-49e0-3088-e5f3704b8526.png)

**飛びました！！**

## 爆弾の仕上げ

完成したBombスクリプトをBombにアタッチしていきます。

Hierarchy ビューからBombを選択 → Inspector ビューにBombスクリプトをドラッグアンドドロップ

変数にはそれぞれ

- 用意したエフェクトをアタッチ
- 爆発力`9`を入力
- 爆発半径`9`を入力

エフェクトがない場合は空でも大丈夫ですが、[こちら](#３エフェクトの生成)の3行を消しておいてください。

`xplosion Force`・`Explosion Radius`には色々な値を入れて動きの違いを確かめてみてください。

![スクリーンショット 2023-12-11 142956.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/c06c9710-2798-db41-9b3a-c47b6a016913.png)

## 動かす

完成です！

爆弾をクリックしてみると...

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2966635/7a343e2b-351c-a5c8-ab5d-224df06923b8.gif" width="500">

**吹き飛びました！！**

## さいごに

できるだけかみ砕いて解説してみました。

ゲーム制作の参考になれば嬉しいです！

## 参考資料
- https://youtu.be/dOnQY0t3TBM?feature=shared
- https://www.freepik.com/icon/bomb_8381044