论GameMaker:Studio事件的执行顺序
游戏制作是一个系统工程,随着规模的扩大,任何一个环节的问题都可能会让游戏崩溃。我们必须要在游戏引擎运行的机制上,尽可能的排除这个干扰项,拒绝黑盒测试。
另外,给GameMaker深入研究群(133687276)做个推广。本群宗旨是对GM于GMS进行深入研究(不闲聊),所以新手还是先打打基础。
注意:以下内容全部基于GMS1.4.1474版本测试并验证。并经在GMS2上初步测试,并没有发现异常。如发现错误,还请及时指出,以免误导大家。
一、说明
首先来了解下GMS的特点:1、事件驱动;2、以实例为基本单元;3、顺序执行。那么我们要了解GMS的内部机制,首先应该研究引擎的执行顺序。
言归正传,由于GMS在每一帧里会按照顺序执行多个事件(事件的内容为程序代码),而执行的基础单元为对象的实例。所以应该把GMS的执行顺序分为两个层面来分析:事件与对象实例的执行顺序。
注意:
1、本次讨论不涉及event_perform函数。我们要研究的内容是GMS事件执行顺序。由于event_perform函数可以随意调用,所以讨论它没有意义。
2、本次讨论不涉及Asynchronous Event。异步事件应属触发式,所以不作为本次讨论的内容。
3、Other Events中No more lives、No more health、Animation end、Close button也不作为本期讨论的内容。原因是用处太少,并且也不会影响游戏整体的执行顺序。
二、正文
由于GMS是以实例为基本单元,没有实例就没事件的执行。其实为了便于我们讨论事件的执行顺序,一个实例显然便于分析与讨论。但实际情况要复杂的多,我们需要至少保证2个实例才能包含所有可能性(具体原因后面再详谈)。
那我们现在正式开始。由于我们要讨论的GMS事件有20多种,为了便于讨论我们分为几个部分来进行。
首先我们看下官网给出的事件(部分事件)执行顺序:
1、 Begin step events
2、 Alarm events
3、 Keyboard, Key press, and Key release events
4、 Mouse events
5、 Normal step events
6、 Collision events
7、 End step events
8、 Draw events
这8个事件逻辑顺序是讨论事件执行顺序的前提。关于事件的执行顺序,我们会以这8个事件作为基础进行展开。
这里要注意的是,GMS 1.3以及后续版本将Draw事件又分成了多个子事件:
l Resize
l Pre Draw Event
l Draw Begin Event
l Draw Event
l Draw End Event
l Post Draw Event
l Draw GUI Begin Event
l Draw GUI Event
l Draw GUI End Event
鉴于这些Draw子事件执行顺序一目了然,可等同于GM、GMS1.1与GMS1.2的Draw事件。所以在执行顺序上,我们将上述子事件都归纳为一个draw事件来对待。
除了上述8个常用事件外,还有无顺序的Destroy事件。由于Destroy事件取决于instance_destroy()函数的位置,属于触发式执行。我们可以把Destroy事件看做为无序可控触发类事件。由于Destroy事件比较常用,所以后面章节会再次进行分析。
当然还有我们经常接触的,最先执行的Create事件。由于Create事件在正常情况下只执行一次(event_perform函数不在讨论范围),并不进行循环执行。我们把这类事件称之为有序一次性事件。
此外,还有不常用的Other Events。但为了了解GMS的执行顺序,其中部分事件也划入到探讨范围内。其中包括Outside room、Intersect boundary、Views、Game start、Game end、Room start、Room end以及End of path事件。
鉴于大家很少接触到这些事件,我直接引用下官方解释:
Outside room: This event happens when the instance lies completely outside the room. This is typically a good moment to destroy it.
Intersect boundary: This event happens when the instance intersects the boundary of the room, that is, it lies (at least) partially outside the room.
Views: Here you find a number of events that are useful when you use views in your rooms. These events test whether the instance lies completely outside a particular view or interesects the view boundary.
Game start: This event happens for all instances in the first room when the game starts. It happens before the room start event (see below) but after the creation events for the instances in the room. This event is typically defined in only one "controller" object and is used to start some background music and to initialize some variables, or load some data.
Game end: The event happens to all instances when the game ends. Again typically just one object defines this event. It is for example used to store certain data in a file.
Room start: This event happens for all instances initially in a room when the room starts. It happens after the creation events.
Room end: This event happens to all existing instances when the room ends.
End of path: This event happens when the instance follows a path and the end of the path is reached.
由于房间的存在,我们还需考虑到房间中涉及执行的两个行为方式:
1、Room Creation Code。这个是房间的初始代码。类似实例的初始事件一样,每个房间都可以有自己独立的初始代码。具体位置见下图左侧。
2、Instance Creation Code of each instance。这个是在房间编辑器里,修改事先在房间添加的对象实例的初始属性。具体位置见下图右侧。
注意:它与对象的Create 事件是两回事。
事件的范围(包括room行为方式)已经确定了。但我们讨论的次序并没有严格的逻辑关系以及顺序,可能大家会感觉到非常混乱。那我们根据事件的特点,一起归纳下上述事件。
我按照事件的特点将上述事件总结出如下3个类别。
1、有序8个循环事件。包括Begin step、Alarm、Keyboard, Key press, and Key release、Mouse、Normal step、Collision、End step以及Draw事件(所有Draw的子事件都归属为一类)。
2、无序可控触发类事件。包括Destroy、Outside room、Intersect boundary、Views、Game end、Room end以及End of path事件。
3、有序一次性事件。包括Create事件、Room Creation Code、Instance Creation Code of each instance(房间编辑器里的实例,与Create事件要区分开)、Game start与Room start事件。
8+5=13(无序触发类事件后面再讨论)。
测试内容暂时只包含一个实例,并且事先在房间编辑器中部署。具体过程我就省略了,直接给大家最终结果。
1. Create events(实例事件)
2. Creation Code of each instance(房间行为方式)
3. Game start events(实例事件)
4. Room Creation Code(房间行为方式)
5. Room start events(实例事件)
6. Begin step events(实例事件)
7. Alarm events(实例事件)
8. Keyboard, Key press, and Key release events(实例事件)
9. Mouse events(实例事件)
10. Normal step events(实例事件)
11. Collision events(实例事件)
12. End step events(实例事件)
13. Draw events(实例事件)
曾经有人提出过这样的问题,为什么在Room Creation Code里写的代码却不能最先执行。通过上述实验得知,它前面还有3个行为事件。
当然,上面讨论的实例是在房间编辑器建立的。如果我们再添加一个非房间编辑建立的实例呢,有序一次性事件(前5个行为事件)会如何执行呢。其实这是一件钻牛犄角的问题,因为我们不应该在两个实例里,都创建Game start与Room start事件,这是毫无意义的行为。但鉴于我们打算深入研究GMS的机制,那就放到一起进行讨论。
本来这部分内容,应该在下期的对象实例执行顺序里讨论。但我认为还是应该提前预热下较好。
既然要添加另外一个实例,那这个实例在什么时候创建,由谁来创建是个关键问题。下面我们会分情况进行讨论。
假设在房间编辑器创建了A实例。那么另外一个在游戏中创建的实例称之为B实例。A与B属于不同对象的实例。需要注意的是,这个B实例的生命周期里是不会包含Creation Code of each instance行为的。还有个重要的前提条件,A所属对象比B所属对象先创建(在IDE编辑器里)。这个重要的原因,我会在对象实例顺序章节再进行讨论。
在A实例的Create事件里创建B实例。A与B的执行顺序:
1. A实例的Create events与B实例的Create events
2. A实例的Creation Code of each instance
3. A实例的Game start events
4. B实例的Game start events
5. Room Creation Code
6. A实例的Room start events
7. B实例的Room start events
其实没什么特别的(A比B先执行的具体原因下章节会详细讨论),除了B实例没有Creation Code of each instance外,都在正常执行。
如果在A实例的Creation Code of each instance里创建B实例。A与B的执行顺序:
1. A实例的Create events
2. A实例的Creation Code of each instance与B实例的Create events
3. A实例的Game start events
4. B实例的Game start events
5. Room Creation Code
6. A实例的Room start events
7. B实例的Room start events
需要注意的是B的Create事件。
注意:任何实例的Create事件的执行,都会在instance_create()函数执行点(当前代码行),立即执行!
如果在A实例的Game start事件里创建B实例。A与B的执行顺序:
1. A实例的Create events
2. A实例的Creation Code of each instance
3. A实例的Game start events与B实例的Create events
4. Room Creation Code
5. A实例的Room start events
6. B实例的Room start events
这个很明显,B实例没有参与Game start事件。原因是B在Game start事件执行后才被创建。
注意:任何实例在当前事件被创立时,除立刻执行自己的Create事件内容外,不会参与本次事件的执行。在下一帧里,被创建的实例会正常执行所有事件。
由于Game start事件属于有序一次性事件,所以在B实例的生命周期里,永远都不会执行Game start事件。哪怕B实例的Game start事件里的确有具体内容。如果B实例是在Room start事件里被创建,那么B实例将正常循环执行8个常用事件。当然,在创建时要立即执行一次Create事件。
关于instance_create函数与Create事件举个例子。
前提是房间里仅有一个obj_A的实例。并且obj_A实例比obj_B实例先执行。
{obj_B对象的代码如下:
Create事件:
global.n=10;
Step事件:
global.n=30;
obj_A对象的代码如下:
Create事件:
global.n=0;
Step事件:
instance_create(100,100,obj_B);//此时obj_B的实例被创建,并立即执行自己的Create事件。但是这个被创建的obj_B实例,在本帧里不会执行Step事件。
global.n=20;
结果:
在本帧结束时,global.n数值先从0,到10,最后为20。
第二帧结束时,global.n数值先从20,到10,再到20,最后为30}
我们对上面内容做个总结。首先是最开始我们提出的13个事件执行顺序,这个是固定不变的。无论实例是在房间编辑器里建立的,还是在中途建立的。其次是有序一次性事件,过期不候。如果是中途建立的实例,在某些有序一次性事件后产生,那么它将永远不会执行那些事件。
剩下就只有无序可控触发类的7个事件了:Destroy、Outside room、Intersect boundary、Views、Game end、Room end以及End of path事件。
先说说Destroy事件。说到Destroy事件,就离不开instance_destroy()函数。这个函数可以在任何事件的任意位置中。如果在某实例的某事件里,执行了instance_destroy(),Destroy事件会在当前代码处立刻执行,然后再执行本事件instance_destroy()函数后的代码,最后在本事件结束后销毁实例。(这里要感谢鸵翼天指出了问题)
再举个instance_destroy()与Destroy事件的例子。
前提是房间里仅有obj_A一个实例。
{obj_A对象的代码如下:
Create事件:
global.n=0;
Step事件:
intance_destroy();//立即执行Destory事件
global.n=20;//执行完Destory事件后还会执行这行代码
//Step事件执行后,销毁本实例
Destory事件:
global.n=10;
End Step事件;
Global.n=30;//由于本实例在Step事件结束后被销毁,这行代码不会执行。
结果:
在本帧结束时,global.n从0,到10,最后为20。}
Outside room、Intersect boundary、Views(包含所有子事件)事件很简单。它会在本帧Step事件执行后执行。
Game end事件会在执行game_end()函数所属的事件执行后执行。而在Game end事件执行后,游戏结束。
Room end事件与Game end事件类似。只是触发Room end事件函数有很多种:room_restart()、room_goto(numb)、room_goto_next()、room_goto_previous()都可以触发。
Room end事件会在函数触发所属事件执行后执行。而在Room end事件执行后,房间会根据触发函数而转变。
End of path事件。这个事件是当你使用路径函数path_start(path,speed, endaction,absolute)才有可能触发。endaction为任何值时都等同于0,走完一次完整path后触发End of path事件。
实验结果表明,End of path事件会在本帧Step事件执行后执行。
注意:path_end()函数不会触发End of path事件。它只会停止正常执行path实例的行为。
至此,GMS事件执行顺序的探讨已经完结。下期我们再研究对象实例的执行顺序。