Unityを触り始めたころは、つい「とりあえず Update に全部書く」実装になりがちですよね。
プレイヤーの入力処理、敵のAI、HP管理、エフェクトの再生…すべてを1つのスクリプトに押し込んでしまうと、あとから仕様変更が入ったときにどこを触ればいいのか分からない巨大なGodクラスになってしまいます。
特に「壊れる壁」のようなギミックは、
・ダメージ計算
・HP管理
・破壊演出(エフェクト、サウンド)
・オブジェクトの削除
といった複数の責務が絡むので、1つのプレイヤースクリプトにまとめて書いてしまうと、すぐにカオスになります。
そこで今回は、「攻撃を受けるとHPが減り、0になると砕け散る演出と共に消滅する」という機能だけに責務を絞ったコンポーネント
「DestructibleWall」を作って、シンプルに壊れる壁を実装していきましょう。
【Unity】攻撃で砕け散る壁をコンポーネント化!「DestructibleWall」コンポーネント
このコンポーネントは、「ダメージを受けて壊れるオブジェクト」専用です。
プレイヤーの攻撃側ロジック(レイキャストや弾丸など)は別コンポーネントに任せて、
「ダメージを受けたときにどう振る舞うか」だけをこのスクリプトで完結させます。
フルコード(DestructibleWall.cs)
using UnityEngine;
/// <summary>
/**
* 攻撃を受けるとHPが減り、0になると砕け散る演出と共に消滅する「壊れる壁」コンポーネント
*
* 責務:
* - HPの管理
* - ダメージの受付
* - 破壊時の演出(パーティクル・サウンド)の再生
* - 自身の削除
*
* 攻撃の検知(レイキャストや弾丸の当たり判定など)は
* 別コンポーネントから TakeDamage を呼び出す設計にしています。
*/
/// </summary>
public class DestructibleWall : MonoBehaviour
{
[Header("基本パラメータ")]
[SerializeField]
[Tooltip("壁の最大HP")]
private int maxHealth = 3;
[SerializeField]
[Tooltip("ダメージを受けたときに一瞬だけ色を変えて分かりやすくするか")]
private bool flashOnHit = true;
[SerializeField]
[Tooltip("ダメージで色を変える時間(秒)")]
private float flashDuration = 0.1f;
[Header("破壊演出")]
[SerializeField]
[Tooltip("破壊時に生成する破片用プレハブ(任意)。Rigidbody付きの破片などを想定")]
private GameObject destroyFragmentPrefab;
[SerializeField]
[Tooltip("破壊時に再生するパーティクル(任意)。再生後自動で消える設定を推奨")]
private ParticleSystem destroyParticlePrefab;
[SerializeField]
[Tooltip("破壊時に再生するサウンド(任意)")]
private AudioClip destroySound;
[SerializeField]
[Tooltip("効果音を再生するためのAudioSource(任意)。未設定の場合は自動生成")]
private AudioSource audioSource;
[Header("自動破壊設定")]
[SerializeField]
[Tooltip("破壊後、何秒後にオブジェクトを削除するか")]
private float destroyDelay = 0.0f;
// 現在のHP
private int currentHealth;
// マテリアルの色を変えるための参照
private Renderer cachedRenderer;
private Color originalColor;
private bool isFlashing = false;
// 既に破壊処理が走ったかどうか
private bool isDestroyed = false;
private void Awake()
{
// HP初期化
currentHealth = Mathf.Max(1, maxHealth);
// Rendererキャッシュ(任意)
cachedRenderer = GetComponentInChildren<Renderer>();
if (cachedRenderer != null)
{
originalColor = cachedRenderer.material.color;
}
// AudioSource が未設定なら自動で追加
if (audioSource == null && (destroySound != null))
{
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
}
}
/// <summary>
/// 外部からダメージを与えるための公開メソッド
/// 例: 弾丸やプレイヤー攻撃スクリプトから呼び出す
/// </summary>
/// <param name="damageAmount">与えるダメージ量(0以上)</param>
public void TakeDamage(int damageAmount)
{
if (isDestroyed)
{
// 既に壊れているなら何もしない
return;
}
if (damageAmount <= 0)
{
// 0以下のダメージは無視
return;
}
currentHealth -= damageAmount;
currentHealth = Mathf.Max(0, currentHealth);
// ダメージ演出(色変更など)
if (flashOnHit && cachedRenderer != null)
{
// コルーチンで一瞬だけ色を変える
if (!isFlashing)
{
StartCoroutine(FlashCoroutine());
}
}
// HPが0になったら破壊処理
if (currentHealth <= 0)
{
HandleDestruction();
}
}
/// <summary>
/// 壁を即座に破壊したい場合に外部から呼ぶショートカット
/// (例: スイッチで一斉に崩す、ステージリセット時に消す など)
/// </summary>
public void ForceBreak()
{
if (isDestroyed)
{
return;
}
currentHealth = 0;
HandleDestruction();
}
/// <summary>
/// 実際の破壊処理本体
/// </summary>
private void HandleDestruction()
{
if (isDestroyed)
{
return;
}
isDestroyed = true;
// 破片プレハブを生成
if (destroyFragmentPrefab != null)
{
// 壁の位置・回転・スケールを引き継いで生成
GameObject fragment = Instantiate(
destroyFragmentPrefab,
transform.position,
transform.rotation
);
// スケールも合わせたい場合
fragment.transform.localScale = transform.localScale;
}
// パーティクルを生成して再生
if (destroyParticlePrefab != null)
{
ParticleSystem particle = Instantiate(
destroyParticlePrefab,
transform.position,
Quaternion.identity
);
particle.Play();
// パーティクル自体の寿命はPrefab側の設定に任せる
}
// サウンド再生
if (destroySound != null && audioSource != null)
{
audioSource.PlayOneShot(destroySound);
}
// 元の壁メッシュを即座に非表示にして「壊れた感」を出す
if (cachedRenderer != null)
{
cachedRenderer.enabled = false;
}
// コライダーを無効にしてこれ以上当たり判定が起きないようにする
Collider[] colliders = GetComponentsInChildren<Collider>();
foreach (var col in colliders)
{
col.enabled = false;
}
// 一定時間後に完全削除
if (destroyDelay > 0f)
{
Destroy(gameObject, destroyDelay);
}
else
{
Destroy(gameObject);
}
}
/// <summary>
/// ダメージを受けたときに一瞬だけ色を変えるコルーチン
/// </summary>
private System.Collections.IEnumerator FlashCoroutine()
{
isFlashing = true;
if (cachedRenderer != null)
{
// ダメージ時の色(ここでは赤にしているが、インスペクターから制御したい場合は別フィールドにしてもOK)
cachedRenderer.material.color = Color.red;
}
yield return new WaitForSeconds(flashDuration);
if (cachedRenderer != null)
{
cachedRenderer.material.color = originalColor;
}
isFlashing = false;
}
/// <summary>
/// 現在HPを取得するための簡易ゲッター
/// UI表示などで使いたい場合に利用
/// </summary>
public int GetCurrentHealth()
{
return currentHealth;
}
/// <summary>
/// 最大HPを取得するための簡易ゲッター
/// </summary>
public int GetMaxHealth()
{
return maxHealth;
}
}
使い方の手順
ここでは、プレイヤーの近接攻撃で壊れる石壁を例に、使い方を手順でまとめます。
-
① 壁オブジェクトを用意する
- Hierarchy で右クリック → 3D Object → Cube を作成し、「StoneWall」などの名前を付けます。
- 必要に応じてサイズを調整し、壁っぽいマテリアルを割り当てます。
- 当たり判定用に
BoxColliderが付いていることを確認します(なければ追加)。
-
② DestructibleWall コンポーネントをアタッチする
- Project ビューで上記コードを
DestructibleWall.csとして保存します。 - 「StoneWall」オブジェクトを選択し、Inspector の「Add Component」から
DestructibleWallを追加します。 Max Healthを 3 〜 10 くらいの好みの値に設定します。- 砕け散る演出をしたい場合は、以下を設定します:
- Destroy Fragment Prefab: 壊れた壁の破片モデルのプレハブ(Rigidbody付きだと迫力UP)
- Destroy Particle Prefab: 砂煙や破片のパーティクル
- Destroy Sound と Audio Source: 壊れる音と再生用 AudioSource
- Project ビューで上記コードを
-
③ 攻撃側からダメージを与える
攻撃の検知は別コンポーネントの責務にします。
例として、プレイヤーの前方に短いレイを飛ばして攻撃する簡易スクリプトを示します。using UnityEngine; /// <summary> /// 非常にシンプルな近接攻撃サンプル。 /// 左クリックで前方にレイを飛ばし、DestructibleWall にダメージを与える。 /// 実プロジェクトでは、プレイヤーの移動やアニメーションとは別コンポーネントにするのがおすすめ。 /// </summary> public class SimpleMeleeAttack : MonoBehaviour { [SerializeField] private int attackDamage = 1; [SerializeField] private float attackRange = 2.0f; [SerializeField] private KeyCode attackKey = KeyCode.Mouse0; private void Update() { if (Input.GetKeyDown(attackKey)) { TryAttack(); } } private void TryAttack() { Ray ray = new Ray(transform.position, transform.forward); if (Physics.Raycast(ray, out RaycastHit hit, attackRange)) { // ヒットしたオブジェクトに DestructibleWall が付いていればダメージを与える DestructibleWall wall = hit.collider.GetComponentInParent<DestructibleWall>(); if (wall != null) { wall.TakeDamage(attackDamage); } } } }- この
SimpleMeleeAttackをプレイヤーオブジェクトにアタッチします。 - ゲーム実行中に壁に向かって近づき、左クリック(デフォルト)で攻撃すると、HPが0になるまでダメージが入り、0になった瞬間に砕け散る演出と共に消滅します。
- この
-
④ 応用例:動く床やギミックとして使う
- 敵の進行を一時的に防ぐバリケードとして配置し、一定ダメージを受けると崩れる。
- スイッチで一斉に崩れる足場にして、
ForceBreak()を呼び出してまとめて破壊する。 - ボス戦のギミックとして、特定の攻撃を当てると壁が壊れて隠し部屋が出現する。
メリットと応用
この DestructibleWall コンポーネントを使うメリットは、「壊れる」という振る舞いを完全に1つのスクリプトに閉じ込められることです。
- HP管理・破壊演出・削除処理が1コンポーネントにまとまっているので、プレイヤーや敵のスクリプトが肥大化しない。
- Prefab 化しておけば、シーン上にコピペで量産するだけで統一された挙動の壊れる壁を配置できる。
- エフェクトやサウンドを変えたい場合も、Prefab のインスペクターを1か所いじるだけで全ステージに反映される。
- 「壊れる床」「壊れる柱」「壊れるオブジェクト全般」にも流用でき、レベルデザインのテンプレートとして再利用性が高い。
また、攻撃側とは TakeDamage(int damage) のメソッドで疎結合になっているため、
- 近接攻撃
- 飛び道具(弾丸)
- 爆発ダメージ(範囲攻撃)
- トリガーゾーン(一定時間ごとにダメージ)
など、どんな攻撃システムからでも同じインターフェースで扱えます。
この「壊れる」という責務だけを切り出したコンポーネント指向の設計にしておくと、プロジェクトが大きくなっても管理がかなり楽になります。
改造案:ダメージ時にスクリーンシェイクを追加する
もう一歩踏み込んで、「壁が壊れたときに画面を揺らす」演出を足したい場合、
例えば以下のような簡易スクリーンシェイクメソッドを別コンポーネント(CameraShake など)に用意しておき、
HandleDestruction() の中から呼び出す形にできます。
/// <summary>
/// 非常にシンプルなスクリーンシェイク例。
/// 実際にはカメラ専用コンポーネントとして分離し、DestructibleWall から呼び出す構成にすると綺麗です。
/// </summary>
private void ShakeCameraOnBreak()
{
Camera mainCamera = Camera.main;
if (mainCamera == null) return;
// ここでは「その場でほんの少しだけランダムに揺らす」簡易版
Vector3 originalPos = mainCamera.transform.position;
float shakeIntensity = 0.1f;
float shakeTime = 0.1f;
// コルーチンで揺らしたい場合は、別途コルーチン化するとよいです
mainCamera.transform.position = originalPos + Random.insideUnitSphere * shakeIntensity;
// 簡易版のため、すぐ元の位置に戻す
mainCamera.transform.position = originalPos;
}
実際の運用では、このような演出系も専用コンポーネントとして切り出し、
DestructibleWall からは「壊れた」というイベントだけを通知するようにしていくと、さらに綺麗な設計になります。
1つのコンポーネントに責務を詰め込みすぎないように意識しつつ、壊れる壁ギミックを量産していきましょう。




