自定义游戏脚本语言的解析与执行器设计

自定义游戏脚本语言的解析与执行器设计


一、引言

在大型游戏项目中,我们常常需要灵活控制角色行为、任务逻辑、剧情流程等。此时,硬编码显得繁琐且缺乏扩展性。自定义脚本语言的引入可以解决:

  • 游戏策划可编写/调试行为逻辑
  • 逻辑热更新(无需重新编译)
  • 支持表达式、控制流与函数调用

本篇将从底层出发,用 C++ 构建一个简单的 解释型脚本语言,具备 词法分析、语法解析、字节码生成、虚拟机执行 四个核心部分,并以 RPG 任务系统为例进行实践。


二、目标语言功能设定

我们构建的脚本语言支持如下功能:

function attack(target) {
    if (target.hp > 0) {
        say("攻击 " + target.name);
        target.hp = target.hp - 10;
    } else {
        say("敌人已被击败!");
    }
}

支持:

  • 变量定义 / 修改
  • 表达式运算
  • 条件分支 / 循环
  • 函数定义与调用
  • 内建函数(如 say())

三、架构设计概览

脚本源码
词法分析器
语法分析器
AST 抽象语法树
字节码生成器
虚拟机执行器
游戏事件

四、词法分析器(Lexer)

将脚本文本拆分为 Token 流:

enum class TokenType {
    Identifier, Number, String,
    Function, If, Else, Return,
    LeftParen, RightParen, Plus, Minus, Equal,
    Eof
};

struct Token {
    TokenType type;
    std::string value;
};

示例源码:

attack("Orc")

词法输出:

[
  { "Identifier": "attack" },
  { "LeftParen": "(" },
  { "String": "Orc" },
  { "RightParen": ")" }
]

五、语法分析器(Parser)

将 Token 序列转为 AST:

struct ASTNode {
    virtual ~ASTNode() = default;
};

struct FunctionCall : ASTNode {
    std::string name;
    std::vector<std::shared_ptr<ASTNode>> args;
};

struct IfStatement : ASTNode {
    std::shared_ptr<ASTNode> condition;
    std::vector<std::shared_ptr<ASTNode>> thenBlock;
    std::vector<std::shared_ptr<ASTNode>> elseBlock;
};

语法树结构图:

FunctionCall: attack
String: Orc

六、字节码生成器

AST 转换为栈式字节码:

enum class OpCode {
    PushConst, LoadVar, StoreVar,
    Add, Sub, Equal,
    JumpIfFalse, Jump, CallFunc, Return
};

struct Instruction {
    OpCode opcode;
    std::vector<std::string> operands;
};

示例代码:

say("敌人已被击败")

字节码指令:

PushConst "敌人已被击败"
CallFunc say 1

七、虚拟机执行器(VM)

解释执行字节码指令:

class VM {
    std::vector<Value> stack;
    std::unordered_map<std::string, Value> vars;
public:
    void run(const std::vector<Instruction>& code) {
        for (size_t ip = 0; ip < code.size(); ++ip) {
            const auto& instr = code[ip];
            switch (instr.opcode) {
                case OpCode::PushConst:
                    stack.push_back(Value(instr.operands[0]));
                    break;
                case OpCode::CallFunc:
                    if (instr.operands[0] == "say") {
                        std::cout << ">> " << stack.back().toString() << std::endl;
                        stack.pop_back();
                    }
                    break;
            }
        }
    }
};

执行结果:

>> 敌人已被击败

八、运行时变量与作用域

支持函数作用域、全局变量与栈帧:

Global Scope
Function Scope
Local Stack

九、示例:RPG 战斗逻辑脚本

脚本:

function battle(target) {
    if (target.hp > 0) {
        say("攻击 " + target.name);
        target.hp = target.hp - 5;
        if (target.hp <= 0) {
            say("击败敌人!");
        }
    }
}

编译后字节码:

LoadVar target.hp
PushConst 0
Greater
JumpIfFalse L1
LoadVar target.name
PushConst "攻击 "
Add
CallFunc say 1
LoadVar target.hp
PushConst 5
Sub
StoreVar target.hp
LoadVar target.hp
PushConst 0
LessEqual
JumpIfFalse L2
PushConst "击败敌人!"
CallFunc say 1
L2:
L1:

十、高级功能扩展建议

功能说明
支持数组与字典类型脚本中访问复杂数据结构
热重载与调试支持实时修改脚本,断点单步调试
支持 async/await实现协程式脚本逻辑控制
脚本与 C++ 互操作内建函数可调用游戏 C++ 方法

十一、脚本语言与游戏引擎集成

双向通信接口:

void RegisterFunction(const std::string& name, std::function<void(Value)> func);

RegisterFunction("say", [](Value v) {
    std::cout << "角色说:" << v.toString() << std::endl;
});

脚本控制游戏事件:

VM vm;
vm.run(loadScript("battle.txt"));

十二、总结与展望

我们构建了一个拥有完整语法解析、字节码编译、虚拟机执行能力的脚本语言框架,它可以:

  • 轻量快速
  • 嵌入游戏引擎
  • 适配任务系统、NPC行为、战斗 AI 等场景
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻口味

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值