[ 分享 ] 简单的对话框体(下)
该教程由油管上的妹子FriendlyCosmonaut原创,能翻墙的尽量看原版吧:Textbox Tutorial。
代码托管在Github。实现的效果为:
其实基本的逻辑已经在简单的对话框体(上)实现了,这一节主要是
- 将代码重构成可以复用,并且更加抽象模块化
- 多页文本对话和跳过对话的功能
- 打字效果(逐字出现的效果)
- 然后美化下对话框的表现:外边框,字体变色
多页对话文本和跳过某页对话的功能
将obj_susan的step事件的对话文本,直接改成变量myText
然后在create事件使用list结构对该变量声明和赋值,这样我们不必直接去step事件一一修改对话文本,可以直接在create对myText变量直接修改,其他地方引用变量。
注意这里的声明方式,list[index] = strings。同样的index的值将作为我们对话框的页码来使用:第一段对话,第二段对话...
既然有了多段对话,那么在obj_textbox的draw事件内,也要分别连续绘制不同的页码的文本。
将Draw事件内的引用的text变量,改为text[page]
同样的,由于使用page变量,在create也对page变量进行声明和赋值
然后为obj_textbox添加step事件,并且进行按键检测,检测到了则跳过当前页的对话(即page变量增加1)。
运行游戏,按下空格键能看到如下效果
修正对话框对象的创建条件为,按下space时,并且与NPC对象(如obj_susan)发生碰撞.
在obj_susan的step事件内,添加if语句
修正page变量的大小会超过文本text(list)的“长度”,即你一直按下空格,当对话框显示完最后一句话后,就销毁该对话框对象。
然后设置creator.myTextbox变量为空。
为什么这么做?
因为在NPC对象创建实例时,会判断自己的myTextBox存不存在,一旦存在它就不会创建第二个了,这个必须将已经被销毁的myTextBox变量设置为空,这样当再次触发创建NPC对象的TextBox时,TextBox才会被创建出来。
首先在obj_Textbox中声明creator变量为noone。然后在创建obj_Textbox时,将obj_Textbox的text变量换成NPC自己的对话文本,并且设置该obj_Textbox的creator(创建者)为自己。
这样在最后,就可以通过creator索引,在obj_Textbox的step事件中,设置NPC对象的myTextBox变量为空了(是不是很魔法,这个骚操作,真是给跪了,这个油管妹子。)
这里有一个优化的操作,作者不希望,当出现最后一句话后,再次按下空格不是销毁文本对话框(obj_Textbox),然后又马上创建新一个文本对话框(obj_Textbox)出现第一句话,而是这一次的空格仅仅只是起到销毁文本框的作用,这里有个细节操作,就是我们不知道GameMakerStudio2的代码运行顺序
所以我们不在运行检测到space空格按下的这一帧中立即将myTextbox变量设置为空,而是使用计时器alarm(并且是NPC对象的alarm变量)来移除myTextbox,就会使得这一次空格按下仅仅只是销毁文本对话框,而不会立即创建新的文本对话框了。
打字效果(逐字出现的效果)
为了实现逐字出现的文本对话框的效果,这里的方式是对展现的文本的内容进行处理。
在obj_Textbox的step事件中,创建一个临时变量textPart,并使用string_copy()函数来从指定的位置,复制指定个数的字符成为新的文本。
先在obj_Textbox的create事件中创建charCount变量(charCount每帧增加的数量可以作为逐字出现的速度来调节)
为了方便调试,查看结果,我们可以为obj_textbox对象添加draw_GUI事件,用显示当前绘制了多少个字符
运行游戏,即可触发对话,即可看到逐字出现的效果,但是可以看到左上的charCount数量在不断增加,没有达到本页对话的最大字符数时停下,这时比较消耗性能的。
为了修正这个问题
字体变色效果
在NPC对象(obj_susan)的create事件创建myname变量
再在对话框中,创建name变量
在创建对话框时,将NPC的名字变量赋值给对话框
然后修改绘制文本的代码
为了美观缘故,这里设置了水平边距和垂直边距,并且使姓名居中显示
运行游戏,即可看到效果。、
NPC对话模块化。
即创建一个可以对话的NPC父物体,其他NPC对象都从该对象继承。
在资源树上选中obj_susan,拿下<crtrl+d>快捷键复制一个对象,重命名为obj_parent_talking。
然后将其精灵设置为空。
接着将其create事件中的myName和myText变量都设置为空(这些内容将交给继承它的NPC对象来填充,毕竟不同的NPC有不同的名称,有不同的姓名)
然后对原来的NPC对象(obj_susan)进行处理
如果我们需要在继承obj_parent_talker的NPC对象另外表现出不同的属性,那么可以继续添加step事件,并在书写自己的属性代码前,添加继承函数event_inherited()
如此以来,我们就可以自由的创建更多的NPC了,只需要添加NPC自己的对话和姓名,而对话的逻辑和功能,都交给obj_parent_talker对象了。例如我们可以创建一个obj_bob对象,设置精灵,对话,姓名。
同时为了方便没有耐心看剧情的玩家,我们再添加一个小功能,在播放文字未完成时,再次按下空格,则将当前页的所有的文字全部显示出来。
运行游戏,则可以看到效果。
美化对话框
先获得对话框高度
再绘制一个包裹对话框外边缘的矩形边框
运行游戏,则可看到文章前贴出的效果图
到此,FriendlyCosmonaut妹子的对话系统教程完结了。
[ 分享 ]7.GAMEPAD INPUT(手柄输入)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
本节主要内容:
- 检测手柄的输入。
- 剥离其他模块的输入检测,集成到scr_get_input中
检测手柄输入
作者是以Xbox360手柄为例,首先在scr_get_input脚本里面添加手柄输入代码
检测手柄是否连接成功(0是指槽位,或者说某个手柄),在连接成功的条件下
访问手柄十字方向轴的按下情况(正负),来为四个Key赋值
注:有关手柄输入的具体函数用法,可以参考官网文档Reference--Mouse、Keyboard and Other Control一节中的内容
剥离obj_player和scr_move_state脚本中的输入检测代码
将关于移动变量的xaxis和yaxis的处理都放到scr_get_input里面去,在obj_player中只访问它们
首先先在obj_player对象的create事件中,初始化变量xaxis和yaxis
然后将原来在scr_move_state脚本中根据四个key值来为xaxis和yaxis赋值的语句删除掉,移动至scr_get_input脚本中
并且在scr_get_input中将检测手柄输入来为四个key进行赋值的语句,修改为直接为xaxis和yaxis变量赋值。
到此。在scr_move_state纯粹是根据xaxis和yaxis的值来进行移动,不再负责按键检测和输入,处理。
RPG_Basic(6):BETTER PLAYER MOVEMENT(优化角色移动)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
本节主要内容:
- 创建移动轴变量,来优化角色移动
- 为什么要优化角色移动,因为之前的移动时垂直速度和水平速度是完全独立,它们仅受各自的按键输入影响,这就导致,斜线运动时,水平和垂直方向都有速度,斜线合速度显然快于这两个速度,而我们需要的是人物朝任意方向移动,速度 都是一致的,这才会让玩家得心应手的控制和移动。
在obj_player的create事件中,创建新变量
- hspd水平速度分量
- vspd垂直速度分量
- len位移
然后将原来在scr_move_state脚本中的移动代码批量注释掉(四个if语句和stop动画语句),然后使用轴向变量yaxis和xasix来进行移动
- xaxis为正,往右移动,xaxis为负,往左移动,并且xaxis的值大小决定水平速度的大小
- xaxis为正,往右移动,xaxis为负,往左移动,并且yaxis的值大小决定垂直速度的大小
- spd作为系数来调整速度大小··
到这里,人物是可以移动了,但是人物没有动画的
需要重新进行动画处理(在scr_move_state进行如下处理)
- 根据yaxis和xaxis的值,来计算移动方向dir
- 根据yaxis和xaxis的值,来判断是否需要进行移动
- 通过lengthdir_x获取在x轴上的分量速度
- 然后根据len的值来设置是否进行播放动画(image_speed是否等于0),image_speed_player作为系数调整
- 如果len为0,就需要设置image_speed=0,停止动画,因为角色在停止时,是静止的(除非需要播放原地待机动作,不然就是如此处理image_speed=0,image_index = 0)
- 接下来是根据vspd和hspd的正负值,来设置角色的精灵图像
注意顺序对精灵动画的影响,这里hspd的代码在后面,也就是说当你按下斜向上的按键时,
会是水平方向的精灵动画(因为一旦有水平速度,在代码的最后,精灵动画最后就会被设置为spr_playe_right或者spr_player_left)
到此角色移动就优化完了
RPG_Basic(5):SMOOTH VIEW MOVEMENT(平滑的移动视图)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
本节主要内容:
- 创建镜头控制器对象,来控制摄像机视图移动。
- 为什么要优化镜头移动,因为默认的镜头是跟随人物移动的,所以当人物停下来时,镜头就马上静止了,这会给你一种不平滑的体验,就像播放或者停止音乐时的音量淡出淡入效果,所以这里为镜头的移动做同样的处理。
创建一个控制摄像机视图移动的控制器 obj_view,在该对象的step事件中,
在每step内,摄像机视图的位置缩短与人物之间的距离的10%
将该对象拖入房间,然后在房间的视图设定中,把摄像机视图的跟随对象改为obj_view
这样一来,摄像机的移动就具有缓动的效果了。
RPG_Basic(4):DRAW EVENT, BACKGROUNDS, SCRIPTS, AND STATES(绘制事件,背景,脚本,状态)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
本节主要内容:
- 导入背景图片,设置平铺
- 绘制角色脚下阴影
- 创建脚本,检测键盘输入
- 使用状态机来储存角色行为
设置房间背景图片
注:在GMS2版本中,资源树已经去除了Background类型,所以在GMS2设置背图片的方式时:
先创建精灵,命名为“spr_grass”,然后导入外部文件作为精灵图像
,然后在房间属性中,设置并平铺(Horizontal Tile水平平铺,Vertical Tile垂直平铺)
设置角色脚底阴影
创建一个精灵,命名"spr_player_shadow"
并导入外部文件作为精灵图像(文件下载地),设置中心为center(x:7,y10)
然后使用obj_player的draw事件来绘制人物的脚底阴影
注:在创建draw事件后,GMS就会不再调用默认的绘制自身的事件,所以需要在代码中额外增加一行“draw_self”
draw_sprite(精灵名称或者索引,图片编号,x坐标,y坐标)
创建脚本检测输入
创建脚本scr_get_input(),并且将检测输入的代码,全部移动至该脚本内
然后移除在obj_player对象内的检测输入代码,换成调用该脚本,主要在create和Step都要调用1次,create调用是因为要创建变量,step是需要每步进行检测
create事件:
step事件:
使用状态机
创建脚本,scr_move_state,然后将原本obj_player内的所有移动相关代码放置到该脚本内
在obj_player的Create事件中,声明状态变量state = scr_move_state
注:所谓状态机,简单而言就是同一时间只执行一个(种)动作或者行为,用变量state存储这个唯一的行为,在GMS2中的方式就是把脚本的名称赋值给变量,该变量就会成为脚本的索引。
在step事件中只要调用script_execute()函数执行该脚本即可保证同一时间只调用1个脚本(状态)
运行游戏,就能看到角色仍旧可以上下左右移动。
RPG_Basic(3):VARIABLES AND CHARACTER SPRITES(变量和人物精灵)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
基本概念
- instance variable 实例变量
- local variable 局部变量
- depth深度
在Create事件创建变量并在Step事件中使用
双击资源树上的obj_player对象,点击event,点击Create,在Create事件中的代码,添加实例变量spd,
注:GameMaker Studio 2 对大小写是敏感的,spd和SPD不是同一个变量
将原来的Step事件中的"4"替换成变量"spd",来代表角色移动的速度
尽量使用变量来控制参数,这样方便修改,如果想要改变角色移动的速度,只要在create事件中修改spd的数值即可,而不是挨个修改step事件中的数值。
使用局部变量来代替复杂的表达式
创建4个local variable(局部变量)来代表复杂的表达式,这样有助于保持代码的可读性。
注:索引按键A可以使用单引号,但在GMS2版本中,必须使用双引号来进行标识。
导入外部图片作为精灵子图像
创建的精灵,双击呼出编辑面板,点击EditImage
在弹出的界面,点击最上方的彩蛋的EDIT,选中需要导入的外部图片文件
然后设置参数即可
依次创建以下几个精灵并导入外部图片(外部图片资源在此下载,若链接失败,请科学上网,或者留言索要)
- spr_player_left
- spr_player_right
- spr_player_up
- spr_player_down
都是14*21大小,总帧数为6。
设置各个精灵的中心为middle centre
设置obj_player的碰撞盒至合适的位置
去除自动补齐颜色
点击游戏上方的按钮的设置按钮,将windows里面图像设置的红框条目,设置为空,这将禁止GMS引擎在游戏运行时,自动补齐精灵图片的动画,从而避免图像模糊。
手动设置精灵运行速度
在obj_player的create事件,令image_speed = 0;
这意味着obj_player的不会自动播放精灵动画,如果不设置为0,该精灵的动画将以精灵的参数speed为动画播放速度。
当你运行游戏,角色就会不停的循环的播放动画,就比如下图,角色没移动,但也在原地播放一个原地跑步的动画。
理解深度的概念
在obj_player还有一个参数,叫做depth,你可以简单理解成画面中的人离屏幕的距离,离得越远数字就越大。
负数离屏幕近,整数离屏幕远,当然这只是一个比喻的说法,正确的来说应该是渲染上的术语,你可以查看帮助文档帮助了解,或者查看青铜的幻想的GMS中文教程,这一点说的很清晰。
注:和GMS1不同,GMS2取消了直接在对象创建时设置depth,而是在脚本使用instance_create_depth()要求必须输入深度,如果你想要在不同的深度创建同一个对象,那么就不必像GMS1一样,创建两个对象啦。
运行游戏发现,在角色移动时,角色是“静止”,所静止其实是指obj_player没有精灵动画,它的精灵子图像永远固定不变。
如果想要在角色移动时,根据他移动的方向,播放不同的动画就需要改变obj_player的精灵子图像索引和动画速度了。
在if事件中添加索引更改,设置动画播放速度为0.2;
在step事件监测按键输入,每帧都会更新按键的情况,从而会导致其中某一个KEY被按下后,松开后,精灵子图像不会复原,就是在最后添加一个复原精灵子图像的代码,注意当设置image_speed = 0,精灵子图像索引会自动回到第一帧。
RPG_Basic(2):STEP EVENT + VIEWS(步事件和视图)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
本节主要内容:
- Step事件
- 脚本基本函数:if、or、ord、keyboard_check()
- 摄像机和视图
- 摄像机跟随人物移动
基本概念
- if()函数
- or()函数
- keyboard_check()函数
- ord()函数
理解Step事件
在设置里面设置的帧数就是指的帧,step.
目前已经在第1节中将游戏运行速度改为1秒=30帧。
也就是说在Step事件里面的代码,(理想状态下)每秒会运行30次。
删除按键事件,添加STEP事件
将第1节,添加的键盘按键检测事件删除(右键点击,在弹出的窗口中点击Delete Event)
然后依次添加如下代码,即不通过GMS2提供的事件来检测键盘按键,而是通过内部函数来检测。
内部函数Keyboard_check():在这一帧,键盘的某个按钮是否被按下,按下返回true,没有返回false.
注意ord()的字符串引用需要使用双引号。
这样WASD和方向四键的检测事件就完成了。
创建房间视图
左键双击资源树上的房间名称
即可在GMS2界面的左下该房间对应的房间属性。
首先我们需要点击Enable Viewport为勾选状态,同时它的visible为勾选状态,否则游戏运行时就是一片漆黑,你可以把viewport理解为一个视频端口,有点类似于电视机里面的视频,AV,HDMI接口。
Cemera和View
Cemera(红色部分)是指在游戏世界能看到多大:指代在游戏具体的像素大小
View(黄色部分)是指游戏窗口有多大:指代游戏运行时,显示的游戏窗口大小
将Cemera和View的属性设置如下,然后点击Object Following(绿色部分)设置为obj_player,这样镜头视图将会跟着角色一起移动。
触发摄像机移动的范围、
在视图属性的下方,有一个设置视图跟随移动的条件
指代跟随的物体走到离屏幕边缘多近时,视图就开始根据人物移动,移动的速度为speed
设置完,运行游戏就能看到人物移动和镜头跟随移动的效果了
RPG_Basic(1):BASIC MOVEMENT AND COLLISIONS(基础移动和碰撞)
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
本节主要内容:
- 创建项目
- 创建精灵
- 创建对象
- 创建房间
- 开启物理引擎效果
- 将对象设置在房间某个位置
- 设置碰撞盒
- 使用键盘按键检测事件控制角色移动
基本概念
以下内容在官方帮助文档中,有更明晰和合理的解释,有疑问和错漏请参考帮助文档进行理解。
- 精灵Sprite:它们可以有独立的屏幕坐标、帧率、周期等等,在最终渲染时,它们会与背景等一起被合成为你看到的每一帧画面,它之所以被称为精灵,是因为Sprite单词含义为“精灵,鬼怪”,简单而言,可以理解成有动画的图片(和GIF动图的效果类似),更详细的内容请参考知乎。
- 对象Object:可以简单的理解成模版,学术化的语言,将其称为“现实事物的抽象,类”,但针对具体的游戏开发而言,举例如怪物模版,角色对象都是对象。
- 实例Instance:对象的一个现实例子,例如狗是对象,那么一只叫做Ryne的狗就是实例,通常在GMS2中对象是指在资源树上创建的Object对象,而实例是指在游戏运行时,以Object为模版,在游戏中实际的实例,这些实例具有一部分在Object定义的属性,如血量,速度等,但它是动态变化的,会受到其他的影响
- 房间Room:一般指的是游戏场景,游戏中角色所处的环境,分为多个种类层级(Layer),在表现上分为背景层,前景层,实例层。
创建项目:
命名项目名称为RPG Basics
选择储存文件的路径
设置项目参数
设置游戏运行帧数为30.
创建精灵
右键点击位于界面最左边的资源树,然后在呼出的二级面板,点击CreateSprite(创建精灵)即可创建新的
创建人物精灵,命名为spr_player
设置大小为32*32
点击Edit进入编辑图片
当我们临时没有美术资源时,可以利用Fill tool填充工具,将图片全部填充成蓝色,暂时替代一个美术资源来用
使用同样的方法,创建墙体精灵,命名为spr_wall,填充其颜色为黑色。
当我们有外部的美术资源,就在先在资源树创建精灵,不编辑和填充颜色,然后双击精灵,在弹出的界面点击import输入外部的图片为精灵
创建对象
同样的操作,在资源树里Create Object,创建玩家对象
双击对象,在弹出的界面,将其对象的Sprite精灵设置为spr_player
以同样的操作,创建墙体对象,obj_wall
创建房间
创建房间,命名为rm_one
在属性设置里面(双击房间,在默认视图的左下角),设置房间大小为640(width)*360(Height)
拖入房间对象
双击房间,在弹出的视图中,选中实例层(instance)
按住对象,拖动鼠标,单个拖入对象,像下图一样。
- 黑色方块:墙体
- 绿色小人:角色
这里有两个快捷操作
- 批量拖入(按住alt,可以单击房间连续拖入)
- 删除(选中,按住键盘Delete键,或者右键,在弹出的界面上点击delete )
- 批量删除(按住shift选取对象,可以进行批量选中)
运行游戏
点击左上的类似播放的按钮,可以运行游戏
可以看到如下效果,黑色墙体中间,有一个蓝色的小人
修改房间物理属性
点击资源树上的房间(一般创建项目后,会默认创建一个room),然后选中这个房间的物理属性,勾选enable Physics
将其Gravity_y修改为0,即Y方向没有力的影响,这样开启物理引擎影响的对象就会往下坠了。因为我们实际需求的是一个2D平面俯视图的效果。
设置对象的物理属性
在资源树上,选中对象obj_player,然后依次开启物理效果,然后修改碰撞盒形状为BOX,再匹配碰撞盒和精灵图片的位置。
实际上很多碰撞盒,不会和精灵图片完全吻合,比如上图中的碰撞盒只设置在下半身,想象在实际生活中,地面障碍可能与人发生碰撞的部位,就是脚部而已,所以类似的,在设置碰撞盒,从实际需求出发来设置碰撞盒的大小和形状。
按照同样的方法,设置obj_wall的物理属性和碰撞盒(这里要全覆盖)
需要注意的一点是,我们需要将墙体设置的密度(Density)设置为0,这意味着墙体是无限重的,能达到任意物体都无法移动它的效果(在房间开启物理效果的前提下)
创建移动代码
在资源树双击obj_player对象,在弹出的面板,点击Add Event,添加键盘输入检测事。这个事件能检测到键盘的左方向键被按下,这时,控制的角色向水平方向右侧移动4个像素,即向左行走,这个事件每帧都会检测,所以每帧都会发生移动(+ 4个像素距离)
同样的,添加其他三个方向键的检测事件,并且如下对应
phy_position_x -= 4;(左)
phy_position_y -= 4;(上)
phy_position_y += 4;(下)
运行游戏即可看到效果。
创建墙体碰撞
上个动图可以看到角色是可以穿过墙体的,所以需要为了obj_player创建和obj_wall的创建事件。
在碰撞事件中,不用书写任何代码,仅仅加上注释即可,但这声明了,obj_play和obj_wall是发生碰撞的。
修正物理旋转
运行游戏后,能看到角色在移动时,会被移动,为了修正这个问题,在obj_player对象的Create事件中,加入代码即可修复
phy_fixed_rotation = true;
QA
- QA1:phy_position_x和x有什么区别?
- QA2:添加collision事件,又不书写代码这是为什么。
附录(蓝色标识已完成):
- 1.BASIC MOVEMENT AND COLLISIONS(基础移动和碰撞)
- 2.STEP EVENT + VIEWS(步事件和视图)
- 3.VARIABLES AND CHARACTER SPRITES(变量和人物精灵)
- 4.DRAW EVENT, BACKGROUNDS, SCRIPTS, AND STATES(绘制事件,背景,脚本,状态)
- 5.SMOOTH VIEW MOVEMENT(平滑的移动视图)6.BETTER PLAYER MOVEMENT(优化角色移动)
- 7.GAMEPAD INPUT(手柄输入)
- 8.DASH STATE(冲锋状态)
- 9.ADDING ENEMIES, DEPTH, AND INHERITANCE(增加敌人,理解深度和继承)
- 10.PLAYER ATTACK STATE(攻击状态)
- 11.HITBOXES AND KNOCKBACK(受击盒和击退)
- 12.BETTER CHARACTER SPRITE CONTROL(角色精灵控制优化)
- 13.ADDING ENEMY STATES(怪物状态机)
- 14.ENEMIES THAT ATTACK(怪物攻击)
- 15.ADDING STATS FOR THE PLAYER(人物状态)
- 16.LEVEL UP!(升级)
- 17.LIMIT DASH AND FIX A BUG(冲锋限制、修复BUG)
- 18.FIXED ENEMY MOVEMENT(修复怪物移动)
- 19.MOVING BETWEEN ROOMS(场景切换)
- 20.ENEMY LOOT DROP(怪物掉落)
- 21.FIXED ENEMY MOVEMENT(修复怪物移动)
- 22.DIALOG(对话)
- 23.IMPROVED DIALOG(对话优化)
- 24.PAUSE MENU(暂停)
- RPG_Basic(25): 储存游戏的简单方式
- RPG_Basic(26): 读取存档的简单方式
- RPG_Basic(27): 发射炮弹Projectiles
- RPG_Basic(28): 优化人物控制
- RPG_Basic(29): 优化AI
- RPG_Basic(30): 简单的音效
相关参考资料:
- GMS官方说明文档
- [Beginner] Make an RPG--HeartBeast
- GameMaker: Studio 中文教程--青铜的幻想
- GMS2官方中文教程系列--顺子
- yoyogames官网--GameMaker Studio下载和注册
RPG_Basic(30): 简单的音效
Youtube视频地址:HeartBeast
以往的内容:
- RPG_Basic(25): 储存游戏的简单方式
- RPG_Basic(26): 读取存档的简单方式
- RPG_Basic(27): 发射炮弹Projectiles
- RPG_Basic(28): 优化人物控制
- RPG_Basic(29): 优化AI
- RPG_Basic(30): 简单的音效
首先介绍一个制作音效的网站:http://www.bfxr.net/.
这个网站上有预设的模版,如Pickup(拾取),Jump(跳跃)等,可以点击后,然后Play可以听听查看效果。
如果满足需求,可以点击Export Wav,导出wav音乐文件。
然后依次重命名为
snd_enemy_die
snd_player_takes_damage
snd_expr
snd_sword_attack
然后在我们需要播放音效的脚本文件中,插入代码 audio_play_sound(音效名称,优先级,是否循环)
运行游戏,攻击时,就可以听到音效。
同样的,在obj_damage的碰撞事件(和obj_player)中加入播放音效的代码
同样的,在obj_player的碰撞事件(和obj_expr)中加入播放音效的代码
同样的,在obj_enemy_chinlin和obj_enemy_slime的destroy事件中加入播放音效的代码
到这里简单的音效添加就完成了。
RPG_Basic(29):AI优化
Youtube视频地址:HeartBeast
实现效果为
首先打开obj_enemy_slime,在Create事件中,删除test变量,并将变量targetx,targety(基于向目标的方式来移动)改为xaxis, yaxis(基于渐近的方式来移动)
打开scr_enemy_choose_next脚本,在现有的代码,可以看到怪物巡逻的地点,是在房间内随机选取一个点。现在要做一个改变,将target_x个target_y的代码移除
换成xaxis, yaxis,在怪物处于巡逻的状态,选取一个新的移动轴来进行移动
同样的,scr_idle_state脚本不用更改,但怪物巡逻时每帧检测玩家的scr_check_for_player脚本,也要修改targetx, targety为xaxis, yaxis,
在scr_enemy_chase_state和scr_enemy_wander_state追击角色的脚本也同样要替换,并且移除掉条件
这样修改以后,会发现怪物巡逻的脚本scr_enemy_wander_state和怪物追击的脚本scr_enemy_chase_state脚本是一样的。
所以为了方便调用,新建一个脚本scr_move_axis(),来作为向着某个移动轴进行移动
此时保存代码,运行游戏会发现,怪物只会自由移动,而不会追击角色
这是因为在scr_move_axis()脚本中,怪物获取方向的代码不对。
因为xaxis和yaxis已经不再是某个坐标的xy值了,如果仍然用point_direction( x, y, xaxis, yaxis)当然是错的。
xaxis和yaxis实际上应该看待为移动速度的分量,所以这里直接改为point_direction( 0, 0, xaxis, yaxis)即可修复。
导入新的怪物精灵图片,全选所有图片,将其拖入GMS2的工作区
然后依次命名名称Name,设置图片原点Origin
spr_chinlin_run_up
spr_chinlin_run_down
spr_chinlin_run_right
spr_chinlin_stand_up
spr_chinlin_stand_rightS
复制obj_enemy_slime,将复制后的副本对象重命名为obj_chinlin,设置精灵图片为obj_chinlin_stand_right
并设置碰撞形状
由于该怪物对象是从obj_lifeform_parent继承而来,只具有最基本一些属性(例如死亡),如果想要额外的属性,可以创建一个从obj_lifeform_parent继承的怪物父对象,然后该怪物对象从怪物父物体继承就可以了,但这里只不打算额外增加了。
和角色创建movement和facing类似,这里也要在obj_enemy_chin的create事件里创建。首先新建一个宏变量IDLE
然后使用方向,状态变量,和二维数组来更新精灵子图片
由于额外增加了IDLE的状态,所以在scr_enemy_stall_state和scr_enemy_idle_state脚本需要为状态movement赋值
因为在移动时,我们只导入right方向上的精灵图片,这里利用sign函数,根据实例移动方向,对对象的精灵进行水平翻转,并且更新了movement为MOVE
修改函数scr_get_face脚本,添加一个参数作为脚本参数
但是因为在obj_player也使用了scr_get_face()脚本,这里改为传参的函数,那么其他地方也要跟着改变,怎么批量更改呢,这里可以按快捷按钮 Ctrl + Shift + F.会呼出一个弹窗,在弹窗搜索栏,直接输入scr_get_input搜索,会得到搜索结果
然后在obj_enemy_chinlin的step事件中也添加更新精灵索引代码
把obj_enemy_chinlin对象拖入房间,运行游戏即可看到效果
RPG_Basic(28):优化精灵控制
Youtube视频地址:HeartBeast
首先添加两个宏变量(Macros)
在obj_ player的create事件中:其中facing变量已有,所以需要再创建一个Movement来标识角色 状态
创建一个二维数组变量[dirction, state],基于方向和状态来存储角色的精灵信息。
来分别设置move状态下的精灵显示和attack状态下的精灵显示。
在obj_player的step事件中,使用刚才创建的二维数组变量来更新精灵索引
在scr_move_state脚本的第一行 ,令movement = MOVE,更新行动状态
值得一提的是如果是八方向的移动,在Macros的宏变量中,就需要添加 八个方向的定义,并在scr_get_face脚本中,dir应该除以45.变量face=8.
但这里只使用4个方向移动。以上就是movement和face(调用scr_get_face脚本)变量更新和赋值
由于我们已经使用二维数组变量来更新精灵索引了,所以可以移除在scr_move_state后面的更新精灵索引switch语句
运行游戏,可以看到效果,角色移动时,精灵会发生变化。
与更新movement为MOVE类似,在scr_attack_state的首行,更新movement = ATTACK;并且移除switch语句。
对应修改下面的代码
然后在scr_dash_state中也将movement =MOVE,因为冲锋过程和移动过程的动画是一样。
到此精灵控制优化就完成了
开了一个系列,记录自己学GMS2的过程,请指正
https://indienova.com/u/metaldudu/blogread/4294
目标是复制一个FC上的小蜜蜂。
初学,请大神们指点,遇到困难会来小组提问,哈哈。
RPG_Basic(27): 发射炮弹Projectiles
Youtube视频地址:HeartBeast
实现效果如下:
创建一个新对象obj_projectile,它和obj_damage类似。在create事件中,初始化
creator = noone;
knockback = 10;
phy_bullet = true;
实际上游戏引擎上的物体移动不是现实生活中连续运动的,而是闪烁移动的,从某个点接着闪烁到某个点,所以有时候会在游戏引擎内看到如果运动的速度太快,会导致物体穿过一面墙的错误。
这里会用到phy_bullet变量来设置为true.这个变量能够帮助我们穿墙的问题,然后对该对象的物理属性进行设置,除了Density密度,其他都设置为0
然后勾选Sensor,这意味着它不会与其他物体发生实体碰撞,而仅仅只是检测到物体之间的重叠。
然后添加和obj_wall的collision事件
instance_destory()
添加和obj_lifeform_parent形式的碰撞事件(对生命体进行伤害,计算击退距离)
我们需要使炮弹击中敌人时,对敌人造成伤害,然后移除,但是这个移除并不是立刻的,因为我们想要在击中的时刻也对附近的敌人造成伤害,所以我们并不打算在碰撞检测到的事件中立刻将敌人删除,而是在下一帧中移除,所以这里引入了一个alarm[0]变量,并创建alarm[0]事件。在该事件中来移除实例对象。
这样它就会击中多个对象。
设置obj_projectile的经理为黑色的圆形spr_damage,并且确保碰撞遮罩形状选取为circle
obj_projectile已经完成了,所以接下来是怎么在游戏中创建该实例,首先,在输入检测脚本scr_get_input中,增加spell_key来控制发射炮弹
炮弹的发射是角色来触发的,所以在角色的行动状态scr_move_state脚本,在attack_key检测后面,增加发射炮弹的spell_key的检测。以20的速度发射,然后分解到x和y方向上的速度,然后使用with语句,对炮弹实例施加冲击力。
到此,发射炮弹的即完成了。
RPG_Basic(26): 读取存档的简单方式
Youtube视频地址:HeartBeast
读档和存档复杂的部分(tricky part)不是如何读取存档信息,复杂的是找到合适的时机和合适的方式去处理信息。
三个部分
1.当我们暂停游戏后,回到场景
2.当我们传送到下一个房间(场景时)
以上两个事件都包含room start 事件(在房间开始初始化时)
3.当我们在pause_menu中,选择Load时
## 创建三个宏变量来代表以上三种情形(Scenarios)
PS:由于GMS2取消了GMS1的All Config的声明Macro变量的方式,我这里采用在第一个房间的初始化
RETURN = 0;
NEW_ROOM = 1;
LOAD = 2;
## 在obj_player_stats对象中的Create事件中,创建ex,ey变量用来实时储存玩家的位置,以便读档时调用
如果存在玩家对象,就记录当前位置
如果不存在,就令其 = (0,0)
将房间开始动作,声明为NEW_ROOM。
### 创建scr_load_game脚本
先定义临时file变量来存储已经打开的存档文件
然后再以读取文本的格式来打开该文件,并赋值给临时变量save_string.关闭文件。
因为信息是经过base64加密的,再次读取信息需要解密
json_decode()解码出来的变量会被GMS2自动转为DS_map结构。
然后依次将其中的信息取出来
然后销毁这个DS_map结构
## 然后在obj_player_stats事件中,选择创建other event中的room_start事件,来储存玩家的开始位置
在从(obj_player_stats对象的step事件中)之前的房间进入到暂停菜单前,需要开启房间的数据持久化
同时需要在返回之前的房间前,记录这个返回操作为room_start_action,并且它的类型为RETURN
然后再在obj_player的room_start事件中,开启obj_player的数据持久化(以便读取功能生效)
然后回到obj_pause_menu的step事件中,补全case2中的储存的游戏的代码:
调用obj_player_stats的room_start_action动画,然后前往obj_player_stats记录的房间
然后运行游戏,在第一个房间先save存档,开始穿过传送门,打开Menu菜单load游戏,报一个错误
它的意思是想要读取的文件"file"没有被打开。回到scr_load_game脚本,发现原因是错误使用的save.txt的命名,将其修改为在25节中的文件命名“mysavegame.txt”
再次运行游戏,重复存档,读档操作,发现读档后,场景发生了变化,但是角色对象实例没有被创建出来
回到scr_save_game,修改错误的x,y赋值,令其等于之前的储存的玩家位置
移除在obj_player_stats对象的step事件中的room_start_action动作
将其移动到obj_pause_menu的step事件中的case1下
至此存档就完成了
GMS跨平台的原理是什么?
是针对不同的平台,生成对应的原生代码吗?还是像RPGMAKER那样跨平台都是用h5实现的?
如果是生成原生代码的话,这个坑是如何填起来的, 相比UNITY在这块都不能那么麻溜。
在GMS2中配置iOS设备调试
GameMaker Studio 2的移动版可以支持iOS和Android双平台的导出,之前我曾经写过Android平台的真机调试的配置方式,前阵子有朋友在后台问我怎么导出iOS版本,因此今天就来介绍一下。
注:GameMaker Studio 2导出iOS版本必须有一台Mac用于编译,并且必须拥有苹果开发账号,本文不涉及注册账号的内容,假定有这需求的朋友自己已经搞定账号和证书了
概述
首先,在IDE的右上角可以切换编译和调试用的平台,选择iOS即可,同样这里有VM和YYC两个选项,这里稍微说明一下这两种编译方式的区别:
VM模式是通过一个叫“YOYO Runner”的特殊运行器直接运行你的代码,而YYC模式则是完全编译成对应平台的原生代码再执行,前者可以理解成是使用GMS2自带的一个万能模拟器来运行你的游戏。
VM模式的运行效率稍微低一些,但是编译速度更快,并且可以使用调试模式(Debugger),而在YYC模式下是无法调试游戏的,当然YYC模式的运行效率更高性能更可靠,不过如果游戏比较大逻辑比较复杂的话编译的时间可能会比较长,各位可以自行权衡取舍。
如果你是用Windows开发的游戏,想要最终输出iOS版的话,也务必需要准备一台Mac电脑,因为GMS2只会导出Xcode的工程文件,还是要在Mac中使用Xcode来最终打包编译游戏的,而且官方说必须是真机,Mac虚拟机用不了。
设备配置
在YYC模式中点击笔形图标即可管理用来运行和调试的设备,跟之前Android设备的管理类似。
在设备列表中默认会同步Xcode中的设备列表进行展示,而进入设备管理界面后如下图:
点击“Detect Device”可以重新检测设备的连接情况,如果你的设备没有显示出来可以检查一下数据线有没有问题并重新插拔一下设备,也有可能是Xcode的同步有问题,可以先启动Xcode然后等几分钟再尝试检测设备。
另外,这里的列表不仅会显示已连接的iOS设备,在Xcode中创建的各个iOS模拟器也都会显示出来,并同样可以用于游戏调试,不过如果使用模拟器调试游戏,最好现在Xcode中把模拟器打开,因为临时启动模拟器耗时较长,另外如果你的Mac内存不够的话,可能会在这个过程中出错导致编译失败。
平台设置
在GMS2的设置项中可以设置输出Xcode工程的路径以及默认的证书信息
如果你的Mac尚未完成开发环境的配置,可能会导致后续的编译工作出问题,另外图中的复选框如果勾选上,在后续调试时项目会自动编译并打开,如果没有勾上,则项目只会被发送到Xcode中需要你手动编译运行。
测试项目
当完成以上所有配置以后,无论使用VM或YYC模式,只要点击“运行”按钮就可以编译游戏,项目会被编译成Xcode工程发送到Xcode中。
如果这是第一次测试该项目,还需要在项目的配置项中做一些调整,如下图所示:
你会发现在“Status”处有红色感叹号,这就意味着配置有问题无法正确编译执行,此处需要修改对应的“Bundle Identifier”。
你需要修改默认配置的包名“com.company.game”为你自定义的游戏包名,这个包名可在此处手动修改,在GMS2中也可事先定义,当修改完成后正常显示应该如下图所示:
当所有的警告信息消失后就可以开始运行游戏了,当你点击Xcode的运行按钮时,游戏将在你选择的设备(或模拟器)上运行起来。
以上最后关于Xcode的使用部分未进行太过详细的说明,相信准备编译iOS版本的同学自己应该已经从其它渠道对iOS的开发和编译有一定程度的了解了:)
游戏暂停的简单实现(2/2)
大家好,距离上一篇说游戏暂停实现机制的文又过去快半个月了,恰好HeartBeast第14集教程也是介绍如何实现游戏暂停,并且就是我们上次提到的将当前场景对象临时禁用的这种方法,今天我们就来介绍一下这一种实现游戏暂停的方式。
不同于我们上次介绍的方式,这个方法是在游戏当前场景中进行暂停,不涉及游戏场景的切换,同时引入了持久化对象的概念,对象属性勾上了持久化(Persistent)以后,该对象一旦创建实例,对应的实例就会存在于后续所有的游戏场景中(除非手动销毁),从而可以用于控制一些所有场景中都需要调用的方法和机制。
本文的具体操作会有别于HB的教程,但思路是一致的
HeartBeast的教程已经传到B站了——简单实现游戏暂停
控制暂停对象
如上文所说,我们需要创建一个新的对象,并把对象的持久化属性打上勾
接着我们创建一个“Create”事件,并在其中定义两个全局变量:
global.paused_ = false ;
global.screen_pic = noone;
这里我们使用全局变量的目的是便于不同的对象都能正确调用和修改这两个值,这两个变量分别是:
- paused_:标识当前是否暂停状态
- screen_pic:用来保存暂停前的游戏画面
因为刚才我们创建的是一个暂停按钮,我希望用户在点击这个按钮时能触发暂停操作,因此可以创建一个点击事件,这里使用“Tap”或“Mouse->Left Down”都可以
然后在这个事件里写暂停相关的代码:
if !global.paused_{
global.paused_ = true
if sprite_exists(global.screen_pic) sprite_delete(global.screen_pic) ;
global.screen_pic = sprite_create_from_surface(application_surface,0,0,room_width,room_height,false,false,0,0)
instance_deactivate_all(all)
instance_create_layer(room_width/2,room_height/2,"pipe",obj_continue)
}
这段代码整体在一个if判断中,第一哈昂的if判断就是判断当玩家点击这个暂停按钮时,当前游戏是否处于“非暂停”即正常游戏状态,注意这里我们前面用了一个感叹号"!"来表示否定,这里也可以使用下面这种写法(HB就是这么写的)
if not global.paused_
当判断条件成立时就直接进入执行暂停的操作,首先是把这个暂停的变量设置为“true”标记将要进入暂停状态
然后我们要给当前画面截屏,在截屏之前我们也做了一个if判断,这个判断是判断当前游戏中是不是已经有相同名称的截屏存在,如果有则要先把之前的截屏删除
这里用了sprite_exists()来判断精灵图像是否存在,然后用sprite_delete()来执行删除操作
if sprite_exists(global.screen_pic) sprite_delete(global.screen_pic) ;
然后我们创建一个新的截屏图像
global.screen_pic = sprite_create_from_surface(application_surface,0,0,room_width,room_height,false,false,0,0)
这里我们使用sprite_create_from_surface()这个方法获取当前应用窗口的界面图像并生成一个精灵图像,然后将其保存到了之前创建的全局变量global.screen_pic中。
这里使用的application_surface是一个默认的surface表面,就是当前整个应用窗口的表层图像窗口。
然后我们用 instance_deactivate_all()这个方法禁用所有的实例,并单独新建了一个用于恢复游戏的按钮"obj_continue"
instance_deactivate_all(all)
instance_create_layer(room_width/2,room_height/2,"pipe",obj_continue)
在HB的教程中因为他使用的是键盘按键来控制暂停事件,因此直接禁用和恢复实例就能实现暂停的效果,而我们这里是通过点击画面中的按钮来实现的,因此恢复游戏的按钮也需要单独创建出来才可以。
恢复游戏对象
好,在刚才我们已经完成了游戏的暂停操作,但是我们要如何解除暂停状态恢复正常游戏呢?
首先同样新建一个对象,由于这个对象也是一个按钮,我们同样创建一个“Tap”事件来执行恢复游戏的操作
在这个“Tap”事件中我们放入的代码如下:
instance_activate_all();
global.paused_ = false;
instance_destroy();
第一行是用instance_activate_all()恢复整个场景中所有的实例,这些实例会保留禁用之前的所有属性,包括坐标、状态等等。
第二行则是重新将暂停状态标记为“未暂停”的状态,表示即将正常恢复游戏
最后则是把自身这个恢复游戏的按钮销毁,因为在正常的游戏过程中这个按钮应该是不会出现的。
到此为止,其实游戏的暂停和恢复基本已经完成了,但是还不够完美。
因为在暂停按钮被按下时所有的实例都被禁用了,因此整个场景中所有的对象实例都会消失不见,整个画面是空的(tiles和背景等还会保留)
希望你还记得我们之前曾经在暂停时单独保存了一张游戏画面的截屏,也希望你在上一张图片中有留意到这个恢复游戏的按钮事件中有一个“draw”事件,我们就要在这个“draw”事件中把之前的截屏绘制到屏幕上,用这张图片来欺骗玩家的眼睛,让他以为因为游戏被“暂停”了所以上面的内容都静止不动了。
draw_sprite(global.screen_pic,0,0,0)
draw_self()
在这个draw事件中,我们首先把刚才截屏画面的精灵图像直接绘制到屏幕上,然后同样不要忘记当我们自定义“draw”事件以后一定要加上一个"draw_self",否则这个按钮本身就不会被绘制出来了。
这里还要注意这两行代码的顺序,因为根据绘制顺序不同的内容的深度也不同,后绘制的内容在上方,如果你先写了"draw_self",那绘制截图的时候就会把按钮给挡住,玩家同样会看不到那个恢复游戏的按钮了。
以上就是通过禁用实例和恢复实例的方式来实现游戏暂停机制的方法。
HB的教学视频中使用了用户事件来封装暂停的操作,而其实也可以用脚本来单独封装,HB没有使用脚本的原因是他的教学视频采用了试用版,对脚本的数量有限制。
而我也没有封装这个暂停操作,是因为感觉没什么必要,好像并没有很多不同的地方会去触发暂停,既然没什么复用的需求也就偷个懒了