这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.com]
继续翻译GameMonkey脚本语言的文章, 这些文章都是在网站上找到的. 在翻译的过程中, 更加深了我对GM的了解和兴趣, 它的协程机制确实比Lua的协程在原生支持方面争强了很多, so enjoy! 上次GM参考手册的翻译放在了一篇文章, 感觉显的太长了, 所以这次我决定将这些长篇翻译分成多篇文章, 这样阅读起来比较方便, 而且可以避免一次信息过大
原文地址:
接上一篇....
脚本线程和'this'
现在你已经探索了GameMonkey的协程模型, 学会了如何使用阻塞来暂停协程, 并且使用信号来唤醒协程继续执行. 现在我们就来学习一下如何让'this' 和协程结合起来, 给你游戏的脚本化提供更强大的支持.
每个协程都可以绑定一个gmVariable, 在协程中通过'this'来访问它. this的概念可以让你高效的将一个object和一个协程绑定起来, 使这个object在协程的整个生命周期中都可以随时被访问. 如果你回顾我写的GameMonkey基础篇会发现我演示了如何使用this将一个object引用到函数调用中, 在GM的协程系统中, 可以使用相同的方法来使用thisglobal my_entity = {
x = 50, y = 100, name = "test" }; global move_ent_left = function() { print("Starting thread – this.name = " + .name); while (this.x > 0) { this.x = this.x – 10; print(this.name + " – x = " + .name); yield(); } print("Ending thread – this.name = " + .name); }; my_entity:thread(move_ent_left); sleep(1);
执行输出:
Starting thread – this.name = test
test – x = 40 test – x = 30 ... Ending thread – this.name = test
在上面的演示中, 创建了一个名为my_entity的简单table, 它拥有x, y, name三个成员变量. 协程函数move_ent_left简单的让entity在x轴上移动十个单位, 这个函数是全局的, 但它不接受任何参数, 因此你不能通过传递进去一个obj去"哄骗"obj移动, 你只能移动和协程绑定的obj.
和以前一样, 还是使用thread接口来创建协程, 这里只有一点和以前不同, 那就是使用了"my_entity::thread(func)"的语法方式将my_entity这个table绑定到协程的this上面.在下一个例子中我将演示使用这种方法在多协程环境下操纵多个对象
global robat = {
x = 50, y = 100, name = "robot" }; global player = { x = 150, y = 200, name = ""player" }; global move_ent_left = function() { print("Starting thread – this.name = " + .name); while (this.x > 0) { this.x = this.x – 10; print(this.name + " – x = " + this.x); yield(); } print("Ending thread – this.name = " + .name); }; robot:thread(move_ent_left); player:thread(move_ent_left); sleep(1);
执行输出:
Starting thread – this.name = robot
robot – x = 40 Staring thread – this.name = player player – x = 140 ... Ending thread – this.name = robot player – x = 90 player – x = 80 ... Ending thread – this.name = player
灵活的使用协程和this的组合非常有用, 对于不同的实体对象, 只要它们拥有相同的属性, 就可以给他们指定不同的行为. 你所需要做的就是像上面这样, 传递不同的函数给协程. 在下面的例子中, 我们定义5个robots, 并且在运行时赋予它们不同的行为.
global create_robot = function(x, y, name) {
return {x = x, y = y, name = name, id, class = "robot"}; }; global behaviour_stupid = function() { print(.name + "is acting stupidly"); }; global behaviour_seek = function() { print(.name + "is seeking resources"); }; global bahaviour_rest = function() { print(.name + "is resting"); }; global rebot_def = { {"tom", behaviour_seek}, {"mike", behaviour_rest}, {"jane", behaviour_stupid}, {"bob", behaviour_stupid}, {"sarah", behaviour_seek}, }; for (id = 0; id < 5; id = id + 1) { robot = create_robot(1 * id, 10 * id, robot_def[id][0]); robot:thread(robot_def[id][1]); } sleep(1);
执行输出
tom is seeking resources
mike is resting jane is acting stupidly bob is acting stupidly sarah is seeking resources
如果设想一下robot是你的游戏中的实体对象, 我确定你现在就可以意识到上述机制可以在你将游戏实体脚本化时提供多么强大的支持.
线程状态
游戏通常使用有限状态机来控制实体的行为和状态. 状态机常常用在实现游戏实体的人工智能化, 一般的使用形式是, 实体必须对发生的事做出反应, 并且在反应结束后回到上一状态. 比如说PacMan[吃豆人]中的幽灵, 在PacMan吃到加强豆的时候, 幽灵开始恐慌乱逃, 当加强豆效果消失的时候, 幽灵们又回到漫游状态. 还有一个例子就是RTS游戏中的巡逻状态, 在巡逻中碰到敌人的话, 实体会拦截并攻击它, 消灭敌人后又重新进入巡逻状态.
在上一节中我已经演示了如何有效的使用协程和this来使游戏实体和一个函数配合工作. 就像你看到的一样, GM使用协程来驱动函数的执行, 而状态绑定机制允许你改变一个协程驱动的函数, 协程将保持自己的协程id不变, 但是在执行上下文改变的时候, 上个函数中局部变量就关闭了, 这个很容易理解, 因为切换时清空了上次执行函数的堆栈. 下面的例子就向你展示如何使用状态机制global awake = function() {
print("I,m awake!"); } global resting = function() { print("I am resting"); sig = block("wakeup"); if (sig == "wakeup") { stateSet(awake); } }; global init_state = function() { // 设置协程的状态机制 stateSet(resting); }; thread(init_state); sleep(1); signal("wakeup");
执行输出:
I am resting
I,m awake!
可以看到, 内建的stateSet接口用来将协程和状态函数绑定在一起, 同时允许传递附加的参数. 你可能注意到这个有一点像创建一个新的协程, 但是不同的是, 这里并没有创建新的协程, 你仍然在使用原来的协程, 只是更换了协程驱动的函数而已. 如果你要传递this给新的状态函数, 你必须在调用stateSet时显式的传递它. 现在你学会了设置一个协程的状态函数, 那么就可以使用一个stateSet调用序列, 使协程在一系列状态中变迁.
在协程状态函数的变迁中, 可以使用stateGet接口获取协程当前的状态函数, 亦可以使用stateGetLast接口来获取协程的前一状态函数. 这些接口很有用, 你可以选择本状态结束后是变迁到下一状态, 或者是简单的回到前一状态. 如果stateGet接口返回null, 说明协程没有处于状态机模式. 同样如果stateGetLast函数返回null, 说明协程没有前一状态. 下面的例子将演示创建实体对象, 并且等待一个条件, 执行动作, 然后复原到前一状态(即又开始等待条件). 第一个对象一开始的状态是asleep, 然后作出行动, 最后还是回到 asleep状态, 另一个对象也是起始于asleep, 它会被玩家制造出的各种各样的噪声吵醒.global ent_state_panic = function () {
print(.name + " is panicking, firing off alrams and attacting attention to you"); }; global ent_state_awake = function() { print(.name + " is waking up"); this:stateSet(stateGetLast()); }; global ent_state_sleeping = function() { print(.name + " is sleeping"); sig = block("quiet_noise", "loud_band", "kill"); if (sig == "quiet_noise") { this:stateSet(ent_state_awake); } else if (sig == "loud_bang") { this:stateSet(ent_state_panic); } else { print(.name + " killed"); } }; global ent_state_init = function(func) { print(.name, " state initialised"); this:stateSet(func); }; global ent_1 = {name = "roboguard 100"}; global ent_2 = {name = "old ticker"}; // 创建两个协程, 每个对象一个, 初始化它们进入sleep ent_1.threadid = ent_1:thread(ent_state_init, ent_state_sleeping); ent_2.threadid = ent_2:thread(ent_state_init, ent_state_sleeping); sleep(1); print("You stand on a twig"); signal("quiet_noise"); sleep(1); print("You fire a gun at" + ent_1.name + " causing a loud noise"); signal("loud_bang", ent_1.threadid); // 杀掉2号对象 signal("kill", ent_2.threadid);
执行输出:
roboguard 1000 state initialised
roboguard 1000 is sleeping old ticker state initialised old ticker is sleeping You stand on a twig old ticker is waking up old ticker is sleeping roboguard 1000 is waking up roboguard 1000 is sleeping You fire a gun at roboguard 1000 causing a loud noise roboguard 1000 is panicking, firing off airams and attacting attention to you odl ticker killed
状态的转换可以用下图来表示
另外一个很有用的机制在这里提一下, 那就是在切换到下一状态之前, GM允许你运行一段清理代码或者是触发其他行为. 你需要调用stateSetExitFunction接口来达到这一目的, 该接口的参数是一个函数, 这个函数将在本次state函数调用完成, 切换到下一state函数之前运行. 一个例子是可以使用它来在适当的时刻播放声音效果, 比如
global awake = function() {
print("I,m awakw!"); }; global waking = function() { print("I am strring..."); }; global resting = function() { print("I am resting"); sig = block("wakeup"); if (sig == "wakeup") { stateSetExitFunction(waking); stateSet(awak); } }; global init_func = function() { // 设置协程的状态函数 stateSet(resting); }; thread(init_func); sleep(1); signal("wakeup");
执行输出:
I am resting
I am stirring... I,m awake!
在我们学习的列子中, 所有的状态设置和切换都发生在当前协程环境中, 但是设置虚拟机中其他协程的状态函数也是很有用的. 想象一下你使用一个协程来模拟一个player, 这个player在掩体后等待一个"前进"的信号, 但是如果在收到前进信号之前掩体被炸毁了, 你可能想强制这个player进入一个躲避的状态中. 接口stateSetOnThread就是用来干这件事的, 它需要一个协程id和一个状态函数作为参数.
global state_advance = function() {
print("Leavinf cover and advancing"); }; global state_decide_action = function() { print("I have to decide on a next action"); }; global state_hiding = function() { print("Behind cover, waiting to advance"); sig = block("advance"); if (sig == "advance") { stateSet(awake); } }; global init_state = function() { stateSet(state_hiding); }; tid = thread(init_state); sleep(1); // print("Cover explodes!"); stateSetOnThread(tid, state_decide_action);
执行输出:
Behind cover, waiting to advance
Cover explodes! I have to decide on a next action
就像你看到的这样, GM脚本中内建的协程机制提供了一个非常有用的功能集, 用它可以灵活的控制游戏中实体的行为, 它向我们的"游戏脚本化需求"提供了一个很好的解决方案.