使用命令模式实现《植物大战僵尸》兵营生产系统
在游戏开发中,命令模式(Command Pattern) 是一种非常实用的设计模式,尤其适用于需要将请求封装为对象、支持撤销操作、或实现请求队列的场景。本文将以经典游戏《植物大战僵尸》中的"兵营生产植物战士"机制为例,使用 命令模式 实现一个简洁而可扩展的生产系统。

🌱 场景需求
- 玩家点击界面上的"图片按钮",向兵营发出生产命令。
- 兵营接收到命令后,每生产一个植物战士需冷却一段时间(如 200ms 或 500ms)。
- 本关只需实现 一种植物战士(豌豆射手) 的生产逻辑。
- 要求使用 命令模式 来解耦"界面按钮(发送者)"、"兵营(调用者)"和"植物生产(接收者)"。
🔧 命令模式核心角色
命令模式包含以下关键组件:
| 角色 | 说明 |
|---|---|
| Command(命令接口) | 声明执行操作的接口(如 Execute()) |
| ConcreteCommand(具体命令) | 实现 Execute(),持有对 Receiver 的引用 |
| Invoker(调用者) | 持有命令对象,负责触发命令执行(如兵营) |
| Receiver(接收者) | 执行实际业务逻辑的对象(如 Peashooter) |
| Client(客户端) | 创建命令、接收者和调用者,并建立关联 |
注意:在本题中,由于
Peashooter的构造函数直接输出日志,因此 具体命令类无需显式持有接收者引用,但结构上仍符合命令模式。
💻 代码实现解析
1. 抽象命令类 Commands.java
java
public abstract class Commands {
public abstract void Execute();
}
定义统一的执行接口。
2. 具体命令类 ProduceCommand.java
java
public class ProduceCommand extends Commands {
@Override
public void Execute() {
new Peashooter(); // 生产豌豆射手
}
}
- 每次执行
Execute()就会创建一个Peashooter对象。 - 构造函数中自动打印
"豌豆射手生产出来了"。
3. 调用者类 CampInvokers.java
java
import java.util.ArrayList;
import java.util.List;
public class CampInvokers {
private final List<Commands> commands = new ArrayList<>();
private final long TrainTimer; // 冷却时间
private long timer; // 上次生产完成的时间戳
public CampInvokers(long trainTime) {
TrainTimer = trainTime;
timer = 0;
}
public void Train() {
commands.add(new ProduceCommand()); // 添加生产命令到队列
}
public void ExecuteCommand() {
if (commands.isEmpty()) return;
// 检查是否过了冷却时间
if (timer + TrainTimer <= System.currentTimeMillis()) {
commands.get(0).Execute(); // 执行队首命令
commands.remove(0); // 移除已执行命令
timer = System.currentTimeMillis(); // 更新冷却计时起点
}
}
public void CancelTrainCommand() {
if (!commands.isEmpty()) {
commands.remove(commands.size() - 1);
if (commands.isEmpty()) {
timer = System.currentTimeMillis();
}
}
}
}
- 使用 命令队列 缓存玩家多次点击。
ExecuteCommand()在冷却结束后才执行队列中的命令。- 完美模拟了"点击快但生产慢"的游戏体验。
4. 接收者:Peashooter.java
java
public class Peashooter implements IBotany {
public Peashooter() {
System.out.println("豌豆射手生产出来了");
}
}
- 构造即代表"生产完成"。
- 符合游戏设计中"实例化即部署"的常见逻辑。
5. 客户端 Client.java
java
public class Client {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt(); // 点击次数
long traintime = scanner.nextLong(); // 冷却时间(ms)
long currtime = System.currentTimeMillis();
CampInvokers camp = new CampInvokers(traintime);
// 模拟玩家快速点击 N 次
for (int i = 0; i < number; i++) {
camp.Train();
}
// 在 1000ms 内持续尝试执行命令(受冷却限制)
while (true) {
camp.ExecuteCommand();
if (currtime + 1000 < System.currentTimeMillis())
break;
}
}
}
- 玩家在瞬间发出 6 次命令。
- 系统在 1 秒内尽可能多地执行(受限于冷却时间)。
🧪 测试示例
输入:6 200
- 冷却 200ms → 1000ms 内最多生产
floor(1000/200) = 5个 - 输出 5 行
"豌豆射手生产出来了"
输入:6 500
- 冷却 500ms → 最多生产 2 个
- 输出 2 行
注意:首次生产从
timer=0开始,所以第一次立即执行,之后每次间隔traintime。
✅ 命令模式的优势体现
- 解耦:界面按钮(发送者)无需知道兵营如何生产,只需发出命令。
- 可扩展 :未来可轻松添加
SunflowerCommand、WallnutCommand等。 - 支持队列与延迟执行 :命令可排队、取消(已有
CancelTrainCommand)。 - 易于测试与维护:每个组件职责单一。
📌 总结
通过命令模式,我们将"玩家点击"这一行为抽象为可存储、可调度的命令对象,使兵营的生产逻辑更加灵活、健壮。这种设计不仅适用于游戏开发,在 GUI 应用、事务处理、宏命令等场景中也极为常见。