开源:可嵌入Unity游戏的BASIC语言
春节期间,撸了不少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
{
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代码里就可以简单调用:
Game.AddBall(x, y, z)(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就好。 这个必须赞!如果有时间,我都想在Unity里复刻一个80年代的Laser310了。不过似乎反射机制在ios是被禁止的,有没有试过水果机? Laser 310 的主意很棒呀~ 沿着这个主意往下想,用 C# 搞个 Z80 simulator不是啥难事,就差不多能完美复刻了~
Reflection 是另一件事。上面这坨代码,并没考虑 iOS,因为要在游戏里编程总要有方便一些的键盘输入,iOS 优先级比桌面稍低。
iOS 里不能用的 Reflection 似乎只是代码生成相关的部分,参见:
Limitations of Xamarin.iOS - Xamarin
我之前没详细测过。 单纯反射是没有问题的 暴露了年龄[害羞]
页:
[1]