|
春节期间,撸了不少Unity游戏源码。如果想在Unity游戏里内置一个编程环境——比如,玩家可以直接修改NPC的逻辑,或在游戏里通过脚本来修房子,或者小朋友可以在游戏里学编程——目前比较标准的选择包括:
MoonSharp:这是Lua语言的C#实现,https://www.moonsharp.org/MiniScript:一个专门为Unity C#环境设计的简化版编程语言,MiniScript Home PageDynamic C#: 一个在C# runtime里跑C#的实现,Dynamic C# | Integration | Unity Asset Store等等……当然,类似的选择不是太多。主要是因为游戏内支持脚本编程的需求不算大,大多数编程语言在选择解释器或编译器的实现方式时,也较少考虑C#。
纯粹为了好玩,我春节时花了两三周时间,把微软的Small Basic编程语言(Small Basic)改写成了一个Unity内可用的版本,算是丰富一下游戏内脚本语言的选择吧。开源在这里:
改造后的语言起名叫Interactive Small Basic (ISB)。与原版的Small Basic相比,主要区别有
(1)可以方便地在命令行、脚本环境使用。比如可以当计算器使:- ] 308 + 73.5 * 2
- 455.0
- ] 88 mod 7
- 4
复制代码 可以逐行执行:- ] a = 10
- ] a
- 10
- ] for i = 1 to a
- > print(math.sin(i))
- > endfor
- 0.841470984807896
- 0.909297426825682
- 0.141120008059867
- -0.756802495307928
- -0.958924274663138
- -0.279415498198926
- 0.656986598718789
- 0.989358246623382
- 0.412118485241757
- -0.54402111088937
复制代码 (2)删掉了原版Small Basic代码里所有冗长、复杂的“基于XML的数据驱动编程模式”,scanner,parser,runtime等模块里的大部分逻辑,都由代码本身自洽地解决,不需要外部XML来定义。
(3)通过C#的反射机制,很容易给ISB增加适应某个新环境的扩展库。比如想在Unity环境里,为ISB语言增加一个“创建小球”的扩展库,就很简单地实现一个C# class就好,没有任何复杂的注册或接口声明逻辑:- public class Game
- {
- [Doc("Example lib function to access Unity objects.")]
- public void AddBall(NumberValue x, NumberValue y, NumberValue z)
- {
- GameObject prefab = Resources.Load<GameObject>(&#34;Prefabs/Sphere&#34;);
- if (prefab != null)
- {
- Object.Instantiate(prefab,
- new Vector3((float)x.ToNumber(), (float)y.ToNumber(), (float)z.ToNumber()),
- Quaternion.identity);
- }
- else
- {
- Debug.Log(&#34;Failed to load prefab.&#34;);
- }
- }
- }
复制代码 上面的扩展库实现后,启动ISB runtime时给出类名,ISB代码里就可以简单调用:(4)scanner,parser到runtime之间,我引入了一个实验性质的IR(中间表示)层。ISB的runtime里实际执行的是这个中间层的指令。这个中间层的语法类似WebAssembly,纯粹是为了展示ISB未来有能力移植到各种不同的运行环境,或者类似的runtime未来有能力在Unity游戏里运行各种不同的编程语言。
实现这个中间层时,不但没做任何优化(所以,别跟我说编译原理教科书里教的东西都比我实现的多),还刻意留了许多冗余实现(比如编译后的中间代码里随处可见的nop)——主要是为了让这个实验层级更简单,不涉及任何复杂逻辑,能跑通就好。
可以通过命令行把BASIC语言代码编译成ISB中间代码,例如:- dotnet run --project ISB.Shell -- -i ../examples/fibonacci.bas -c
复制代码 在Unity游戏里引入ISB的方法可以参加一个示例程序:
这个示例游戏使用Unity GUI组件输入代码,按按钮后就简单启动ISB引擎,解释并执行代码:- public class Program : MonoBehaviour
- {
- public Button uiButton;
- public InputField uiInput;
- public GameObject objBall;
- void Start()
- {
- uiButton.onClick.AddListener(onButtonClick);
- }
- void Update()
- {
- }
- void onButtonClick()
- {
- string code = uiInput.text;
- Debug.Log(code);
- Engine engine = new Engine(&#34;Unity&#34;, new Type[] { typeof(Game) });
- if (engine.Compile(code, true) && engine.Run(true))
- {
- if (engine.StackCount > 0)
- {
- string ret = engine.StackTop.ToDisplayString();
- Debug.Log(ret);
- }
- }
- else
- {
- foreach (var content in engine.ErrorInfo.Contents)
- {
- Debug.Log(content.ToDisplayString());
- }
- }
- }
- }
复制代码 在这个示例游戏里,用Basic语言批量创建小球并看着小球弹跳的画面见下面的动图:
写在最后的善意提醒:不要拿这份代码当编译原理的学习参考。原因:这份代码里的很多逻辑继承自Microsoft Small Basic的源码——说实话,那份源码并不是一份高质量的编译器范本,顶多是几个熟练码农在20%时间做的非常工程化的实用项目。我新增的代码逻辑,又都是为了Unity的使用环境或实现方便做了很多trade-off的东西。
要学习编译原理,还是参考经典教材如龙书,经典课程如斯坦福CS143就好。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|