设计模式与游戏开发:游戏循环中的模板方法、AI行为树中的策略、技能系统中的命令

游戏开发,说白了就是一场与性能和复杂度的博弈。我做了十几年游戏后端和引擎架构,最深的体会是:没有设计模式,游戏代码撑不过三个版本迭代。今天咱们就聊聊三个在游戏开发里高频出现的模式——模板方法、策略、命令。它们分别对应着游戏循环、AI行为树和技能系统这三个核心场景。

一、游戏循环中的模板方法模式

游戏循环是什么?就是那个“接收输入→更新逻辑→渲染画面”的死循环。每个游戏都有,但每个游戏的实现细节又不一样。这时候模板方法模式就派上用场了。

我习惯把游戏循环的骨架固定下来,把可变的部分留给子类去实现。你想想看,这样既保证了循环的稳定性,又给了具体游戏足够的灵活性。

核心思想:父类定义算法骨架,子类重写特定步骤。

Java 示例:游戏循环骨架

public abstract class GameLoop {
    private boolean running = false;

    // 模板方法——固定骨架
    public final void run() {
        init();
        running = true;
        while (running) {
            long startTime = System.nanoTime();
            
            processInput();   // 处理输入
            update();         // 更新逻辑
            render();         // 渲染画面
            
            long elapsed = System.nanoTime() - startTime;
            sleepIfNeeded(elapsed);
        }
        cleanup();
    }

    // 子类必须实现的步骤
    protected abstract void init();
    protected abstract void processInput();
    protected abstract void update();
    protected abstract void render();
    protected abstract void cleanup();

