Unityにおけるリスコフの置換原則(LSP)適用例
― 敵キャラクターの攻撃処理を安全に拡張する設計 ―
目次
1. 背景と目的
Unityでは継承を多用してさまざまなエンティティ(キャラ、UI、アイテムなど)を表現する場面があります。しかし、誤った継承により動作や契約が壊れると、ゲーム全体の挙動に悪影響を及ぼします。
ここでは「敵キャラクターの攻撃処理」を題材に、LSPを守った実装を行います。
2. 悪い例(LSP違反)
public class Enemy : MonoBehaviour
{
public virtual void Attack()
{
Debug.Log("敵が攻撃!");
}
}
public class PassiveEnemy : Enemy
{
public override void Attack()
{
// 攻撃しない敵なので、例外で未対応を表現
throw new System.NotImplementedException("攻撃できません");
}
}
問題点
- Enemy は「必ず攻撃できる」という契約を持っている。
- しかし PassiveEnemy はその契約を破って例外を投げる。
- List<Enemy> で全体を一括処理するとクラッシュする可能性がある。
3. 良い例(LSPを守る設計)
3.1 共通インターフェースを定義
public interface IAttackable
{
void Attack();
}
3.2 敵キャラの基本クラス
public abstract class Enemy : MonoBehaviour
{
public string enemyName;
public virtual void Move()
{
Debug.Log($"{enemyName} が移動した");
}
}
3.3 攻撃可能な敵
public class Goblin : Enemy, IAttackable
{
public void Attack()
{
Debug.Log($"{enemyName} が剣で攻撃!");
}
}
3.4 攻撃しない敵
public class Slime : Enemy
{
// 攻撃しないが、Move はできる
}
4. 使用例(LSPを守る処理)
public class EnemyManager : MonoBehaviour
{
public List<Enemy> enemies;
void Start()
{
foreach (var enemy in enemies)
{
enemy.Move();
if (enemy is IAttackable attackable)
{
attackable.Attack();
}
else
{
Debug.Log($"{enemy.enemyName} は攻撃できません");
}
}
}
}
実行結果の例
ゴブリンが移動した
ゴブリンが剣で攻撃!
スライムが移動した
スライムは攻撃できません
5. ポイント整理
悪い設計 | 良い設計 |
---|---|
継承元クラスが全機能を前提としている | 振る舞いごとにインターフェースで分離 |
子クラスが契約を破る(例外を投げる) | 子クラスが契約を守る |
型の置換で実行時エラーが起こる | どのクラスでも安全に処理できる |
6. まとめ
- Unityでも「継承だけに頼らず、役割ごとにインターフェースを定義」することで、LSPを守る設計が実現できる。
- とくに「攻撃できる or できない」などの分岐は、IAttackableのような小さなインターフェースを使って区別することが効果的。
- 今後の機能追加時も、既存コードを壊さずに拡張できる。
訪問数 12 回, 今日の訪問数 1回
ディスカッション
コメント一覧
まだ、コメントがありません