小话游戏脚本(二)

小话游戏脚本(二)

二.一种基于命令的脚本语言

通过上面的讲述,我们对于基于命令的脚本也算有了一些感性的认识,之后么就是这次我认为比较关键的部分:设计一个基于命令的脚本语言。

由于本人写作此文的目的也是为了学习,所以暂时仅仅试探性的给出一些想法,至于正不正确、可不可行着实不能回答,而且,世上也不存在一蹴而就的完美设计,我们能做的也仅仅是一步步的完善罢了,所以本着迭代改进的原则,让我开始以下的信口开河:)

1.语言特性概述

首先谈谈一些语言周边的内容:

.是否需要支持注释:软件行业发展至今,注释已经表现的非常重要,尤其是好的注释习惯,已经成为优秀程序员的一项必备素质。现在已经很难找到一种不支持注释的程序语言(当然,一些“诡异”的语言除外),所以我们也没有什么充足理由拒绝对于注释的支持。C 语言支持段注释(/*...*/),C++在此基础上增加了行注释(//...),而后生的Java则又增加了称为文档注释(/**...*/)的第三类注释,与此也可见注释的重要性了:)在此,我的想法是仅仅支持单行注释,并且为了简洁起见,采用 @ 符号(纯粹个人喜好:))为注释的起始符,处理方法是在预编译脚本文件时直接忽略 @ 之后的一行内容(包括 @ )。

.是否需要支持 Include 等类C的预处理操作:对于有一定C/C++编程经验的人一定对他的预处理功能有所接触,也许你未曾揣摩过#ifdef,或者未曾深究过#pragma,但是也一定使用过#include,没有他,简单的控制台IO都会变成一件十分困难的事情:( 所以我们有必要至少考虑一下支持Include的需求。Include能够使我们更加清晰的书写代码,并且能够做到很好的代码复用,但同时,支持Include也需要我们付出不小的代价,首先,我们必须解决Include嵌套的问题,这可能并不是非常棘手,采用递归的表达以及代码的链表表示方式可以解决,但毫无疑问的添加了很多复杂性;在者便是处理Include的相互包含,对于这个问题,一种简单的方法便是保存曾经包含过文件的历史记录,解析过程中对于每个Include的文件进行比对,直接忽略掉重复的文件。(在这点上,除了编译哨岗(#ifndef...#define...#endif)之外,C/C++并没有很好的方法解决Include互相包含的情况,记得曾经用gcc编译一个相当简单的程序,可是每次在编译过程中都会引发abort,当时尽顾着查看代码,却忽略了第一行的#include,后来才发现我Include了文件自身,所以导致堆栈溢出而引发abort,汗...)但实际上,游戏脚本很少会用到Include的这些高级功能,所以对于需不需要支持的问题,仍然是一个适用范围的问题,需要权衡...在此,我选择一种折中的方式,方法是将 IncludeFile "filename" 设定为一项命令,并且可以出现在代码的任何位置,执行的操作也仅是简单的代码替换 :)

.脚本的加载、编译与执行:按照上面的一些讨论,我们对于这个议题已经有了一些了解,现在的讨论则会对其进行进一步的细化,首先,由于代码的数量未知,以及灵活性的考虑,我决定采用链表的形式存储编译后的指令(命令),每条指令由 指令代码 以及 参数列表 组成,而编译的作用也便是将源代码转换成这种指令形式并以链表形式储存起来,至于执行,我们自然可以根据编译后的指令顺序进行,但这里仍有一些问题,首先便是脚本的循环问题,是否我们运行的脚本都只需运行一次,考虑一个RPG游戏中的场景,我们的主角克里斯来到了有“沙漠绿洲”之称的普利特城,里面自然有形形色色的各位NPC,而且其中有很大一部分人为了生计都在四处逛游,很显然,为他们每人编写一份各具特色的脚本是一个明智之举,但是你能想像当运行完一遍自身的脚本之后,城中的各位NPC都好像突然中了最终Boss的时间停止,没有了一点生气,如果是这样,我想我会崩溃的:(所以,我们有必要支持脚本的循环运行,与此同时,上面的例子也暴露了另一个不容忽视的问题,那便是多个脚本的并发执行,也许你会想到依次运行各NPC脚本的方法,但可惜,这离并发仍然还差一步,你能忍受当一个武器商人向你推销钻石之剑的时候,整个世界都莫名其妙的停止了吗,至少我不行:( 而相应的解法则要涉及到多线程的概念,接着便是条件竞争、原子操作、临界区、互斥量、信号量等等概念,在此为了简洁起见,示例仅支持单线程 :)

接着我们可以谈谈数据类型与相关语法了(首先的首先,我将设计的脚本先起个名字,号曰:heScript :) )

.heScript所支持的数据类型:如前所述,heScript一般必须支持的数据类型为整型以及字符串型,但同时,只要做一些简单的扩展,脚本也可以支持布尔型和浮点型,其中对于布尔型的支持,可以扩展整型的表示来达到,方法是遇到 true 以及 false 时将其分别解析成 1 和 0 ,(处于简洁考虑,我个人比较反对对于大小写的区分(例如将 True 以及 true 视为不同)),而对于浮点数的支持,虽然也不繁琐,仅需添加一个解析浮点数的模块,但是就其实用性来说,其实十分有限,所以个人想法不打算在heScript中支持浮点数。而对于常量,则仅仅是将标示符与整数对应的过程,上面谈到的布尔型同样也可以以定义常量的方法来解决,而且这种方法较之上面更加简洁 :)

