游戏编程模式11-行为模式-字节码

行为模式-字节码

参考章节:https://gpp.tkchu.me/bytecode.html

脑内画面

字节码模式把行为压缩成一串小指令,由游戏里的虚拟机解释执行。它像给设计师一套安全的积木:不能直接改引擎代码,但可以组合"取数、相加、造成伤害、生成效果"这些受控动作。
#mermaid-svg-Ea1r01iOalxwX6o8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ea1r01iOalxwX6o8 .error-icon{fill:#552222;}#mermaid-svg-Ea1r01iOalxwX6o8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ea1r01iOalxwX6o8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ea1r01iOalxwX6o8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ea1r01iOalxwX6o8 .marker.cross{stroke:#333333;}#mermaid-svg-Ea1r01iOalxwX6o8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ea1r01iOalxwX6o8 p{margin:0;}#mermaid-svg-Ea1r01iOalxwX6o8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 .cluster-label text{fill:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 .cluster-label span{color:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 .cluster-label span p{background-color:transparent;}#mermaid-svg-Ea1r01iOalxwX6o8 .label text,#mermaid-svg-Ea1r01iOalxwX6o8 span{fill:#333;color:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 .node rect,#mermaid-svg-Ea1r01iOalxwX6o8 .node circle,#mermaid-svg-Ea1r01iOalxwX6o8 .node ellipse,#mermaid-svg-Ea1r01iOalxwX6o8 .node polygon,#mermaid-svg-Ea1r01iOalxwX6o8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ea1r01iOalxwX6o8 .rough-node .label text,#mermaid-svg-Ea1r01iOalxwX6o8 .node .label text,#mermaid-svg-Ea1r01iOalxwX6o8 .image-shape .label,#mermaid-svg-Ea1r01iOalxwX6o8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ea1r01iOalxwX6o8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ea1r01iOalxwX6o8 .rough-node .label,#mermaid-svg-Ea1r01iOalxwX6o8 .node .label,#mermaid-svg-Ea1r01iOalxwX6o8 .image-shape .label,#mermaid-svg-Ea1r01iOalxwX6o8 .icon-shape .label{text-align:center;}#mermaid-svg-Ea1r01iOalxwX6o8 .node.clickable{cursor:pointer;}#mermaid-svg-Ea1r01iOalxwX6o8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ea1r01iOalxwX6o8 .arrowheadPath{fill:#333333;}#mermaid-svg-Ea1r01iOalxwX6o8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ea1r01iOalxwX6o8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ea1r01iOalxwX6o8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ea1r01iOalxwX6o8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ea1r01iOalxwX6o8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ea1r01iOalxwX6o8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ea1r01iOalxwX6o8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ea1r01iOalxwX6o8 .cluster text{fill:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 .cluster span{color:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ea1r01iOalxwX6o8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ea1r01iOalxwX6o8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ea1r01iOalxwX6o8 .icon-shape,#mermaid-svg-Ea1r01iOalxwX6o8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ea1r01iOalxwX6o8 .icon-shape p,#mermaid-svg-Ea1r01iOalxwX6o8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ea1r01iOalxwX6o8 .icon-shape .label rect,#mermaid-svg-Ea1r01iOalxwX6o8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ea1r01iOalxwX6o8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ea1r01iOalxwX6o8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ea1r01iOalxwX6o8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 技能配置/脚本
编译器或导出器
字节码数组
虚拟机
受控游戏 API

它解决的问题

当玩法行为变化很频繁,而每次都让程序员改 C#、重新编译、发版本,迭代会变慢。字节码把行为变成数据,由虚拟机执行,既保留一定灵活性,又能限制脚本能做什么。

C# 示例

csharp 复制代码
public enum OpCode : byte
{
    PushConst,
    Add,
    Damage,
    End
}

public sealed class SpellVm
{
    private readonly Stack<int> _stack = new();

    public void Run(byte[] code, ICombatApi api, int casterId, int targetId)
    {
        var ip = 0;

        while (ip < code.Length)
        {
            var op = (OpCode)code[ip++];

            switch (op)
            {
                case OpCode.PushConst:
                    _stack.Push(code[ip++]);
                    break;

                case OpCode.Add:
                    _stack.Push(_stack.Pop() + _stack.Pop());
                    break;

                case OpCode.Damage:
                    var amount = _stack.Pop();
                    api.Damage(casterId, targetId, amount);
                    break;

                case OpCode.End:
                    return;

                default:
                    throw new InvalidOperationException($"Unknown opcode: {op}");
            }
        }
    }
}

public interface ICombatApi
{
    void Damage(int casterId, int targetId, int amount);
}

// 示例:Push 20, Push 5, Add, Damage, End
byte[] fireSpell =
{
    (byte)OpCode.PushConst, 20,
    (byte)OpCode.PushConst, 5,
    (byte)OpCode.Add,
    (byte)OpCode.Damage,
    (byte)OpCode.End
};

什么时候用

  • 大量行为需要数据驱动,并且会频繁迭代。
  • 希望设计师或工具链能生成行为。
  • 需要把行为放进沙箱,避免直接执行任意代码。
  • 行为性能要求中等,不在最核心热路径。

使用时的锋利边

字节码系统会自然长成一门语言。指令、调试器、错误提示、版本兼容、工具链都会成为长期成本。只有当数据驱动行为的收益足够大时,才值得引入。