    private void sleepIfNeeded(long elapsed) {
        long targetFrameTime = 16_666_666; // 60 FPS
        if (elapsed < targetFrameTime) {
            try {
                Thread.sleep((targetFrameTime - elapsed) / 1_000_000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

具体游戏只需要继承这个类,实现那几个抽象方法就行。我曾经在一个RPG项目里用这个模式,把PC版和手机版的游戏循环统一了——PC版用垂直同步,手机版用自适应帧率,但骨架完全一样。

避坑指南:我曾经把模板方法写成非final的,结果有个同事直接重写了run()方法,把整个循环逻辑改得面目全非。记住:模板方法一定要加final,别给后人留“发挥空间”。

二、AI行为树中的策略模式

AI行为树,听着高大上,其实核心就是“根据条件选择行为”。但问题来了:行为的选择策略千变万化——有的AI喜欢优先攻击,有的喜欢猥琐发育,有的会根据玩家血量动态调整。

这时候策略模式就特别好用。把“选择行为”这个算法独立出来,让不同的策略类去实现。行为树只负责调用策略,不关心具体怎么选。

C++ 示例:AI行为选择策略

// 策略接口
class IBehaviorStrategy {
public:
    virtual ~IBehaviorStrategy() = default;
    virtual BehaviorNode* selectBehavior(Blackboard* board) = 0;
};

// 具体策略:攻击优先
class AggressiveStrategy : public IBehaviorStrategy {
public:
    BehaviorNode* selectBehavior(Blackboard* board) override {
        if (board->getHealth() > 50) {
            return new AttackNode();
        }
        return new DefendNode();
    }
};

// 具体策略:猥琐发育
class DefensiveStrategy : public IBehaviorStrategy {
public:
    BehaviorNode* selectBehavior(Blackboard* board) override {
        if (board->getHealth() < 80) {
            return new RetreatNode();
        }
        return new FarmNode();
    }
};

// 行为树节点——使用策略
class BehaviorTree {
private:
    std::unique_ptr<IBehaviorStrategy> strategy_;
public:
    void setStrategy(std::unique_ptr<IBehaviorStrategy> strategy) {
        strategy_ = std::move(strategy);
    }
    
    void tick(Blackboard* board) {
        BehaviorNode* node = strategy_->selectBehavior(board);
        if (node) {
            node->execute(board);
        }
    }
};

嗯,这里要注意:策略模式在AI系统里最大的好处是运行时切换。我记得有个项目,Boss在不同阶段会切换AI策略——第一阶段莽撞攻击,第二阶段猥琐召唤小怪,第三阶段狂暴。用策略模式,只需要在阶段切换时调一下setStrategy()就行,行为树本身不用动。

注意:策略模式不要滥用。如果只有两三种行为选择,用if-else反而更清晰。策略模式适合“行为选择逻辑经常变化”或“需要运行时动态切换”的场景。

三、技能系统中的命令模式

技能系统,是游戏里最复杂的模块之一。一个技能可能涉及:伤害计算、动画播放、音效触发、冷却管理、状态效果……而且还要支持撤销(比如玩家反悔)、回放(比如观战模式)、队列(比如连招)。

命令模式简直就是为这种场景量身定做的。把每个技能封装成一个命令对象,包含执行所需的所有信息。然后由调用者统一管理这些命令的调度、撤销和记录。

Java 示例:技能命令系统

// 命令接口
public interface SkillCommand {
    void execute();
    void undo();
}

// 具体技能:火球术
public class FireballCommand implements SkillCommand {
    private Player caster;
    private Target target;
    private int damage;
    private int manaCost;

    public FireballCommand(Player caster, Target target) {
        this.caster = caster;
        this.target = target;
        this.damage = 50;
        this.manaCost = 30;
    }

    @Override
    public void execute() {
        if (caster.getMana() >= manaCost) {
            caster.spendMana(manaCost);
            target.takeDamage(damage);
            System.out.println("火球术!造成 " + damage + " 点伤害");
        }
    }

    @Override
    public void undo() {
        target.heal(damage);
        caster.addMana(manaCost);
        System.out.println("撤销火球术");
    }
}

// 命令管理器——支持撤销和回放
public class SkillManager {
    private Stack<SkillCommand> history = new Stack<>();
    private List<SkillCommand> replayBuffer = new ArrayList<>();

    public void executeCommand(SkillCommand cmd) {
        cmd.execute();
        history.push(cmd);
        replayBuffer.add(cmd);
    }

    public void undoLastCommand() {
        if (!history.isEmpty()) {
            SkillCommand cmd = history.pop();
            cmd.undo();
        }
    }

    public void replay() {
        for (SkillCommand cmd : replayBuffer) {
            cmd.execute();
        }
    }
}

我个人特别喜欢命令模式的一点是:它把“请求”和“执行”完全解耦了。客户端只需要创建命令对象,至于谁来执行、怎么执行、要不要记录,那是调用者的事。我在一个MOBA项目里用这个模式实现了技能连招系统——玩家按顺序释放技能,命令管理器把它们排队,然后按顺序执行,中间还可以插入取消操作。

小技巧:命令对象可以序列化。我曾经用这个特性实现了“战斗回放”功能——把每帧执行的命令序列化到文件里,观战时反序列化再执行一遍。比录视频省空间多了。

四、三者关系与整体架构

这三个模式在游戏里不是孤立的。我画了一张图,帮你理清它们之间的关系:

游戏循环(模板方法模式) init() → processInput() → update() → render() → cleanup() 固定骨架,子类实现具体步骤 AI行为树(策略模式) BehaviorTree → IBehaviorStrategy → selectBehavior() 运行时切换策略,行为树不关心具体选择逻辑 技能系统(命令模式) SkillCommand → execute() / undo() → SkillManager 请求与执行解耦,支持撤销、回放、队列

从这张图你能看出来:游戏循环是顶层调度器,它驱动整个游戏的运转;在update()阶段,AI行为树根据策略选择行为;而技能系统则作为行为的具体实现,通过命令模式管理技能的释放和撤销。三层各司其职,又紧密配合。

总结一下:
  • 模板方法——固定游戏循环骨架,避免重复代码,保证帧率稳定
  • 策略模式——让AI行为选择灵活可变,支持运行时动态切换
  • 命令模式——把技能封装成对象,解耦请求与执行,支持撤销和回放

这三个模式在游戏开发里几乎是标配。我建议你在设计游戏架构时,优先考虑它们——不是为了用模式而用模式,而是因为它们确实能解决实际问题。下次写游戏循环、AI或者技能系统时,不妨试试看。


公众号:蓝海资料掘金营,微信deep3321