综上考虑,则heScript所支持的数据类型有:整型,字符串型,以及整型常量。

.heScript所支持的语法:天下所有的程序结构,无外乎以下三种:顺序、分支以及循环,而就基于命令的脚本来说,对顺序结构有其天生的支持,这点显而易见,但实际上,只要我们稍加扩展,简单的模拟分支以及循环也并不复杂。考虑以下的一条命令:

If IsSoundOn Play Stop

其中IsSoundOn是条件,而Play是肯定分支所执行的内容,Stop则是否定分支的内容,但是很显然,分支条件中往往都需要执行多于一条命令的内容,以上的表达显然缺乏这种能力,但是如果我们支持模块(Block)(类似于函数)的定义,即将一系列的命令写在一起并起上一个模块名,那么我们便可以较好的模拟分支,例如如下的命令:

If IsSoundOn PlayBlock StopBlock

其中PlayBlock以及StopBlock都是相应的模块名称,其中可以包括许多操作。模块的引入虽然带来了灵活性,但同时也会引起一些问题,首先便是模块的进入和返回,同调用函数一样,我们需要记忆模块的起始地址以及结束地址,同时还必须在调用模块之前保存程序下一步的运行地址,以使程序正常执行下去;另外的一个问题便是模块间的嵌套问题,同函数嵌套相同,我们需要一个调用堆栈来帮助我们:)总的来说,模块的支持有好有坏,仍然存在权衡问题。

至于循环的支持,大抵同分支相同,只是有一点需要提出,那就是由于heScript并不支持逻辑运算,所以为了方便起见,应该提供两种循环符号(互相补充):While 以及 Until,至于他们的语义我想大家应该一目了然:)

综上所述,heScript支持If、While以及Until的命令,并支持类似函数的Block。而且为了分支及循环的支持,heScript则还应支持整型变量。

.heScript中命令管理:对于一些一般意义上的通用命令,例如:If While Return,我们可以分配一些固定的指令代码,以及编写一些固定的代码,而对于其他的很多与适用领域高度相关的命令,我们可以采用动态注册的方法来进行管理,就面向对象而言,我们可以编写一个脚本命令管理器来充当这个角色,这么做的目的也显而易见,自然是进一步的扩展heScript的灵活性和实用性。

.heScript的一段可能的示例程序:综合上述观点,以下是一段可能的heScript示例程序

