リスコフの置換原則を活かした直接ボタンでエネミーを攻撃する手順
1. 概要
複数のエネミー(GoblinやTrollなど)がシーン上に存在する状態で、UIのボタンを押すだけで該当のエネミーを Attack() メソッドで攻撃する実装方法を解説します。
2. コード実装
EnemyManager.cs
using UnityEngine;
namespace LiskovSubstitutionPrinciple
{
public class EnemyManager : MonoBehaviour
{
// UIのOnClickから直接呼び出せるメソッド
public void AttackSelectedEnemy(Enemy enemy)
{
if (enemy != null)
{
enemy.Attack();
}
}
}
}
- AttackSelectedEnemy(Enemy enemy) を public メソッドとして定義します。
- 引数に攻撃対象の Enemy オブジェクトを受け取り、Attack() を呼び出します。
3. UnityエディタでのUI設定
- TextMeshProボタンの作成
- Hierarchy ビューで右クリックし、UI → UI → Button – TextMeshPro を選択します。
- シーンに Canvas と EventSystem が自動生成されます。
- OnClickイベントの設定
- 作成された Button (TMP) オブジェクトを選択し、Inspector の Button (Script) コンポーネント内にある On Click () リストを展開します。
- リスト下部の「+」をクリックし、EnemyManager をアタッチした GameObject をドラッグ&ドロップします。
- 関数プルダウンから EnemyManager → AttackSelectedEnemy(Enemy) を選択し、引数欄に攻撃対象の Enemy(シーン上の Goblin や Troll オブジェクト)をドラッグします。
4. サンプル構成例
Hierarchy の一例
Liskov Substitution Principle(シーンルート)
├ Main Camera
├ Directional Light
├ EnemyManager(EnemyManager.cs アタッチ)
├ Goblin(Enemy コンポーネント)
├ Troll(Enemy コンポーネント)
└ Canvas
├ Button - TextMeshPro(名前: ゴブリンを攻撃)
├ Button - TextMeshPro(名前: トロールを攻撃)
├ EventSystem

Inspector の設定例
「ゴブリンを攻撃」ボタン
- On Click ()
- Target: EnemyManager
- Function: EnemyManager.AttackSelectedEnemy(Enemy)
- Argument: Goblin

「トロールを攻撃」ボタン
- On Click ()
- Target: EnemyManager
- Function: EnemyManager.AttackSelectedEnemy(Enemy)
- Argument: Troll

