【经验分享】关于灵活使用父对象、事件管理器的思想、用户事件
gamemaker吧
全部回复
仅看楼主
level 15
sunyubokkkkk 楼主
注:本人水平也有限,此贴为经验分享,非教程。有任何错误之处,烦请指出。[滑稽]
先把这三者分开来讲,最后会以我手头的工程为例子,把三者合在一起。
首先说说父对象。我本人平常开发的时候是比较频繁使用父对象的,但我发现一些新人似乎完全不用,甚至完全不理解父对象,没有得到父对象这一功能对开发效率的提高以及对工程结构的清晰化。
先用自己的话解释一下GM里的父对象:
GM中父对象(Parent)的概念,即其他语言中“继承”或“派生”的概念。那继承又是什么意思呢?我们生活中常听到,谁谁谁的小孩继承了他爸爸什么什么特点,意思是这个小孩(子对象)拥有他父亲(父对象)的某些特点。但同时,毕竟这个小孩是不同于他父亲的另一个人,所以在拥有他父亲的某些特点的同时,也会有自己不同于其父亲的特点。因此,我们可以推出,父对象的作用是令其子对象拥有其本身的代码以外同时可以有自己的其他代码。
----------------------------------------
举个例子,你的选关界面有20个按钮,你会怎么做?
A. 编写每一个按钮的时候新建一个object,独立为其写Create事件定义自己需要的变量,写Step事件进行鼠标点击判断和执行点击后的动作,写Draw事件绘制出按钮的背景和文字等;
B. 编写好第一个按钮的各个事件,然后复制19个,再分别打开这19个复制出来的object中的Step事件代码窗口以改变点击后执行的动作;
C. 编写一个父对象,写Create事件定义所有按钮都共有的变量,写Step事件进行鼠标点击判断,写Draw事件绘制背景,再创建20个对象继承自这个父对象,这20个新建的object不需要修改任何地方也不需要自己写点击判定代码,只需要写上点击后的动作。
-----------------------------------------
从创建时的工作量来说,A排除,因为要打的代码比另外两个多太多。B和C的工作量类似。
从整个工程中的代码量来说,C明显比A和B要少很多。
从修改时的工作量来说,倘若你后来发现你的按钮的判定代码书写有误,或者你要为所有按钮添加一个共同的新功能,那么A和B要一个一个打开来重复地修改相同的地方,而C只需要打开父对象修改一次对应位置。
由此可见,使用父对象对开发效率有很大的提高,对于大工程中各个object之间的关系也有了更好的结构。
2018年02月01日 12点02分 1
level 9
谢谢了
2018年02月01日 12点02分 2
level 9
但是我的GM8的父对好像有bug,用不起。
2018年02月01日 12点02分 3
level 13
受教了,支持楼主
2018年02月01日 13点02分 4
level 15
sunyubokkkkk 楼主
现在我们举个例子来说说父对象的一个实际例子。
看这个名为oParent的object:
如图,这个object的三个基础事件的内容分别是Create定义变量,Step判断鼠标左键按下,以及Draw绘制自身和文字。效果是,绘制自身,再绘制text所储存的文字;同时如果鼠标左键在区域内按下,就执行0号用户事件并跳转到target_room所指定的房间。假设现在还是上文所说的20个按钮:
如图,只要oChild1到oChild20均选择oParent作为父对象,它们就只需要在Create定义自身不同于其他19个的两个变量,即要绘制的文字,和点击后要跳转到的房间即可,剩下了大量工作。
有人可能会问,我不需要这些子对象,我只需要那个oParent,然后对text和target_room的赋值可以在房间中实例的Creation Code中写呀。是的,的确,但是如果这20个按钮除了这以外还需要执行一些自己独特的代码呢?这样Creation Code就做不到了。
至于这个User Defined 0事件,即用户事件,我们在后面说。
子对象会毫无保留地执行父对象中的相同事件的内容。此处,oChild1到oChild20的Step事件和Draw事件是空白的,因此会自动执行oParent的Step和Draw事件;而因为它们的Create事件不是空白的,覆盖了oParent的Create事件,所以需要一句event_inherited()来执行oParent的Create事件。
2018年02月01日 13点02分 5
level 15
sunyubokkkkk 楼主
下面再来说说用户事件。可能很多新人会很好奇,添加事件的时候,Other分类下的User defined是干什么用的?
就如F1中所说,这些用户定义事件永远不会由GM自己触发。唯一触发这些事件的方法是使用event_perform函数或event_perform_object函数。
那么这些东西有什么用呢?
User defined这里面的User(用户)指的不是玩家,而是开发者。表示这些事件是由开发者自己定义的,而非GM定义的。但是,在GM开发的游戏中绝大部分事件都已经由GM定义并包含了,为什么还需要其他事件呢?其实,用户事件一个很大的用处就是用在父对象与子对象的继承中。
脑补一下,GM引擎在运行的时候,每一帧都会调用每一个object的实例的Step事件,反过来说,即所有object的实例在每一帧都会自动执行由GM引擎定义的Step事件。想想看,这一过程是不是有点眼熟?在上一节中,我们是不是刚讲过子对象会自动执行由父对象定义的对应事件?所以,是否可以把GM引擎视为所有object的父对象?我们假设GM引擎内部这个元素为所有object的父对象,那这个元素定义了所有这些GM内置的事件,Create、Destroy、Step、Draw、Collision等等。也就是说,子对象的事件是父对象定义的。那我们自己的父对象是否也可以给它的子对象定义事件呢?当然可以!这就是用户事件。
2018年02月01日 13点02分 6
level 12
同感,对于需要频繁碰撞计算伤害,组队之类的应用很有价值
2018年02月01日 13点02分 7
level 15
sunyubokkkkk 楼主
依然延续上面的类比。
GM为Create事件定义的条件是,当一个object的实例被创建时触发;为Step事件定义的条件是,当一个object的实例在每一帧的时候被触发;为Draw事件定义的条件是,当一个object的实例在每一帧需要更新绘制内容时触发;为Key Pressed事件定义的条件是,当某个键盘按键被按下时触发。当我们使用用户事件的时候,也需要决定其触发条件。
如之前在说父对象的时候20个按钮的例子,在oParent的Step事件中判断条件“鼠标左键按下且光标在范围内”成立,就触发0号用户事件(User Defined 0)。于是乎,虽然我们不能给0号用户事件命名,但是实际上,它可以被称作“选关按钮单击”事件。当然,根据只有子对象才会执行父对象对应事件的原理,在非oParent子对象的object中0号用户事件还是没有意义的空白事件。于是乎,在0号用户事件被触发时,oChild1到oChild20中User Defined 0事件中的代码就会被执行。
2018年02月01日 13点02分 8
level 15
sunyubokkkkk 楼主
再说说事件管理器(Event Manager)思想。
这是个什么东西呢?这不是GM里的一种功能,而是一种思想。大致意思如下:
如图,物体A、B、D有可能会遇到情况X(橙色),物体B、C有可能会遇到情况Y(绿色),物体D可能会遇到情况Z(紫色),而物体E专门用来处理情况X,物体F专门用来处理情况Y,物体G、H均能响应情况Z。此时我们需要一个中介,那就是事件管理器。有了这个中介,就不用让情况X发生时让物体A、B、D自己分别取寻找物体E来处理,也不用让情况Z发生时让物体D自己去分别寻找物体G、H来告诉他们要响应。
----------------------------------------
举个例子,脑补一个突突怪物的游戏,当最后一个小怪死亡时,我们假设发生如下事情:1. 销毁死亡的怪物的实例;2. 左上角的计分板加100分;3. 计分板旁边的击杀数提示增加1;4. 刷出Boss。我们假设计分板、击杀数提示、怪物生成器是3个独立的object。此时,可以这么写:
例子中的情况这样写仿佛并没有什么问题。但是当有很多会死亡的物体(例如,有很多类型但无法完全共用父对象的实体,不但有怪物,还有可以被摧毁的炮台等放置物,可以被摧毁的怪物的盾牌等),或者死亡之后要执行操作的物体很多(例如还要刷新主角技能,提升主角血量,更改场景元素等)时,这样写就十分麻烦。此时我们就需要一个事件管理器。
2018年02月01日 14点02分 9
level 15
sunyubokkkkk 楼主
我们这样安排这些物体:
我们来解释一下这些东西。首先是oEntityParent,是游戏中所有实体,即可移动的“活物”,的父对象。在Create事件中它声明最大生命值为100,生命值为最大生命值,击杀得分为0。在Step事件中,判断如果实体生命值小于等于0,就触发0号用户事件。
再看继承自oEntityParent的oEnemy。Create事件中首先用event_inherited()自动执行oEntityParent的Create事件,同时还把自己的击杀得分设为120。同时,添加了User Defined 0事件,当被触发(即hp小于等于0)时,销毁自身,并执行一个自定义脚本。这个脚本在后面说。
再看继承自oEntityParent的oTurret。Create事件中首先用event_inherited()自动执行oEntityParent的Create事件,同时还把自己的击杀得分设为70。而且,还声明了一个自有的exists_time变量用来表示炮台在场上剩余的帧数。oTurret的死亡条件由于不同于oEntityParent(因为在场时间清零时也算死亡),所以重新写Step事件来覆盖oEntityParent的Step事件。
接着是那段自定义脚本event_invoke。
这里面出现了oEventManager,传说中的事件管理器。为了方便理解,我们先看oEventManager再回来看这段脚本。
事实证明,事件管理器object本身并不复杂。我们来看看。sender变量表示事件的触发者,在event_invoke脚本里也体现了,会把调用事件的实例ID赋给sender,这样我们的事件管理器便知道是谁调用了这事件。在这个例子中,常量ev_enemy_die的值等于ev_user0,这样,当oEnemy或oTurret调用event_invoke(ev_enemy_die);时,oEventManager会收到触发死亡事件的敌人或炮台的id,并且自己的0号用户事件会被触发。在此再来执行加分、加击杀技术,如果死亡的是敌人而不是炮台则刷出Boss等。既省事又逻辑清晰。
------------------------------------------------
注:事实上标准的事件管理器中,要接受事件的监听器(Listener)应当自己向事件管理器注册(Register)以使事件管理器在执行某一事件时能通知到对应的监听器。但此处为了简化过程省略了监听器。
2018年02月01日 15点02分 10
后面这段……感觉变得高深了,仔细一看,原来是英文版GM,感觉好像很难
2018年02月02日 04点02分
level 8
顶顶顶
2018年02月02日 01点02分 11
level 10
赞一个[笑眼]
2018年02月02日 06点02分 12
level 9
666[滑稽]
2018年02月02日 10点02分 13
吧务
level 12
技术贴是要顶的!
2018年02月02日 13点02分 14
level 9
但是我遇到一楼这样的问题一般就会用一个obj完成,然后在房间编辑时给他定义序号
2018年02月09日 12点02分 16
所以说父对省力主要在多个同类的与游戏运行有关的东西上,选关建议用同一种obj
2018年02月09日 12点02分
回复 豆芽酱🌱 :是,我是特意说了在需要不同代码的地方才用父对象,否则可以单对象+Creation Code
2018年02月09日 13点02分
@sunyubokkkkk 对啊
2018年02月09日 22点02分
1 2 尾页