*以下为ConstValue.txt文件的内容

IncludeFile "Block.txt"

@ 一些常量定义 :)

DefConst UP 0

DefConst DOWN 1

DefConst LEFT 2

DefConst RIGHT 3

DefConst FALSE 0

DefConst TRUE 1

*以下为Block.txt文件的内容

DefBlock SBlock

ShowMessage "This is in SBlock" 1

Return

@脚本定义终结符

EndBlock

DefBlock NSBlock

ShowMessage "This is in NSBlock" 1

Return

EndBlock

*以下是heScript.Txt的内容

@ This is The Test Script :)

IncludeFile "ConstValue.txt"

DefVar IsNS TRUE

DefVar IsS FALSE

PrintLogo

If IsS SBlock NSBlock

SetVar IsS TRUE

If IsS SBlock NSBlock

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CSDN技术头条

Go语言实践:从新手入门到上线真实的小型服务所遇到的那些坑

Teamwork团队在去年写了近20万行Go代码,建造了一堆速度奇快的小型HTTP服务,本文列出了他们总结的9条经验教训。 为什么选择Go语言?Go语言,又称G...

23370
来自专栏Java帮帮-微信公众号-技术文章全总结

Java高级开发工程师,面试总结

Java高级开发工程师,面试总结 每个人都会有的抉择 时隔两年,再一次的面临离职找工作,这一次换工作有些许的不舍,也有些许的无奈。个人所在的技术团队不错,两年时...

39250
来自专栏wOw的Android小站

[Objective-C] Block实现回调和简单的学习思考

关于Objective-C的回调,最常见的应该是用delegate代理实现。不过代理的实现比起Block要更基础,就不介绍了,下面总结一下Block回调的实现。

12020
来自专栏Golang语言社区

Go语言实践:从新手入门到上线真实的小型服务所遇到的那些坑

摘要: Teamwork团队在去年写了近20万行Go代码,建造了一堆速度奇快的小型HTTP服务,本文列出了他们总结的9条经验教训。 为什么选择Go语言?Go...

38660
来自专栏Golang语言社区

Go语言实践:从新手入门到上线真实的小型服务所遇到的那些坑

摘要: Teamwork团队在去年写了近20万行Go代码,建造了一堆速度奇快的小型HTTP服务,本文列出了他们总结的9条经验教训。 为什么选择Go语言?Go...

35570
来自专栏阮一峰的网络日志

都柏林核心(Dublin Core)

在上一篇日志中,我介绍了元数据(MetaData),并且说只要有一个集合,就可以定义一套元数据。 这样一来,很自然的,我们就会想到一个问题:有没有可能定义一套通...

28470
来自专栏Java后端技术栈

Java代码评审歪诗!让你写出更加优秀的代码!

架构师说, 用20个字描述代码评审的内容, 自省也省人。由于是一字一含义, 不连贯, 为了增强趣味性, 每句都增加对应的歪解。只是对常见评审的描述, 不尽之处,...

10610
来自专栏斑斓

漂亮的with,鱼与熊掌可以兼得

假设要加载磁盘上的一个文件,并以二进制形式读取文件的数据。若要从健壮性的角度考虑,需得考虑两种异常情况: 加载文件失败,例如给定的文件路径并不存在该文件 读取文...

38780
来自专栏青玉伏案

设计模式(八): 从“小弟”中来类比"外观模式"(Facade Pattern)

在此先容我拿“小弟”这个词来扯一下淡。什么是小弟呢,所谓小弟就是可以帮你做一些琐碎的事情,在此我们就拿“小弟”来类比“外观模式”。在上面一篇博文我们完整的介绍了...

239100
来自专栏Golang语言社区

Go语言实战: 编写可维护Go语言代码建议

大家好, 我在接下来的两个会议中的目标是向大家提供有关编写Go代码最佳实践的建议。

23630

扫码关注云+社区

领取腾讯云代金券