5. 注意点・拡張例
- Prefab生成時の登録:実行時に Prefab から Enemy を生成する場合は、スクリプト側で動的にボタンの OnClick イベントを登録するか、生成後に Inspector で設定を行ってください。
Unityでは、ゲーム中にエネミー(Enemy)のPrefabをInstantiateで動的に生成することがよくあります。この場合、UIボタンのOnClickイベントに、生成したエネミーの参照を紐付ける作業も動的に行う必要があります。
なぜなら、Inspectorでの手動登録は事前にシーン上にあるオブジェクトしか指定できないためです。
具体的な実装例
たとえば、「敵を生成するたびに対応する攻撃ボタンも生成し、OnClickイベントに生成したEnemyをバインドしたい」場合は、次のようなコードになります。
1. EnemyのPrefabとボタンのPrefabをProjectに用意
- Enemy(例: Goblin、Trollなど)のPrefab
- Button(TextMeshPro Buttonなど)のPrefab
2. Managerスクリプトで動的生成・OnClick登録
using UnityEngine;
using UnityEngine.UI; // Button用
using TMPro; // TextMeshPro用
public class EnemySpawner : MonoBehaviour
{
public EnemyManager enemyManager; // EnemyManagerへの参照
public Enemy enemyPrefab; // 生成するEnemyのPrefab
public Button buttonPrefab; // 生成するボタンのPrefab
public Transform buttonParent; // 生成したボタンを配置する親(例: VerticalLayoutGroupなど)
void Start()
{
// 例:3体のエネミーを生成してUIも作成
for (int i = 0; i < 3; i++)
{
// Enemy生成
Enemy newEnemy = Instantiate(enemyPrefab, new Vector3(i * 2, 0, 0), Quaternion.identity);
// Button生成
Button newButton = Instantiate(buttonPrefab, buttonParent);
// ボタンラベル変更
var text = newButton.GetComponentInChildren<TextMeshProUGUI>();
text.text = $"エネミー{i + 1}を攻撃";
// OnClickにEnemyManager.AttackSelectedEnemy(newEnemy)を追加
newButton.onClick.AddListener(() => enemyManager.AttackSelectedEnemy(newEnemy));
}
}
}
ポイント解説
- 動的登録の理由シーン開始後に生成されたEnemyやButtonは、Inspectorで事前にアサインできません。そのため、Instantiate後にスクリプトからonClick.AddListenerでバインドします。
- ラムダ式でキャプチャ() => enemyManager.AttackSelectedEnemy(newEnemy) のように、ラムダ式でその時生成されたEnemyを正しく参照させる必要があります。ループ内でnewEnemyを直接参照することで、ボタンごとに正しい敵が攻撃対象になります。
応用:Prefabごとに異なる攻撃ボタンを増減する
この仕組みを使えば、敵を追加・削除したときにボタンUIも連動して増減させることができます。
また、ボタンの外観や機能も動的にカスタマイズ可能です。
注意点
- 動的に生成したUIやEnemyは、**不要になったら破棄(Destroy)**することも考慮してください。
- UIの親にはLayout Group(Vertical/Horizontal)を使うと、ボタンが自動的に整列します。
- PrefabはProjectウィンドウからドラッグしてスクリプトのpublicフィールドにセットしてください。
Inspectorで生成後に手動で登録する方法
生成後のEnemyやButtonをHierarchyでドラッグ&ドロップしてOnClick登録することも可能ですが、
数が多い場合や動的生成が主な用途ならスクリプトでの自動化が推奨されます。
まとめ
- 実行時にPrefabからEnemyを生成した場合は、ボタンのOnClickイベントもスクリプトで動的にバインドする。
- ラムダ式で「今生成したEnemy」を正しくキャプチャし、イベント登録するのがポイント。
- Inspectorでの手動登録は、あくまで「事前にシーンにあるオブジェクト」用です。
もし具体的なサンプルプロジェクトや、動的にUIを生成するサンプルが必要であれば、さらに詳細にご案内できます。
- ボタンのラベル変更:Text (TMP) コンポーネントの Text プロパティを編集して、表示文字を変更できます。
1. ボタンオブジェクトの構成
Unityで UI > Button – TextMeshPro を作成すると、次のような階層になります。
Button - TextMeshPro
└── Text (TMP)
- Button – TextMeshPro オブジェクト:ボタン本体。
- Text (TMP) オブジェクト:ボタン上に表示されるテキストラベル部分。
2. ラベル(表示文字)の編集手順
- Hierarchyビューで対象のボタンを展開作成したボタン(例:「ゴブリンを攻撃」)をクリックし、子オブジェクトとしてある Text (TMP) を選択します。
- Inspectorでテキストを編集Text (TMP) を選択すると、Inspector ウィンドウに Text (TMP) コンポーネントが表示されます。
- この中の「Text」プロパティ(大きなテキストエリア)に、実際にボタン上に表示したい文字列(例:「ゴブリンを攻撃」)を入力します。
- ※画像は参考イメージ
- リアルタイムで反映入力した文字は、即座にSceneビューやGameビューのボタンに反映されます。
3. さらに柔軟な活用例
- 動的にラベルを変更したい場合(例:スクリプトから文字を変える)は、ボタンの TextMeshProUGUI コンポーネントを取得し、text プロパティを書き換えます。
using TMPro;
public TextMeshProUGUI label;
void Start()
{
label.text = "ゴブリンを攻撃";
}
- ※ラベル用のフィールドに TextMeshProUGUI 型を指定し、インスペクタから該当の Text (TMP) を割り当てます。
まとめ
- 手動編集:Inspector から Text (TMP) の Text プロパティを直接書き換えることで、誰でも直感的にラベルを変更可能。
- 動的変更:スクリプトから TextMeshProUGUI.text を変更することで、状況に応じてボタンの表示を切り替え可能。
この方法により、ユーザーにとって分かりやすいラベルに簡単に変更できるので、UIの分かりやすさ・操作性が向上します。
- より柔軟な管理:将来的にボタンを自動生成する場合は、List<Enemy> をループして動的に UI を生成し、AttackSelectedEnemy へバインドする実装を検討してください。
より柔軟な管理:動的にボタンとバインドを生成する方法
【背景・目的】
手動でボタンを用意し、それぞれに Enemy を割り当てていく方法は、エネミー数が少ない場合や固定の場合にはシンプルです。しかし、エネミーが動的に増減する場合や、Prefabから生成される場合、エネミーごとに個別のボタンを自動で作成したいシーンも多くなります。
この場合、List<Enemy> で管理しながら、対応するUIボタンもスクリプトから動的に生成・バインドすると、拡張性が飛躍的に高まります。
【実装例】
1. 必要な準備
- ボタンのPrefab を1つ作っておきます(Resources/EnemyButton.prefab など)。
- Button (TextMeshPro) にして、Text部分に仮のテキスト(例: “Enemy”)をセットしておく。
- UI Canvas に配置するボタンの親オブジェクト(VerticalLayoutGroup 付きの GameObject など)を用意。
2. スクリプト例(EnemyManager.cs)
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
namespace LiskovSubstitutionPrinciple
{
public class EnemyManager : MonoBehaviour
{
public Transform buttonParent; // ボタンを並べる親(例: Empty + VerticalLayoutGroup)
public GameObject buttonPrefab; // ボタンのPrefab
public List<Enemy> enemies; // シーン上の全Enemy。自動取得する場合はFindObjectsOfTypeも可
void Start()
{
// すべての敵ごとにボタンを生成し、バインド
foreach (var enemy in enemies)
{
CreateEnemyButton(enemy);
}
}
void CreateEnemyButton(Enemy enemy)
{
// ボタンを生成
var buttonObj = Instantiate(buttonPrefab, buttonParent);
var button = buttonObj.GetComponent<Button>();
var label = buttonObj.GetComponentInChildren<TextMeshProUGUI>();
// ボタンテキストに敵の名前を表示
label.text = enemy.gameObject.name + " を攻撃";
// バインド:ラムダ式で敵を引数に渡す
button.onClick.AddListener(() => AttackSelectedEnemy(enemy));
}
// UIから呼ばれる攻撃メソッド
public void AttackSelectedEnemy(Enemy enemy)
{
if (enemy != null)
{
enemy.Attack();
}
}
}
}
【ポイント解説】
- ボタンPrefabを使いまわして、必要な数だけ動的に生成→ エネミーの増減に柔軟に対応できます。
- AddListenerにラムダ式を使うことで、個別のEnemyインスタンスを安全にバインド→ 例えば button.onClick.AddListener(() => AttackSelectedEnemy(enemy)); のように記述。
- ボタンのテキストや見た目も動的に変更可能→ 各エネミー名や状態を反映できます。
- 敵のListを自動取得したい場合は、FindObjectsOfType<Enemy>() なども利用可能
// Unity 6以降推奨のEnemy検索
enemies = FindObjectsByType<Enemy>(FindObjectsSortMode.None).ToList();
【応用例】
- ボタンにアイコンやHPバーなどを追加すれば、よりリッチなUIにも応用できます。
- エネミーを削除した時、ボタンも連動して消すよう管理できます。
- 複数パーティやチーム、グループにも簡単に応用できます。
【LSP(リスコフの置換原則)との関係】
この方式でも、
AttackSelectedEnemy(Enemy enemy) の引数は Enemyの基底型 のままなので、
Goblin, Troll, Dragon などあらゆる派生クラスに柔軟に対応でき、
将来的な拡張・派生クラス追加にも影響を与えません。
【まとめ】
- List<Enemy>をループして、UIボタンを自動生成・バインドすることで、エネミーの増減や拡張に強い設計となる。
- ボタンから各エネミーへのAttack処理も、LSP(リスコフの置換原則)に準拠し、安心して拡張できる。
実運用では、ボタンPrefab・Canvas構成・エネミーのList管理の方針に合わせて、上記の実装例を調整してください。
6. LSP(リスコフの置換原則)との関係
- AttackSelectedEnemy(Enemy enemy) が基底クラス Enemy を受け取ることで、Goblin や Troll、Dragon などの派生クラスを同一の方法で扱い、置き換え可能にしています。
- 新しい Enemy 派生クラスを追加しても、EnemyManager のコードは一切変更不要で機能します。これにより拡張時のコード変更リスクを低減します。
- 各派生クラスは基底クラスの契約(Attack() メソッドの振る舞い)に従って実装する必要があり、これにより予期しない副作用や例外を防ぎ、安定した挙動を保証します。
以上の手順で、Unity 6 環境でも UI のボタンを押すだけで指定したエネミーを攻撃 しつつ、リスコフの置換原則(LSP) を活かした設計が実現できます。
ディスカッション
コメント一覧
まだ、コメントがありません