0x06.策略模式
策略模式(Strategy Pattern),解决的是“行为和算法的多样性”问题。
在 Unity 开发中,策略模式是用来消灭长篇大论的 if-else 或 switch 语句的神兵利器。
💡 为什么要用策略模式?
假设你正在做一款射击游戏,玩家可以切换不同的武器(手枪、散弹枪、激光枪)。
新手的灾难写法:
public class PlayerWeapon : MonoBehaviour
{
public enum WeaponType { Pistol, Shotgun, Laser }
public WeaponType currentWeapon;
public void Fire()
{
switch (currentWeapon)
{
case WeaponType.Pistol:
Debug.Log("发射单发子弹!");
break;
case WeaponType.Shotgun:
Debug.Log("发射扇形散弹!");
break;
case WeaponType.Laser:
Debug.Log("发射持续激光!");
break;
}
}
}
这种写法的缺点很明显:如果你要加 10 种新武器,这个 Fire 方法会变得极其臃肿。更糟糕的是,如果你想给散弹枪增加“击退效果”,你得在这个庞大的类里面修改,很容易把其他武器的代码弄坏(违背了开闭原则)。
策略模式的优雅解法:
把每一种“开火行为(策略)”单独封装成一个类。玩家(PlayerWeapon)不需要知道具体是怎么开火的,它手里只要拿着一个“开火策略”,调用开火就行了。随时可以把手里的策略替换掉!
一、 经典的纯 C# 接口写法
这是最标准的面向对象策略模式写法,通过 C# 的 interface 来实现。
1. 定义策略接口 (定义规则)
// 所有的开火行为,都必须实现这个接口
public interface IShootStrategy
{
void Shoot(Transform firePoint);
}
2. 实现具体的策略 (把具体算法封装起来)
using UnityEngine;
// 策略 A:手枪单发
public class PistolStrategy : IShootStrategy
{
public void Shoot(Transform firePoint)
{
Debug.Log("手枪开火:在 " + firePoint.position + " 生成了一发子弹");
// 这里写具体的 Instantiate 逻辑
}
}
// 策略 B:散弹枪
public class ShotgunStrategy : IShootStrategy
{
public void Shoot(Transform firePoint)
{
Debug.Log("散弹开火:在 " + firePoint.position + " 生成了 5 发扇形子弹");
}
}
3. 使用策略的上下文 (玩家武器控制器)
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public Transform firePoint;
// 玩家当前持有的策略
private IShootStrategy _currentShootStrategy;
void Start()
{
// 默认装备手枪
EquipWeapon(new PistolStrategy());
}
void Update()
{
// 随时可以在运行时切换策略!
if (Input.GetKeyDown(KeyCode.Alpha1)) EquipWeapon(new PistolStrategy());
if (Input.GetKeyDown(KeyCode.Alpha2)) EquipWeapon(new ShotgunStrategy());
// 执行策略
if (Input.GetKeyDown(KeyCode.Space))
{
// 玩家不需要管具体是怎么开火的,让策略自己去执行
_currentShootStrategy?.Shoot(firePoint);
}
}
// 切换策略的方法
public void EquipWeapon(IShootStrategy newStrategy)
{
_currentShootStrategy = newStrategy;
Debug.Log("切换了新武器!");
}
}
二、 🌟 Unity 开发者特供版:ScriptableObject 策略 (强烈推荐)
上面的纯接口写法有一个问题:C# 的 interface 在 Unity 的 Inspector 面板里是无法直接拖拽赋值的! 这让策划和美术很难受。
在 Unity 中,实现策略模式最强大、最原生的方式是使用 ScriptableObject。它不仅能完美实现策略模式,还能让策划直接在面板上像搭积木一样配置技能和武器!
1. 定义策略基类 (继承 ScriptableObject)
using UnityEngine;
// 注意这里不再是 interface,而是继承自 ScriptableObject 的抽象类
public abstract class ShootStrategySO : ScriptableObject
{
// 可以配置一些通用参数,比如攻击力、冷却时间
public int damage;
public float fireRate;
// 抽象的开火方法,子类必须实现
public abstract void Shoot(Transform firePoint);
}
2. 实现具体的策略资产
using UnityEngine;
// 添加这个标签,你就可以在 Project 右键菜单里创建这个策略的资产文件了!
[CreateAssetMenu(fileName = "New Pistol Strategy", menuName = "Weapons/Pistol Strategy")]
public class PistolStrategySO : ShootStrategySO
{
public GameObject bulletPrefab; // 直接把预制体拖给这个策略
public override void Shoot(Transform firePoint)
{
Debug.Log($"手枪开火,造成 {damage} 点伤害");
Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
}
}
[CreateAssetMenu(fileName = "New Shotgun Strategy", menuName = "Weapons/Shotgun Strategy")]
public class ShotgunStrategySO : ShootStrategySO
{
public GameObject pelletPrefab;
public int pelletCount = 5; // 散弹枪特有的参数
public override void Shoot(Transform firePoint)
{
Debug.Log($"散弹枪开火,发射 {pelletCount} 发弹丸");
// 具体的扇形发射逻辑...
}
}
3. 在玩家代码中使用 (策划狂喜)
using UnityEngine;
public class PlayerShooter : MonoBehaviour
{
public Transform firePoint;
// 现在这个策略可以在 Inspector 面板里直接拖拽了!
public ShootStrategySO currentWeaponStrategy;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && currentWeaponStrategy != null)
{
// 直接调用当前 SO 资产里写好的逻辑
currentWeaponStrategy.Shoot(firePoint);
}
}
}
这种 Unity 特供版写法的爽点在哪?
- 策划可以在项目里右键创建出各种各样的武器策略文件(资产)。
- 想让哪个怪物或者玩家用哪把武器?直接把对应的策略资产拖拽到它身上的
currentWeaponStrategy槽位里就行了,一行代码都不用改! - 完美实现了代码和数据的分离。
💡 总结
-
核心思想: 将变化的行为(怎么移动、怎么开火、怎么算分数)提取出来,封装成独立的类,互相替换。
-
适用场景: * 敌人的 AI 状态(巡逻策略、追击策略、逃跑策略)。
-
技能释放逻辑(火球术、冰冻术)。
-
伤害计算公式(普通攻击计算、暴击计算、护甲穿透计算)。
-
避坑指南: 如果你的行为只有两种(比如简单的开门和关门),直接用
if-else就行了,不要强行写一堆策略类,避免过度设计导致文件数量爆炸。只有当行为种类繁多,且未来极大概率会继续增加时,策略模式才是你的救星。