GameMonkey编程---高级进阶1
这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.com]
继续翻译GameMonkey脚本语言的文章, 这些文章都是在GameDev网站上找到的. 在翻译的过程中, 更加深了我对GM的了解和兴趣, 它的协程机制确实比Lua的协程在原生支持方面争强了很多, so enjoy! 上次GM参考手册的翻译放在了一篇文章, 感觉显的太长了, 所以这次我决定将这些长篇翻译分成多篇文章, 这样阅读起来比较方便, 而且可以避免一次信息过大.
原文地址: http://www.gamedev.net/reference/programming/features/gmScriptAdv/
Continuing GameMonkey Script: Advanced Use
简介
在阅读了我写的<GameMonkey编程(语言基础)>后, 你应该已经掌握了GameMonkey脚本语言的基本特性, 而且知道如何将GM嵌入到你自己的游戏应用中去. 这篇文章将要向你介绍关于GM语言的高阶用法和虚拟机的API. 我将在讲解的时候使用GM编写一些简单的脚本, 来快速的演示这些高级功能. 然后用一些实例来讨论和演示如何在你的游戏引擎中访问和使用这些功能.
在你阅读这篇文章之前, 我假设你阅读过<GameMonkey编程(语言基础)>, 搞懂了那篇文章中所讲的东西, 并且可以自己实现文中出现的例子. 我也希望你能有一些消息系统和事件驱动系统的概念, 这样你可以更加轻松的进行本文中知识的学习,
本文包含以下内容:
协同线程
阻塞/唤醒
协程和this
协程状态
协程的结构
创造可用脚本扩展的实体
GM最佳实践
脚本协同线程
每个"运行"在GM虚拟机中的脚本都是由一个协同程序来执行的. 一个协同程序都拥有独立的堆栈和可运行的byte-code, 它们共享访问GM虚拟机中的数据和函数. 不同于操作系统中的线程的定义, GM虚拟机中的线程是基于协同-线程模型. 所以, 每次当执行到gmMachine::Execute() 函数的时候, 这个协程必须完成(终止)或者是主动让出自己的控制权以便让虚拟机允许别的协程有机会执行.
当协程运行时, 它会执行一个函数 --- 一段位于它自己栈中的byte-code. 当你在GM中执行脚本的时候, 它会被编译成为一个函数, 然后创建一个协程来运行这个函数. 在下面的文章中, 将会讲解这个过程中的相关细节.
在脚本中创建协程
在GM中创建协程有两个办法:
1. 通过脚本语言自身提供的函数 thread(), 它接受一个函数和一些传给函数做参数的值. 下面演示一下在脚本中创建协程的方法
global thread_1 = function(a_count) {
print("[1] Starting...");
for (i = 0; i < a_count; i = i + 1) {
print("[1] iteration: " + i );
};
print("[1] Finishing...");
};
print("[0] Ready to execute...");
thread(thread_1, 100);
sleep(1);
print("[0] Thread created...");
执行输出:
[0] Ready to execute...
[1] Starting...
[1] Iteration 0
......
[1] Iteration 99
[1] Finishing...
[0] Thread Created...
在本例中, 一个新的线程被创建出来用来执行thread_1函数进行从0到99的迭代. 它将一直执行到thread_1完成, 消耗虚拟机的运行时间, 只到自己完成. 本例中调用sleep()函数将导致主协程向虚拟机归回控制权, 虚拟机调度thread_1所在的协程开始运行. sleep将在后面的文章中讨论.
协程归回控制权
现在你可以生成一个新的脚本协程, 并且运行它. 这里要提到的是, GM虚拟机不是基于抢占式线程环境的, 而是协作式线程环境, 还是那句话, 你的线程需要在完成一部分工作后主动让出控制权给其他线程.
在下面的例子中, 创建了两个协程, 每一个都从0开始迭代到某个数. 这个例子不使用yield(), 你可以在测试前先预测一下结果.
global thread_func = function(a_name, a_count) {
print("[" + a_name + "] Starting...");
for (i = 0; i < a_count; i = i + 1) {
print("[" + a_name + "] Iteration: " + i);
};
print("[" + a_name + "] Finishing...");
};
print("[0] Ready to execute...");
thread(thread_func, 1, 100);
thread(thread_func, 2, 100);
sleep(1);
print("[0] Thread created...");
执行输出:
[0] Ready to execute...
[1] Strating...
[1] Iteration 0
.....
[1] Iteration 99
[1] Finishing...
[2] Starting...
[2] Iteration 0
......
[2] Iteration 99
[2] Finishing...
[0] Thread Created...
就像你看到的那样, 脚本并没有并行的输出, 而是连续的执行只到整个协程函数执行完成才开始执行下一个. 尽管创建了两个协程, 但是第一个协程一直控制着虚拟机, 阻塞了第二个协程的执行, 只到第一个完成后控制权回到虚拟机, 才调度第二个线程执行. 如果你想要两个协程依次单循环执行, 协程就必须主动让出控制权. 为了做到这一点, 你可以简单的调用一个yield()函数.
下面改动一下上面的例子, 来达到我们想要的效果
global thread_func = function(a_name, a_count) {
print("[" + a_name + "] Starting...");
for (i = 0; i < a_count; i = i + 1) {
print("[" + a_name + "] Iteration: " + i);
yield();
};
print("[" + a_name + "] Finishing...");
};
print("[0] Ready to execute...");
thread(thread_func, 1, 100);
thread(thread_func, 2, 100);
sleep(1);
print("[0] Thread created...");
执行输出:
[0] Ready to execute...
[1] Starting...
[1] Iteration 0
[2] Starting...
[2] Iteration 0
......
[1] Iteration 99
[2] Iteration 99
[1] Finishing...
[2] Finishing...
[0] Thread Created...
运行上述脚本, 你可以看到, 不像上一个脚本那样连续的执行完一个协程后再执行另一个. 从结果上看两个协程并行运行着. 在内部, GM虚拟机在同一时刻其实还是只运行一个协程, 但是yield()可以让虚拟机切换上下文, 并且去执行另一个协程.
有时你可能想让协程暂停指定长度的时间. 比如你希望一个NPC停在一个路点上过10秒后再移动. 这是一个使用sleep()的最佳例子, sleep函数接受一个数字参数, 这个参数描述了协程希望暂停多久. 一个暂定的线程也会让出对虚拟机的控制权, 等它到时恢复后, 就可以继续执行代码. 你可以用sleep代替yield重新试验上述脚本
阻塞和唤醒
在现实生活中进行游戏编程, 你往往不会使用一个大的Loop和yield, 取而代之的是使用GM提供的更加强大的阻塞/唤醒机制来控制线程执行上下文的切换. 在游戏中, 你需要实现一个大脑行为的一个实体, 它可以在每个游戏循环中思考然后根据情况做一些事情, 多数情况下它是在Loop中等待一个触发条件. 下面就是一个传统而极端低效的现实:
global WakeUp = false;
global thread_1 = function(a_count) {
while (WakeUp == false) {
// Snooze
print("zzz");
yield();
}
print("I just woke up, boy was I tired!");
};
// 启动线程
thread(thread_1);
// 睡一分钟后设置唤醒变量
WakeUp = true;
当运行脚本的时候, 你可以看到屏幕上打印出满屏的zzz, 只到协程被唤醒. 这里协程实际上一直在不定的运行, 消耗着CPU指令周期等待唤醒. 这样是很低效的, 就是通常大家称为的忙等. 不过GM提供了更为高效的阻塞和唤醒机制.
一个协程可以阻塞它自己, 只到接收到一个(或者几个中的一个)特殊的信号. 阻塞会将本协程从活跃协程池中移出避免在收到信号前再次被无意义的调度, 并且让出控制权. 只到信号唤醒, 协程会重新加入到活跃协程池中参与调度, 下面用阻塞/唤醒机制实现上个例子
global thread_1 = function() {
block("WAKEUP");
print(“I just woke up, boy was I tired!”);
};
// launch thread
thread(thread_1);
// 在唤醒前睡一秒
sleep(1);
signal("WAKEUP");
这个协程将会阻塞, 只到一秒钟后它收到一个"WAKEUP"信号字符串. 很高效, 因为它不像前一个忙等的实现, 并没有使用一个循环去不停的检测一个脚本变量. 值得注意的是, 线程不仅可以使用string来阻塞和唤醒, 它可以使用任何的gmVariable来进行这个操作, 只不过使用string来演示将更加直观的表现这个功能.
下面让我们继续看看更多的例子. 有两个协程, 他们被创建并且阻塞在不同的信号上, 第一个信号将在主协程sleep 1秒后发出, 唤醒线性thread_1, thread_1中发出第二个信号唤醒thread_2.
global thread_1 = function() {
block("WAKEUP");
print("I just woke up, boy was I tired! You should wake up too!");
signal("YOUTOO");
};
global thread_2 = function() {
block("YOUTOO");
print("What did you wake me up for?");
};
// launch thread
thread(thread_2);
thread(thread_1);
sleep(1);
signal("WAKEUP");
执行输出:
I just work up, boy as I tired! You should wake up too!
What didi you wake me up for?
通常协程可以阻塞在多个条件上直到收到其中一个时被唤醒. 比如, 你可能想用GM描述一个实体在一片区域走动, 然后停下来等待移动到下一个点的命令, 攻击另一个实体, 或者是简单的进行防御. 阻塞/唤醒机制允许你阻塞在多个信号上. block返回唤醒协程的信号, 你可以用这个信号来实现不同的策略.
global blockfunc = function() {
print("Waiting for instruction, sir!");
signal_received = block("attack", "move", "defend");
if (signal_received == "attack") {
print("Attacking");
}
else if (signal_received == "move") {
print("Moving to position!");
}
else if (signal_received == "defend") {
print("Defending til the death!");
}
};
thread(blockfunc);
sleep(1);
signal("attack");
执行结果
Waiting for instruction, sir!
Attacking!
在上面的例子中, 协程阻塞在attack, move, defend 3个信号上. 协程会根据收到的信号来决定具体执行什么行为. 在这个例子中它收到了attack信号, 所以执行attack行为.
在上面的几个例子中, 协程都是被全局信号[signal("attack")]唤醒的, 这意味着如果两个协程阻塞在同一个信号上, 他们会被同时唤醒. 在游戏中来说, 所有的阻塞在同一个信号上的游戏单位都会被唤醒并被执行. 当然, 也可以向指定的协程发信号, 通过调用signal函数时指定协程id[signal("attack", thread_id)], 就可以实现. 协程id是你在使用thread函数创建协程时的返回值. 下一个例子就演示这种用法
global blockfunc = function(name) {
print(name + ", waiting for instruction, sir!");
singal_rece = block("attack", "move", "defend");
if (signal_rece == "attack") {
print(name + " is attacking!");
}
else if (signal_rece == "move") {
print(name + " is moving to position!");
}
else if (signal_rece == "defend") {
print(name + " is defending til the death!");
}
};
thread_1 = thread(blockfunc, "woob");
thread_2 = thread(blockfunc, "foo");
sleep(1);
signal("attack", thread_1);
signal("defend", thread_2);
执行输出
woob, waiting for instruction, sir!
foo, waiting for instruction, sir!
woob is attacking!
foo is defending til the death!
就像你看到的这样, GM中的阻塞/唤醒机制允许你在虚拟机中创建多个协程, 并且让他们不消耗任何执行周期的情况下等待信号唤醒. 这允许你使用信号机制来创建实体之间复杂的交互行为.
今天先翻译到这里, 后面的内容下次再做:)