项目希望能够实现一些剧情动画,类似角色移动,镜头变化,台词展现等.剧情动画这东西随时需要修改调整,不能写死在代码里.考虑之后认为需要做一个简单的DSL来定制剧情脚本,策划在脚本里按顺序写入命令,然后我们解释命令执行即可.
项目的很多功能系统并没有能够实现导入lua中,非我所能决定,若可以则使用lua方便不少.因此我决定使用C++来制作这个剧情脚本DSL.
使用boost的spirit来负责脚本的解析,使用asio的coroutine简化了指令处理逻辑.
DSL当然不能太复杂,第一个版本看起来类似:
role_walk LEFT 100; role_dialog "stop!!!" 4; role_jump FORWARD; role_walk RIGHT 200; monster_dialog "byebye!!!" 2; monster_run RIGHT 400 role_walk RIGHT 500; role_jump BACK;
稍加按上边指令流程走下来,会发现一些指令是有延时性的.比如走,跑等,都需要移动到目标地点才算结束.当遇到这个指令时,我们是继续往下解析指令,还是在当前指令阻塞呢?遇到指令立即解析执行,那很可能在一帧里就把脚本的所有指令都执行完毕了,本来30秒的剧情在不到1/60秒里结束了.如果遇到延时性指令立即阻塞呢,会遇到可能有几条延时性指令同时开始的场景.因此决定再加上一个规则,使用方括号括起来的脚本指令,将强制同时执行,第二版本如下:
role_walk LEFT 100; role_dialog "stop!!!" 4; role_jump FORWARD; role_walk RIGHT 200; monster_dialog "byebye!!!" 2; [ monster_run RIGHT 400 role_walk RIGHT 500; ] role_jump BACK;
至此我认为脚本的规则能适应足够多场景了.该脚本暂不需要控制结构,控制条件在脚本进行时都预先知道了.
这是脚本解析代码.
#ifndef __MovieCommandAST_H__ #define __MovieCommandAST_H__ #include <boost/fusion/include/adapt_struct.hpp> #include <boost/variant/variant.hpp> #include <boost/variant/recursive_variant.hpp> #include <boost/fusion/include/std_pair.hpp> namespace MovieScript { typedef boost::variant<std::string, int, float> ArgType; typedef std::vector<ArgType> ArgList; namespace Parser { struct command_atom { std::string cmd; ArgList args; command_atom():cmd("") {} }; struct command_flow; typedef boost::variant<boost::recursive_wrapper<command_flow>, command_atom> command_unit; typedef std::list<command_unit> CommandUnitList; struct command_flow { CommandUnitList cmd_flow; }; } } BOOST_FUSION_ADAPT_STRUCT ( MovieScript::Parser::command_atom, (std::string, cmd) (MovieScript::ArgList, args) ) BOOST_FUSION_ADAPT_STRUCT ( MovieScript::Parser::command_flow, (MovieScript::Parser::CommandUnitList, cmd_flow) ) #endif