我想要下载图片上甜蜜冲刺有这个游戏吗,但是忘记了甜蜜冲刺有这个游戏吗叫什么,请求帮助

当前位置:
安游我的世界下载站:我的世界中国版玩家下载后的游戏或者其他资源包类的已经不想要了的话,是可以将下过东西删除的,具体在哪里删除呢?请看下图所示……
编辑:禾木程发布时间: 13:43
  中国版玩家问题:
  在中国版服务器下载后的游戏、地图、材质包等等在哪里删除?
  安游我的世界下载站:
  我的世界中国版玩家下载后的游戏或者其他资源包类的已经不想要了的话,是可以将下过东西删除的,具体在哪里删除呢?请看下图所示:
  在右上角&游戏管理&中找到你下载的单人或者多人游戏,点击进去删掉不想要的东西吧!
  安游我的世界官方群:欢迎各位我的世界小彩笔加入讨论,老司机带着小编飞!
  以上就是我的世界中国版中关于怎么删除下载后的东西的详细解答内容了,如果您还有其他问题可以关注安游我的世界专区问答站哦!
经营许可证:皖ICP备号-1,QQ:1104747 、5633249你可能还会喜欢
i wanna系列精品游戏
<div class="spf_off1" style="width:%">10分
<div class="spf_off1" style="width:%">5分
<div class="spf_off1" style="width:%">10分
<div class="spf_off1" style="width:%">10分
<div class="spf_off1" style="width:%">7.5分
<div class="spf_off1" style="width:%">5.0分
<div class="spf_off1" style="width:%">5.0分
<div class="spf_off1" style="width:%">10分
从2008年-2014年 快猴网一直在努力做到最好 Www.KuaiHou.Com
备案编号:粤ICP备号-1&figure&&img src=&https://pic3.zhimg.com/e77ee79fd9fdafeaa491847_b.jpg& data-rawwidth=&800& data-rawheight=&450& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/e77ee79fd9fdafeaa491847_r.jpg&&&/figure&&h2&编者按&/h2&&p&为了获得更好的阅读体验,欢迎前往&a href=&http://link.zhihu.com/?target=http%3A//indienova.com/indie-game-development/gms-tutorial-1-introduction-and-installation/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&原链接&/a&阅读本系列教程。&/p&&p&GameMaker: Studio 是一款非常值得推荐的 2D 引擎,非常适合用于快速原型开发或者中小型的独立游戏开发项目。 indienova 会员&b&青铜的幻想&/b&为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程,本文为第一期,主要对这款引擎进行简单的介绍和安装说明。欢迎读者朋友在文章后留言,以便作者能够针对性地安排接下来的教程内容。&/p&&h2&介绍&/h2&&p&在经历了数年的游戏开发后,我既参与了 AAA 级的商业大作,也完成过个人或小团队的休闲小品。如今,当有人向我寻求建议问,“我想要开发 2D 独立游戏,应该使用什么引擎?”。我会向他推荐 GameMaker: Studio(简称GMS)。是的,不是 Unity,而是 GMS。&/p&&p&GameMaker 的初次发布是在 1999 年,在很长一段时间内我都只是把它当成是一个玩具引擎,认为它存在的主要意义是给那些想尝试制作游戏的初学者一种“喔,我可以做游戏了!”这样的感觉,潜台词就是这引擎没法做出真正意义上完整的游戏出来。让我转变这种观念的原因有两点:一是 GMS 经过了时间的考验,二是一大批优秀的由 GMS 做出的游戏。&/p&&p&从1999年 GameMaker 的初次发布至今,已经有 17 年了,期间有着持续的版本更新。从最早的 Windows 平台,到如今支持几乎所有的手机、主机平台。一个游戏引擎能够生存并发展起来,本身就是对这个引擎最好的肯定,因为这意味着该引擎具有良好的生态环境。引擎有开发人员的持续更新、支持和维护,对于游戏制作者来说有易用的文档、教程及可以进行交流的社区,同时使用该引擎能够制作出足够优秀的游戏作品来盈利。只有拥有这样良性的游戏开发循环,才能维持引擎本身的发展。&/p&&p&而由 GameMaker 制作出的优秀游戏作品,是引擎本身良性发展的结果和佐证。相信 YoYo Games 公司(GameMaker 的开发商)也对此十分自豪与自信,在主页中设立了Show Case 专栏展现由其开发的优秀作品。其中古有《洞穴探险》(Spelunky)这款自2008年发布就收到好评无数,相继登录各大主机平台的经典2D横版冒险游戏;今有 IGN 评出的 2015 年“年度最佳电脑游戏”(PC Game of the Year)——《地下传说》(Undertale),以及 Crashland、Hyper Light Drifer 等今年表现上佳的新游戏。这些优秀的游戏告诉我们,只要你有创意,限制你的绝不会是技术及引擎。&/p&&p&Spelunky&/p&&p&&figure&&img src=&https://pic1.zhimg.com/ccfca3dbe8771fcee68ec_b.jpg& data-rawwidth=&500& data-rawheight=&281& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic1.zhimg.com/ccfca3dbe8771fcee68ec_r.jpg&&&/figure&Undertale&/p&&figure&&img src=&https://pic3.zhimg.com/c71e3eca368edbd014e1c049ee896e5c_b.jpg& data-rawwidth=&500& data-rawheight=&281& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic3.zhimg.com/c71e3eca368edbd014e1c049ee896e5c_r.jpg&&&/figure&&p&Crashland&figure&&img src=&https://pic4.zhimg.com/9f92abab7f0d5cb06e6c25e1e199b9d4_b.jpg& data-rawwidth=&500& data-rawheight=&281& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic4.zhimg.com/9f92abab7f0d5cb06e6c25e1e199b9d4_r.jpg&&&/figure&&/p&&p&Hyper Light Drifer&figure&&img src=&https://pic2.zhimg.com/440ebd3bcb26f194d53f2_b.jpg& data-rawwidth=&500& data-rawheight=&281& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic2.zhimg.com/440ebd3bcb26f194d53f2_r.jpg&&&/figure&&/p&&p&当然,我也在标题中说明了该引擎的最佳适用范围。一是 2D 游戏,因为 GMS 对 3D 的支持非常有限,和其他成熟的 3D 引擎相比完全没有优势。二是独立,因为 GMS 对版本管理的支持不是太好(可以使用 SVN 进行版本管理),这决定了它并不适合大规模的团队开发。&/p&&p&最后再谈谈 Flash 及 Unity,如果除开 GMS,那么也许在2010年以前,Flash 是一个不错的选择。之后随着 Flash 的衰退和 Unity 的兴起,以及 2013 年 Unity 4.3 版本开始内置对 2D 游戏的支持,Unity 成为了一个比 Flash 更好的选择。但这两个引擎的问题在于依然过于强大——即过高的自由度。强大的工具所带来的问题是复杂度的提升,这对于独立开发来说意味着更多的工作量。对于独立游戏开发个人和团队来说,最理想的游戏引擎是刚好满足自己游戏所需的功能,多余的功能一个都不要有。&/p&&p&从另一个角度来看,如果你想做的仅仅是一个传统的日式 RPG,那么也许你的最佳选择是 RPG Maker。但如果你想在游戏中有更多的动作、操控的部分,例如射击游戏、动作 RPG、平台游戏或横版过关游戏等等,从功能和复杂性的平衡出发,GameMaker: Studio 是最适合的引擎。&/p&&h2&免费版安装流程&/h2&&p&如果你决定为你的独立游戏选择 GameMaker: Studio(简称 GMS)作为开发引擎,或者是想要尝试体验一下 GMS 的游戏开发环境,你需要做的第一步是安装 GMS。同 Unity 一样,GMS 为个人或者独立游戏开发者提供了免费版本。免费版本在游戏开发及调试功能上与专业版并无区别,你完全可以等到游戏完成之后再花 100 美元左右升级到专业版发布游戏。&/p&&p&GMS的安装方式有两种:一是通过官方网站下载、注册及安装,二是通过 Steam 安装。这里首先介绍官网的安装流程,但如果你已有 Steam 账号的话,通过 Steam 平台安装将更为简便快捷(从官网买的账号可以转成 steamkey,具体见&a href=&http://link.zhihu.com/?target=http%3A//help.yoyogames.com/hc/en-us/articles/-Getting-a-Steam-key& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&)。&/p&&p&GMS的官方网址是:&a href=&http://link.zhihu.com/?target=http%3A//www.yoyogames.com/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&yoyogames.com/&/span&&span class=&invisible&&&/span&&/a&&/p&&figure&&img src=&https://pic2.zhimg.com/4c60badb0d4c562c4fba_b.jpg& data-rawwidth=&1361& data-rawheight=&709& class=&origin_image zh-lightbox-thumb& width=&1361& data-original=&https://pic2.zhimg.com/4c60badb0d4c562c4fba_r.jpg&&&/figure&&p&首页上展示的游戏是当前大热的生存动作游戏 CrashLands(《崩溃大陆》),说实话当我刚刚知道这个游戏是由 GMS 所开发的时候,有些吃惊,因为这款游戏的巨大无缝世界场景,我原本以为 GMS 是无法胜任的。点击右上角的“GET GAMEMAKER”就会来到所有 GMS 版本的比较与下载页面:&/p&&figure&&img src=&https://pic2.zhimg.com/5e19b756e7c86cecd46a8_b.jpg& data-rawwidth=&1365& data-rawheight=&711& class=&origin_image zh-lightbox-thumb& width=&1365& data-original=&https://pic2.zhimg.com/5e19b756e7c86cecd46a8_r.jpg&&&/figure&&p&在这个比较表格中,可以看到免费版与专业版及大师版的主要区别在于没有以下功能:&/p&&ul&&li&Customisable Splash Screen: 可定制的启动页面&br&&/li&&li&Early Access: 早期功能试用&br&&/li&&li&Marketplace selling: 在yoyogames网站上销售你的游戏&br&&/li&&li&Mobile Testing: 在手机上测试游戏&br&&/li&&li&Export Modules: 各平台导出模块&br&&/li&&/ul&&p&但其实对于独立游戏开发来说,目前我们只需要有全功能的引擎(Fully-Featured Engine)就足够了。&/p&&p&接下来点击 FREE 版本那一列下方的“免费下载”(FREE DOWNLOAD),这时会根据你当前是否已经登录了你的 yoyogames 账号而跳转到不同页面。&/p&&p&如果你已经登录,就会直接来到下载链接页,若没有登录,则会跳转到登录与注册页面,如下:&/p&&figure&&img src=&https://pic2.zhimg.com/db_b.jpg& data-rawwidth=&1348& data-rawheight=&1163& class=&origin_image zh-lightbox-thumb& width=&1348& data-original=&https://pic2.zhimg.com/db_r.jpg&&&/figure&&p&按顺序填写及重复自己的邮箱及设定登录密码,然后在 Licence key 那一栏暂时不用填写。比较好玩的是“进行人机身份验证”那里,我的题目是这样的:&/p&&figure&&img src=&https://pic4.zhimg.com/8d6cd9bccdf04cb7cca3c491_b.jpg& data-rawwidth=&1349& data-rawheight=&675& class=&origin_image zh-lightbox-thumb& width=&1349& data-original=&https://pic4.zhimg.com/8d6cd9bccdf04cb7cca3c491_r.jpg&&&/figure&&p&如果你是机器人,那么估计难以通过这一步了。最后点击“注册”按钮完成注册。注册完成之后你的邮箱会收到一封标题为“YoYo Account: User Activation Required”的邮件,点击邮件正文中的激活链接会再次将你带到登录与注册页面并提示你的账号已经激活,此时你就可以通过左边的登录框输入邮箱密码登录了。&/p&&figure&&img src=&https://pic1.zhimg.com/b6b75468edb12a6cd2bfbbc_b.jpg& data-rawwidth=&586& data-rawheight=&354& class=&origin_image zh-lightbox-thumb& width=&586& data-original=&https://pic1.zhimg.com/b6b75468edb12a6cd2bfbbc_r.jpg&&&/figure&&p&登录后就会来到下载链接页,点击中间的下载链接即可开始 GMS 免费版本的下载。值得注意的是因为我们之后还要回来申请一个免费的 License Key,因此先不要关闭这个页面。&/p&&figure&&img src=&https://pic4.zhimg.com/70f09efb5c5_b.jpg& data-rawwidth=&1196& data-rawheight=&454& class=&origin_image zh-lightbox-thumb& width=&1196& data-original=&https://pic4.zhimg.com/70f09efb5c5_r.jpg&&&/figure&&p&在安装文件下载完成后运行,整个安装过程中只需一路狂点“下一步”或者“同意”即可,我个人的经验是在安装 GameMaker: Player 的进度条走到最后一点的时候停了一段时间,但耐心等待即可完成安装。&/p&&figure&&img src=&https://pic1.zhimg.com/acc7e9a5de9b76b914f1723_b.jpg& data-rawwidth=&596& data-rawheight=&490& class=&origin_image zh-lightbox-thumb& width=&596& data-original=&https://pic1.zhimg.com/acc7e9a5de9b76b914f1723_r.jpg&&&/figure&&p&如果你没有去掉“启动 GameMaker-Studio 1.4”这个选项,那么在点击完成后就会自动运行 GMS。在 GMS 启动以前会进行自动更新检测,更新完成后就会来到欢迎页面。&/p&&figure&&img src=&https://pic1.zhimg.com/d31abe2afff_b.jpg& data-rawwidth=&898& data-rawheight=&638& class=&origin_image zh-lightbox-thumb& width=&898& data-original=&https://pic1.zhimg.com/d31abe2afff_r.jpg&&&/figure&&p&在这个页面,你不仅需要你的邮箱和密码,还需要一个 License Key 才能登录,因此我们需要再回到之前提到过的下载网页来申请这个 License Key。&/p&&figure&&img src=&https://pic2.zhimg.com/bbf6aad07acb_b.jpg& data-rawwidth=&1201& data-rawheight=&1129& class=&origin_image zh-lightbox-thumb& width=&1201& data-original=&https://pic2.zhimg.com/bbf6aad07acb_r.jpg&&&/figure&&p&点击图中红色框位置的“获取免费 GMS 注册码”链接后,该网页会自动刷新,同时将注册码显示在相同的位置。&/p&&figure&&img src=&https://pic4.zhimg.com/a667ebfddc72_b.jpg& data-rawwidth=&1012& data-rawheight=&288& class=&origin_image zh-lightbox-thumb& width=&1012& data-original=&https://pic4.zhimg.com/a667ebfddc72_r.jpg&&&/figure&&p&将该注册码复制粘贴到 GMS 的欢迎页面,再填上你注册的邮箱和密码,就可以完成登录 GMS 了。在登录成功后 GMS 会提示你重新启动,手动关闭 GMS 再打开,看到这个页面时,即说明安装完毕:&/p&&figure&&img src=&https://pic3.zhimg.com/211cfc8b9c99cc02aa16c34c727d0914_b.jpg& data-rawwidth=&1092& data-rawheight=&653& class=&origin_image zh-lightbox-thumb& width=&1092& data-original=&https://pic3.zhimg.com/211cfc8b9c99cc02aa16c34c727d0914_r.jpg&&&/figure&&p&好了,GameMaker:Studio 现已安装在了你的电脑上,你已经迈出了你的独立游戏之旅的第一步,敬请期待后续教程。&/p&&h2&一些资料&/h2&&p&在接下来的教程中,会详细介绍使用 GMS 制作游戏的具体流程和方法。下面列出一些相关的资料,供读者参考:&/p&&ul&&li&&a href=&http://link.zhihu.com/?target=http%3A//www.yoyogames.com/learn& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&官方教学视频&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=https%3A//docs.yoyogames.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&官方文档&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=http%3A//deciia.wang/GMS_help/index.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&某不完全汉化的官方文档&/a&(感谢新浪微博 @decii)&/li&&li&&a href=&http://link.zhihu.com/?target=http%3A//www.gmlscripts.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GML 脚本收集&/a&&/li&&li&&a href=&http://link.zhihu.com/?target=http%3A//www.gmtoolbox.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GML 插件收集&/a&&/li&&/ul&
编者按为了获得更好的阅读体验,欢迎前往阅读本系列教程。GameMaker: Studio 是一款非常值得推荐的 2D 引擎,非常适合用于快速原型开发或者中小型的独立游戏开发项目。 indienova 会员青铜的幻想为希望了解学习 GameMaker: Studio 的中文读者专门撰写…
&figure&&img src=&https://pic2.zhimg.com/877e108b9077dfb9dd25f8_b.jpg& data-rawwidth=&800& data-rawheight=&450& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic2.zhimg.com/877e108b9077dfb9dd25f8_r.jpg&&&/figure&&h4&引言&/h4&&p&indienova 会员&a href=&http://link.zhihu.com/?target=http%3A//indienova.com/u/%59D%5E9%59C%59A%5E5%5BB%583%2525B3& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&青铜的幻想&/a&为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程。由于他目前正在参与的《冰杖秘闻》开发工作日益繁重,本次教材将是正篇教材的最后一期,之后会以特别篇的形式进行小主题更新,大家对这个系列有什么建议也欢迎留言说明。&/p&&h4&教程文件&br&&/h4&&p&本次教程共享至&a href=&http://link.zhihu.com/?target=https%3A//github.com/akane2002/GMS_TUT& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&& GitHub &/a&的资源文件分为两部分:&/p&&ul&&li&其一位于GMS_TUT_07_BASE目录中,是对上一次教程的项目文件进行重构后的结果,作为此次教程后续内容的基础。&/li&&li&其二位于GMS_TUT_07目录中,包含此次教程完成后的全部内容。&/li&&/ul&&h4&教程目标&/h4&&p&在上一次教程里,我们加入了恶魔行者这个敌方角色,但只是作为一个傀儡的存在。这一次,我们要将他变成疾行在黑暗中的冷血杀手!&/p&&h4&再次重构&/h4&&p&如果你看过上一次的教程,应该会还记得恶魔行者的脚本 DevilCreate、DevilStep 等最初是从伊瑟拉的脚本复制过来,然后进行修改的。当时的出发点是为了能够复制一些必要的功能过来,从而快速的添加一个简单的恶魔行者。我们对比一下现在这两者脚本的差异,以 YseraCreate 脚本和 DevilCreate 脚本为例:&/p&&figure&&img src=&https://pic1.zhimg.com/2b78a586ea0126b6eee9ce0fd8c84210_b.jpg& data-rawwidth=&1163& data-rawheight=&617& class=&origin_image zh-lightbox-thumb& width=&1163& data-original=&https://pic1.zhimg.com/2b78a586ea0126b6eee9ce0fd8c84210_r.jpg&&&/figure&&p&除了前面声明枚举变量 PlayerDirection 的地方之外,唯一的差别就是在后面添加了这几行代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_attachedHitbox = instance_create(x, y, obj_hitbox);
m_attachedHitbox.m_attachedParent =
m_isDead =
&/code&&/pre&&/div&&p&主要作用是为角色添加碰撞盒、增加生命值变量和标志角色死亡的变量,但我们想想看,其实这部分的代码正是伊瑟拉也需要的功能。这说明两者的创建脚本是完全一样的,这样的重复就是我们需要为这两个人物提取出一个共同的父类的强烈信号。&br&于是我们新建一个Object类别叫做obj_character(人物类别)作为他们的父类,它在这个游戏的类别继承关系中是这样的,在加入它之前:&/p&&figure&&img src=&https://pic4.zhimg.com/aa70a32f650d972bc190e2d_b.jpg& data-rawwidth=&863& data-rawheight=&377& class=&origin_image zh-lightbox-thumb& width=&863& data-original=&https://pic4.zhimg.com/aa70a32f650d972bc190e2d_r.jpg&&&/figure&&p&在插入人物类别以后:&/p&&figure&&img src=&https://pic3.zhimg.com/f717c9f2fc_b.jpg& data-rawwidth=&861& data-rawheight=&538& class=&origin_image zh-lightbox-thumb& width=&861& data-original=&https://pic3.zhimg.com/f717c9f2fc_r.jpg&&&/figure&&p&具体的重构过程不再赘述,主要的思路就是将两者共有的代码移入父类的对应脚本中,而具体取值的差异,例如伊瑟拉的站立动画是spr_ysera_idle,恶魔行者的站立动画是spr_devil_idle。对于这种情况可以新建一个变量spr_character_idle,然后对两者分别赋值即可。这里给出一个重构前后的代码行数对比:&/p&&br&重构前共计 252 行代码:&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&YseraCreate:14
YseraStep:107
YseraAnimationEnd:11
DevilCreate:12
DevilStep:90
DevilAnimationEnd:17
DevilOnDamage:1
&/code&&/pre&&/div&重构后变动如下:&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&YseraCreate:12
YseraStep:64
YseraAnimationEnd:该脚本删除
DevilCreate:12
DevilStep:38
DevilAnimationEnd:该脚本删除
DevilOnDamage:该脚本删除
CharacterCreate:33
CharacterStep:52
CharacterAnimationEnd:17
CharacterOnDamage:1
&/code&&/pre&&/div&&br&&p&重构之后的代码总行数变为 229 行。减少的部分主要来自于从两个子类移入父类的代码,而增加的部分来自于新增变量的初始化和在子类中赋值的部分,但总的代码量还是减少了。重构后的项目文件已放入 GitHub 项目的 GMS_TUT_07_BASE 文件夹中,作为本次教材的基础供读者参考。&/p&&h4&AI行为模式&/h4&&p&说起 AI,常常会给人一种高深、神秘的感觉。但现实情况是,高深而神秘的 AI 一般只存在于学术界的前沿课题中,而游戏里需要具体实现的 AI 系统通常只是运用了简单的“规则”。&/p&&blockquote&由于知乎图片大小限制,下面几段的图片请移步&a href=&http://link.zhihu.com/?target=http%3A//indienova.com/indie-game-development/gamemaker-studio-8-enemy-and-ai/%23iah-5& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&查看。&/blockquote&&p&为了让我观点更具有说服力,我特意挑选了一款最近玩过的游戏《挺进地牢》来作为验证。首先让我们来看看以下场景。&/p&&p&这里你看到了什么?只是一群疯狂追逐着你的敌人和弹幕,你很难察觉到规则的存在。但如果我们将这个场景中三种敌人的行为分解开来看,你就会发现第一种敌人是一个一次射击一发子弹的杂兵。&/p&&p&在这个动图里可以清楚的看到它的所有逻辑可以很简单的描述成:靠近至主角到一定距离后以一定的频率向主角射出一发子弹。&br&第二种敌人和前者类似,唯一的区别是发出一圈扇形的子弹。&/p&&p&第三种敌人同样是尝试接近游戏主角,只是到一个更近的距离,然后在一段时间蓄力后冲撞玩家人物。&/p&&p&如果单看每种敌人,逻辑都很简单,但当策划把不同种类的敌人放在一起,他们之间的各种组合以及玩家自发的反应结合起来,就奇妙的形成了一个生机勃勃的战斗场景。&/p&&p&因此对于我们要赋予恶魔行者的AI,也并不需要多么复杂,根据我们的策划案,恶魔行者是一个敏捷的近战,他的行为描述如下:&/p&&ul&&li&向玩家方向移动&/li&&li&当与玩家间小于一定距离后对玩家发起冲刺,冲刺的目标是玩家角色当前位置两侧,该目标确定后不再随玩家角色移动而改变&/li&&li&冲刺到达目标位置后向玩家角色所在的方向发起一次攻击&/li&&li&攻击完成后向远离玩家的方向后退2秒钟,然后重复第1步&/li&&/ul&&h4&有限状态机模型&/h4&&p&“有限状态机”这个词同样源自计算机科学,如果你去谷歌搜索一下,你可能又会迷失在一大堆有的没的专业术语之中。以上面提到的恶魔行者的行为为例,我这里简单介绍一下有限状态机在游戏开发中的具体应用:&/p&&ul&&li&状态的定义。如果按照以上的描述,可以为恶魔行者建立4个状态——追踪、冲刺、攻击、撤退。&/li&&li&每种状态下的行为。例如“追踪”状态下,他的行为是想玩家角色所在的方向移动。&/li&&li&状态的切换。在“追踪”状态下,当与玩家角色的距离小于一定数值时,切换至“冲刺”状态。&/li&&li&状态切换时的行为。例如从“追踪”状态切换到“冲刺”状态时,是不是需要给角色播放一个大喝一声的音效呢?&/li&&/ul&&p&我们为他建立的状态机模型可以按下图来描述:&/p&&figure&&img src=&https://pic4.zhimg.com/dd8fbcf9_b.jpg& data-rawwidth=&651& data-rawheight=&472& class=&origin_image zh-lightbox-thumb& width=&651& data-original=&https://pic4.zhimg.com/dd8fbcf9_r.jpg&&&/figure&&p&在我看来,把这样的状态模型用图的形式描述出来的最大好处是方便你思考现有模型是否合理,状态切换有没有其他的可能性。例如,如果主角有瞬间移动的功能可以突然出现在恶魔行者的面前,那么他有没可能直接在追踪状态下发起攻击,跳过冲刺的过程呢?如果在追踪的时候,恶魔行者受到了攻击生命值很低,我们要不要设定他直接从追踪状态切换到撤退状态呢?这些都是在开始具体实现之前需要考虑的问题。但在这个教程中,我们将完全按照这张图中所描述的模型来实现。&/p&&h4&有限状态机在 GMS 中的实现&/h4&&p&有限状态机的实现方式非常灵活,最简单的一种就是将枚举类型与 if-else 语句配合使用。&/p&&p&首先在脚本 DevilCreate中声明枚举变量,并将初始状态设置为“追踪”:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&enum DevilState{
DEVIL_FOLLOW,
DEVIL_DASH,
DEVIL_ATTACK,
DEVIL_RETREAT
m_devilState = DevilState.DEVIL_FOLLOW;
&/code&&/pre&&/div&&p&然后就是整个AI行为控制的主循环:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_devilState == DevilState.DEVIL_FOLLOW){
DevilUpdateFollow();
else if(m_devilState == DevilState.DEVIL_DASH){
DevilUpdateDash();
else if(m_devilState == DevilState.DEVIL_ATTACK){
DevilUpdateAttack();
else if(m_devilState == DevilState.DEVIL_RETREAT){
DevilUpdateRetreat();
&/code&&/pre&&/div&&p&这些 DevilUpdateXXX函数,简单来说就是在哪个状态就干哪个状态该干的事,同时辅助状态的切换。&/p&&p&但值得注意的是,由于 GML 中并不支持一个脚本文件中定义多个函数,因此需要对每个 DevilUpdateXXX 函数定义一个同名脚本,即新建 DevilUpdateFollow、DevilUpdateDash、DevilUpdateAtttack 和 DevilUpdateRetreat 四个新的脚本。&/p&&p&在明确了代码的结构和功能后,剩下的工作就是具体的编码实现了,这可能反倒是游戏开发中较为容易的部分。&/p&&h4&追踪状态&/h4&&p&首先从 DevilUpdateFollow 开始,这部分其实就是在上一次的教程里恶魔行者的行动代码,只需要从 DevilStep 搬运至 DevilUpdateFollow 中即可,但需要在这里加入状态切换的条件。&/p&&p&DevilCreate脚本中新增变量有:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//用于定义从追踪状态切换至冲刺状态的距离范围
m_dashDistance = 200;
//用于定义冲刺至玩家角色两侧的距离,这个距离应当与恶魔行者的攻击动画匹配。若玩家角色保持静止不动,那么恶魔行者冲刺到这里时应当正好能够攻击到她
m_dashDelta = 40;
//冲刺的终点目标
m_dashTargetX = 0;
m_dashTargetY = 0;
DevilUpdateFollow脚本中状态切换的代码:
if (distance_to_point(player.x, player.y) & m_dashDistance){
m_devilState = DevilState.DEVIL_DASH;
if(x & player.x){//冲刺至玩家左侧
m_dashTargetX = player.x - m_dashD
else{//冲刺至玩家右侧
m_dashTargetX = player.x + m_dashD
m_dashTargetY = player.y;
&/code&&/pre&&/div&&p&这段代码应该也很好理解,就是当恶魔行者距玩家的距离小于设定数值时,切换至冲刺状态,并设置冲刺的终点。如果当前他的位置在玩家左侧,那么就向左侧冲刺,反之亦然。&br&写了这么多代码,来测试一下:&/p&&figure&&img src=&https://pic1.zhimg.com/82b76a6b43e40db8c6984_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic1.zhimg.com/82b76a6b43e40db8c6984_r.jpg&&&/figure&&p&恶魔行者在行至玩家一定距离处就停住了,停住我们就放心了,说明他进入了冲刺状态,而冲刺状态我们还什么都没有做。&/p&&h4&冲刺状态&/h4&&p&接下来我们来添加 DevilUpdateDash 脚本中的代码,它所做的事情与追踪状态类似,主要的差别是目标点换成了m_dashTargetX 和 m_dashTargetY所定义的坐标,但这次我们尝试用与之前追踪状态里不同的方式来实现:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var distance = distance_to_point(m_dashTargetX, m_dashTargetY);
var deltaX = (m_dashTargetX - phy_position_x)/distance * m_dashS
var deltaY = (m_dashTargetY - phy_position_y)/distance * m_dashS
if(distance & m_dashSpeed){
phy_position_x = m_dashTargetX;
phy_position_y = m_dashTargetY;
m_devilState = DevilState.DEVIL_ATTACK;
m_isAttacking =
sprite_index = spr_devil_
image_index = 0;
phy_position_x += deltaX;
phy_position_y += deltaY;
if(deltaX & 0){
image_xscale = -1;
else if(deltaX & 0){
image_xscale = 1;
&/code&&/pre&&/div&&p&在 DevilUpdateFollow 脚本中,x 轴方向与 y 轴方向的运动距离是独立计算的,这样的做法会导致当人物在沿斜 45 度角移动时,速度要比沿水平或垂直方向移动的速度要快,因为它是这两个方向移动的叠加。而目前在 DevilUpdateDash 脚本中,移动的距离是按照目标点与自身的连线方向计算的,因此能够保证各个方向同样的移动速度。&/p&&p&然后在达到冲刺目标后,设置为攻击状态,并播放攻击动画。好,那么在添加了冲刺的代码后再次进行测试:&/p&&figure&&img src=&https://pic1.zhimg.com/c50e0f5dc8abbed_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic1.zhimg.com/c50e0f5dc8abbed_r.jpg&&&/figure&&p&很好,现在恶魔行者开始疯狂的输出了!&/p&&h4&攻击状态与撤退状态&/h4&&p&在从冲刺状态切换至攻击状态时,实际已经开始播放了攻击动画。所以在这个状态里所要做的事情仅仅是等待攻击动画播放完毕时,然后切换至下一个状态——撤退,并取消攻击动画和重置撤退时间。在 DevilUpdateAttack 脚本中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_isAttacking == false)
m_devilState = DevilState.DEVIL_RETREAT;
sprite_index = spr_devil_
m_retreatCurrentTime = 0;
&/code&&/pre&&/div&&p&在撤退的脚本 DevilUpdateRetreat 里,所做的事情和在追踪与冲刺里的相反,恶魔行者向远离玩家的方向前进,我们在DevilCreate中增加两个变量:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_retreatCurrentTime = 0;
m_retreatTime = 2;
&/code&&/pre&&/div&&p&用于跟踪撤退的时间,具体实现如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_retreatCurrentTime & m_retreatTime){
var player = instance_find(obj_ysera, 0);
var distance = distance_to_point(player.x, player.y);
if(distance & 0){
var deltaX = (phy_position_x - player.x)/distance * m_retreatS
var deltaY = (phy_position_y - player.y)/distance * m_retreatS
phy_position_x += deltaX;
phy_position_y += deltaY;
m_retreatCurrentTime += 1/30.0;
m_devilState = DevilState.DEVIL_FOLLOW;
&/code&&/pre&&/div&&p&运行测试一下吧。是不是看起来差不多像样了:)&/p&&p&但实际上现在恶魔行者的攻击还只是一个动画而已,并没有实际的碰撞检测和减少主角生命值的功能。&/p&&h4&恶魔行者的攻击实现&/h4&&p&想要让他的攻击造成伤害,其实现原理与伊瑟拉发出的魔法球实际上相当近似,我们可以想象成在恶魔行者挥出那一刀时发出了一阵剑气,所有与剑气碰撞的目标都会受到伤害。唯一所不同的是魔法球是可以持续飞行的,而剑气在出现后立刻消失。&/p&&p&因此在具体编码实现上,两者也十分类似。对于伊瑟拉,我们有一个 Object 是 obj_ysera_magic_bullet,同样我们也为恶魔行者建立一个用于进行碰撞检测造成伤害的 Object,叫做 obj_devil_attack_area。但记住这个剑气其实只是我所做的比喻,实际上你并不想让玩家看到它,不过为了调试你可以在开发的初期给这个形状一个半透明的颜色用来观察它是否出现在了你想要的位置:&/p&&figure&&img src=&https://pic3.zhimg.com/83fc65f7bbdc89e5e39dd8_b.jpg& data-rawwidth=&83& data-rawheight=&83& class=&content_image& width=&83&&&/figure&&p&比如这个就是我初始设置的形状颜色,当功能全部调试完成后,只要简单的把这个图形的不透明度(Opacity)设置成0就好了。&/p&&p&这个 Object 的具体实现首先需要设定碰撞形状,其次是需要两个与之关联的脚本,一个用于在创建时利用 GMS 的 alarm 系统设定一个闹钟,另一个在闹钟到期时删除它:&/p&&p&脚本 DevilAttackAreaCreate:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&alarm[0] = 10;
&/code&&/pre&&/div&&p&这里为了调试查看碰撞形状,暂把闹钟时间设定为 10 帧以后。由于 GMS 默认游戏是 30 帧,因此这个时间是三分之一秒钟。在10帧过后,由于我们设定的是闹钟 0(alarm[0]),因此 Alarm 0 事件对应的行为会被调用,添加该事件是在事件窗口的以下选项:&/p&&figure&&img src=&https://pic2.zhimg.com/a73584bd3ac_b.jpg& data-rawwidth=&255& data-rawheight=&292& class=&content_image& width=&255&&&/figure&&p&在这个事件对应的行为中我们编写代码调用脚本 DevilAttackAreaAlarm,该脚本内容为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&instance_destroy();
&/code&&/pre&&/div&&p&最后我们把DevilCreate脚本中的这一行:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&obj_character_bullet =
&/code&&/pre&&/div&&p&改为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&obj_character_bullet = obj_devil_attack_
&/code&&/pre&&/div&&p&这样,每次恶魔行者在做完攻击动作后,都会生成一个obj_devil_attack_area作为碰撞检测的物体,并在10帧后去除。最后再加上碰撞形状在人物两侧进行攻击时的偏移量(具体实现可参考源代码及教程六中的相关部分),测试如下:&/p&&figure&&img src=&https://pic3.zhimg.com/a92a08bfda67d91d79e582a02cad2412_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic3.zhimg.com/a92a08bfda67d91d79e582a02cad2412_r.jpg&&&/figure&&p&最后,如果你还记的我们在上次教程中为每个人物添加的全身碰撞盒,我们现在需要再为它加上与obj_devil_attack_area的碰撞,并设定一些碰撞条件:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if((m_attachedParent.object_index == obj_ysera
&& other.object_index == obj_devil_attack_area)
|| (m_attachedParent.object_index == obj_devil
&& other.object_index == obj_ysera_magic_bullet))
with(m_attachedParent)
CharacterOnDamage();
&/code&&/pre&&/div&&p&这里的含义是让恶魔行者与伊瑟拉的魔法球进行碰撞,以及伊瑟拉与恶魔行者的攻击判定碰撞。这里在以后添加更多种类的敌人或者己方队友时可扩展为将子弹类物品和人物分为“敌方”和“我方”两类,然后令不属同一方的子弹能与人物发生碰撞。&/p&&p&在这个改动后,伊瑟拉和恶魔行者之间相互伤害的流程就完整了:&/p&&figure&&img src=&https://pic3.zhimg.com/28821eddd8e5119b07fcf19a_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic3.zhimg.com/28821eddd8e5119b07fcf19a_r.jpg&&&/figure&&h4&上期教程的错误更正&/h4&&p&感谢细心的网友小囧()在上期教程发现的一处错误,魔法球飞行尾迹的方向问题。问题产生的原因是当魔法球具有物理属性后,不再能通过设置物体的 image_angle 属性来控制它的旋转方向,而是应该用 phy_rotation 来控制。这一点与 phy_position_x 和 phy_position_y类似,具有物理属性的物体,你同样无法直接设置它们的x和y坐标。&/p&&p&另外值得注意的一点是 phy_rotation 与 image_angle的旋转方向是相反的,一个是顺时针,一个是逆时针。&/p&&h4&结束语&/h4&&p&这个教程的初衷是在参与《冰杖秘闻》制作的过程中萌发的,我惊讶地发现 Game Maker:Studio 这款已经拥有了十多年历史的游戏引擎竟然可以如此完美地达到易用性与灵活性间的平衡。所以想要把制作过程中的一些经验心得整理出来做成一个系列教程,能够让有心制作游戏但对于编程又不那么有信心的玩家有多一个选择。此外,我也坦承希望通过这篇教材宣传一下我们正在开发中的独立游戏《冰杖秘闻》。&/p&&br&&p&因此,我最初的想法是希望这个教程是一个让从来没有接触过 GMS 的新手都能跟着做游戏的教程,最开始的规划是至少做 10 期。但随着教程项目的进展,内容丰富度与日俱增,我感觉教程的难度在飞速提升。不仅仅超过了新手的接受范畴,对我来说写作难度也逐步提高。随着《冰杖秘闻》开发工作日益繁重,这个教材系列从早期能够把每个遇到的概念都讲透彻,到后期很多地方只能遗憾地简单带过,以致最近两期自我感觉并不满意,风格有些近乎流水账了。为了保证教材的质量,加之我也需要将精力重心转移到《冰杖秘闻》的开发工作之中,因此,这篇教材可能是本系列正篇的最后一节了。&/p&&p&不过,这个系列并不会就此宣告终结,由于对这个教程我还非常依依不舍,在与 indienova 沟通协商后,我们计划在正篇内容外再追加特别篇的教材 —— 下一期我们会特别教授配合版本控制工具与 Git 的使用来辅助进行多人协作的游戏开发。比起某些具体的技术细节来说,是否使用版本控制工具绝对是玩一票和认真做的独立游戏开发者之间更大的差距。无论是个人开发还是多人合作,版本控制都是必不可少的。&/p&&p&正如前文提到的一些原因,特别篇部分的 GMS 教材可能不会完全都由我来撰写,想参与本系列教材撰写工作的开发者也可以站内给我们私信。&/p&&p&所以呢~ 老时间,下周再会!&/p&
引言indienova 会员为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程。由于他目前正在参与的《冰杖秘闻》开发工作日益繁重,本次教材将是正篇教材的最后一期,之后会以特别篇的形式进行小主题更新,大家对这个系列有什么建议也…
&figure&&img src=&https://pic3.zhimg.com/fcde4f93332bedf9a0b621a62f3aa1b4_b.jpg& data-rawwidth=&800& data-rawheight=&450& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/fcde4f93332bedf9a0b621a62f3aa1b4_r.jpg&&&/figure&&h4&编者按&/h4&&p&indienova 会员&a href=&http://link.zhihu.com/?target=http%3A//indienova.com/u/%59D%5E9%59C%59A%5E5%5BB%583%2525B3& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&青铜的幻想&/a&为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程。本期教程将在游戏中添加一名敌人,并为主角的攻击动作加入逻辑。&/p&&h4&教程目标&/h4&&p&在上期教程中,我们向游戏添加了《冰杖秘闻》女主角伊瑟拉的魔法攻击和特殊技能。但就目前而言,仅有动作和效果,还只是美术表现上的完成而已。想要完成它的功能,我们必须还得有一个敌人来配合承受伤害,因此在本次教程中,我们会首先来为游戏加入一名蠢蠢的敌人——恶魔行者,接着真正意义上实现攻击的逻辑。&/p&&h4&准备工作&/h4&&p&照例,本系列教程的项目/代码及原始美术素材全部位于&a href=&http://link.zhihu.com/?target=https%3A//github.com/akane2002/GMS_TUT& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&该 GitHub 项目库&/a&。&/p&&p&本次教程的内容基于目录 GMS_TUT_05 下的内容开始,完成后的项目文件放在目录 GMS_TUT_06。此次教程所需的美术资源可以在&a href=&http://link.zhihu.com/?target=https%3A//github.com/akane2002/GMS_TUT/raw/master/GMS_TUT_06/Tutorial06_Assets.zip& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&下载。&/p&&p&如果对之前内容感到生疏,可以查看&a href=&http://link.zhihu.com/?target=http%3A//indienova.com/column/7& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里的专题内容&/a&做回顾和复习。&/p&&h4&导入美术资源&/h4&&p&这次教程里需要的美术资源分别是恶魔行者的站立、移动、攻击和死亡的动画:&/p&&figure&&img src=&https://pic4.zhimg.com/cba84f59b24aee_b.jpg& data-rawwidth=&622& data-rawheight=&125& class=&origin_image zh-lightbox-thumb& width=&622& data-original=&https://pic4.zhimg.com/cba84f59b24aee_r.jpg&&&/figure&&p&将上述4个动画分别导入GMS并保持与之前的规则一致,命名为:&/p&&br&&ul&&li&spr_devil_idle&/li&&li&spr_devil_walk&/li&&li&spr_devil_attack&/li&&li&spr_devil_die&/li&&/ul&&p&和主角主要的区别在于,他只有一个方向的行走和攻击动画(小角色嘛,没那么多预算)。为了使的资源结构清晰,我们为不同人物分别建立目录存放其相关资源:&/p&&figure&&img src=&https://pic4.zhimg.com/d27fa8c4b9cdfbefe1df9_b.jpg& data-rawwidth=&320& data-rawheight=&293& class=&content_image& width=&320&&&/figure&&p&注:在 Objects 或 Scripts 等资源分类中新建资源时我通常也会遵循类似的原则,尽量将相关资源单独建立文件夹存放管理。这只是我为了能够方便的浏览资源内容的一个习惯,并非必要的步骤,因此在今后的教程中为了节省篇幅会略过这样的细节。&/p&&h4&建立Object&/h4&&p&在开始动手写代码之前,让我们先想一想这个需要新加入的角色和之前的主角伊瑟拉有什么区别和共同点,因为这决定了哪些功能可以共享,哪些是需要新加的功能。比如两者最主要的区别在于,恶魔行者的行动是由 AI 代码来控制的,而伊瑟拉的行动是由玩家按键来控制的。但他们也有很多相似的地方,例如在站立、行走、攻击时需要播放对应的动画,又或者是在攻击的时候不能移动这样的逻辑。&/p&&p&既然与之前的代码有相似的地方,我们不妨试试看直接从之前的 obj_ysera 复制一个一模一样的 Object 来试试看会如何:&/p&&figure&&img src=&https://pic3.zhimg.com/0ffa5cce9be1a530db23dde_b.jpg& data-rawwidth=&1026& data-rawheight=&654& class=&origin_image zh-lightbox-thumb& width=&1026& data-original=&https://pic3.zhimg.com/0ffa5cce9be1a530db23dde_r.jpg&&&/figure&&p&你们可以看到,我给这个 Object 取了新的名字 obj_devil,为他的 Sprite 选了新的站立动画 spr_devil_idle。那么如果此时我们将这个新的恶魔行者放入场景会怎么样呢?&/p&&figure&&img src=&https://pic1.zhimg.com/07ebdfd2197fdf3cff3527_b.jpg& data-rawwidth=&1212& data-rawheight=&812& class=&origin_image zh-lightbox-thumb& width=&1212& data-original=&https://pic1.zhimg.com/07ebdfd2197fdf3cff3527_r.jpg&&&/figure&&p&出现了两个伊瑟拉!并且她们可以同时被玩家所控制!&/p&&p&这是因为之前 YseraStep 脚本中类似这样的代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
else if(keyboard_check(ord('A')))
phy_position_x = phy_position_x - 4;
sprite_index = spr_ysera_walk_
image_xscale = 1;
m_playerDirection = PlayerDirection.LEFT;
&/code&&/pre&&/div&&p&我们直接在移动的时候把人物动画设置成了 spr_ysera_walk_side,所以不管是什么 Object 在使用这段代码,他移动时都会变成伊瑟拉的样子了。&/p&&p&但直接复制一个 Object 的好处是可以为我们省去一些相同的设置,例如物理属性的设置、父类的选取以及事件与脚本的关联等等,作为下一步修改的基础。&/p&&h4&复制并修改代码&/h4&&p&因为我们要对之前的代码进行修改,需要先复制一份代码出来,将 YseraCreate、YseraStep 和 YseraAnimationEnd 复制为 DevilCreate、DevilStep 和 DevilAnimationEnd。&/p&&figure&&img src=&https://pic1.zhimg.com/75b47cb9a8ae5ef5fc4ecb69f6a4d4d3_b.jpg& data-rawwidth=&1212& data-rawheight=&812& class=&origin_image zh-lightbox-thumb& width=&1212& data-original=&https://pic1.zhimg.com/75b47cb9a8ae5ef5fc4ecb69f6a4d4d3_r.jpg&&&/figure&&p&然后在 obj_devil 的事件界面中把 Create、Step 和 Animation End 这三个事件里面对之前脚本的调用换成对新复制出来的三个恶魔行者的脚本的调用。&/p&&p&接下来我们就可以在新的脚本中开始改动了。首先看 DevileCreate 脚本,里面没有什么和伊瑟拉相关的代码因此保留不变就好。&/p&&p&然后看 DevilStep 脚本,我们将要把伊瑟拉相关的代码替换为恶魔行者的数据。首先打开脚本编辑器界面,然后 Ctrl+f 打开搜索框,在其中填入 ysera,那么所有高亮显示的地方就是我们要改动的地方:&/p&&figure&&img src=&https://pic1.zhimg.com/9aec5ba7fb_b.jpg& data-rawwidth=&792& data-rawheight=&498& class=&origin_image zh-lightbox-thumb& width=&792& data-original=&https://pic1.zhimg.com/9aec5ba7fb_r.jpg&&&/figure&&p&我先总结一下,然后直接贴出最终修改后的代码:&/p&&ul&&li&将三个攻击动画 spr_ysera_attack_back、spr_ysera_attack_front 和 spr_ysera_attack_side 替换为spr_devil_attack。&/li&&li&将技能动画 spr_ysera_skill 替换成 noone。这个关键词 noone(记住不是 none,是 no one——“没有人”)等价与其它编程语言中的 null,就是表示没有值或者是空值的意思。&/li&&li&将伊瑟拉的三个行走动画替换为 spr_devil_walk。&/li&&li&将spr_ysera_idle替换为spr_devil_idle。&/li&&/ul&&p&进行以上改动后,最后还剩三处出现了 ysera 的地方分别是发出魔法球的代码和释放技能效果的代码,因为在恶魔行者的逻辑中暂时还没有相对应的地方,所以先不管放在这里。修改后的 DevilStep 脚本如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&image_speed = 0.25;
if(!m_isAttacking && !m_isInSkill){
if(keyboard_check(ord('J'))){
m_isAttacking =
switch(m_playerDirection){
case PlayerDirection.UP:
sprite_index = spr_devil_
case PlayerDirection.DOWN:
sprite_index = spr_devil_
case PlayerDirection.LEFT:
sprite_index = spr_devil_
case PlayerDirection.RIGHT:
sprite_index = spr_devil_
image_index = 0;
else if(keyboard_check(ord('K'))){
m_isInSkill =
sprite_index =
image_index = 0;
else if(keyboard_check(ord('A'))){
phy_position_x = phy_position_x - 4;
sprite_index = spr_devil_
image_xscale = 1;
m_playerDirection = PlayerDirection.LEFT;
else if(keyboard_check(ord('D'))){
phy_position_x = phy_position_x + 4;
sprite_index = spr_devil_
image_xscale = -1;
m_playerDirection = PlayerDirection.RIGHT;
else if(keyboard_check(ord('W'))){
phy_position_y = phy_position_y - 4;
sprite_index = spr_devil_
m_playerDirection = PlayerDirection.UP;
else if(keyboard_check(ord('S'))){
phy_position_y = phy_position_y + 4;
sprite_index = spr_devil_
m_playerDirection = PlayerDirection.DOWN;
sprite_index = spr_devil_
if(sprite_index == spr_devil_attack
|| sprite_index == spr_devil_attack
|| sprite_index == spr_devil_attack){
if(image_index & 2 && m_fired == false){
var magicBullet = instance_create(x, y, obj_ysera_magic_bullet);
var deltaX = 0;
var deltaY = 0;
switch(m_playerDirection){
case PlayerDirection.UP:
magicBullet.m_speedY = -10;
magicBullet.image_angle = 270;
deltaY = -89;
case PlayerDirection.DOWN:
magicBullet.m_speedY = 10;
magicBullet.image_angle = 90;
deltaY = 7;
case PlayerDirection.LEFT:
magicBullet.m_speedX = -10;
deltaX = -65;
deltaY = -33;
case PlayerDirection.RIGHT:
magicBullet.m_speedX = 10;
magicBullet.image_angle = 180;
deltaX = 65;
deltaY = -33;
magicBullet.x += deltaX;
magicBullet.y += deltaY;
if(sprite_index == spr_ysera_skill){
if(image_index & 2 && m_fired == false){
instance_create(x, y, obj_ysera_skill_effect);
m_fired = true
&/code&&/pre&&/div&&p&最后修改 DevilAnimationEnd 脚本,将其中的攻击动画替换为 spr_devil_attack,技能动画 spr_ysera_skill 不用管就好。&/p&&p&此时如果尝试运行的话,会发现一个编译错误提示我们枚举类型 PlayerDirection 重复定义了,因此我们需要删除掉 DevilCreate 脚本中重复定义 PlayerDirection 的部分。&/p&&p&注:其实这里更加完善的做法是抽取伊瑟拉和恶魔行者的脚本中相同的部分并建立一个两个共享的父类(例如叫做obj_character_base),将这些相同的脚本放入父类中执行。而上面提到的枚举类型 PlayerDirection 的定义也应该放入这个父类中。&/p&&p&接下来运行测试看看:&/p&&figure&&img src=&https://pic2.zhimg.com/57b2e91f6c79ad1416ada_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic2.zhimg.com/57b2e91f6c79ad1416ada_r.jpg&&&/figure&&p&这次测试验证了恶魔行者的站立、行走和攻击动画现在已经可以正确播放了,接下来需要改动的是 AI 控制部分。&/p&人物控制&p&在控制主角的时候,人物上下左右的移动和攻击是通过检测按键的状态来完成的。而对于敌人,则是由 AI 代码来完成。出于篇幅的限制,我们没法在这期教程里实现复杂的AI逻辑,但我们可以通过一个简单的行为来演示 AI 控制人物。我们将要实现的逻辑是让恶魔行者始终向着玩家所在的位置移动。&/p&&p&我们把这部分的代码添加到 DevilStep 脚本,放在之前的这个条件语句中,把这个条件括号中之前的代码删除掉:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(!m_isAttacking && !m_isInSkill){
//将之前的控制代码删除
&/code&&/pre&&/div&&p&然后加入AI控制的逻辑,首先是获取伊瑟拉所在的位置。这里需要用到的函数是 instance_find(obj, n),它的作用是找到第 n 个 obj 类型的实例(instance),所以我们需要调用:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
var player = instance_find(obj_ysera, 0);
&/code&&/pre&&/div&&p&来取得场景中的伊瑟拉,这里我们做了一个假设是场景中有且仅有一个伊瑟拉存在。随后计算出从当前的这个恶魔行者到伊瑟拉的坐标差值:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
var deltaX = player.x -
var deltaY = player.y -
&/code&&/pre&&/div&&p&接下来根据这个差值来移动恶魔行者的坐标:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
var mySpeed = 2;
if(deltaX & mySpeed){
phy_position_x += myS
else if(deltaX & -mySpeed){
phy_position_x -= myS
phy_position_x += deltaX;
if(deltaY & mySpeed){
phy_position_y += myS
else if(deltaY & -mySpeed){
phy_position_y -= myS
phy_position_y += deltaY;
&/code&&/pre&&/div&&p&最后再根据移动方向设置人物的朝向,以及设定动画:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
if(deltaX & 0){
image_xscale = -1;
else if(deltaX & 0){
image_xscale = 1;
sprite_index = spr_devil_
&/code&&/pre&&/div&&p&注意这里总是把人物动画设定成 spr_devil_walk,这是因为他没有不同方向的行走动画。&/p&&p&这样,我们就实现了一个基本的跟随行为。&/p&&h4&魔法球的碰撞检测&/h4&&p&在加入了这个基本的敌人之后,就可以回到我们最初想要实现的攻击了。伊瑟拉的攻击方式有两种,第一个是发出魔法球的普通攻击,第二个是范围技能攻击,但实现方式基本相同,这个教程里以魔法球为例。首先需要让魔法球能够与敌人发生碰撞检测,所以要设置 obj_ysera_magic_bullet 的物理属性,将魔法球的碰撞形状设置成圆形,如下:&/p&&figure&&img src=&https://pic4.zhimg.com/f8dc38c86f7a6de46e5c17_b.jpg& data-rawwidth=&393& data-rawheight=&397& class=&content_image& width=&393&&&/figure&&p&在为魔法球加入了物理属性之后,我们就不能直接修改它的坐标 x 和 y 了,而是设置 phy_position_x 和phy_position_y,因此要将 MagicBulletStep 脚本的内容修改为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&phy_position_x = phy_position_x + m_speedX;
phy_position_y = phy_position_y + m_speedY;
&/code&&/pre&&/div&&p&接下来在事件窗口中为魔法球加入和其他物品的碰撞并添加碰撞的处理脚本 MagicBulletCollision:&/p&&figure&&img src=&https://pic3.zhimg.com/e0aeb08fd1b0767_b.jpg& data-rawwidth=&902& data-rawheight=&508& class=&origin_image zh-lightbox-thumb& width=&902& data-original=&https://pic3.zhimg.com/e0aeb08fd1b0767_r.jpg&&&/figure&&p&脚本MagicBulletCollision的内容是:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(other.object_index != obj_ysera){
instance_destroy();
&/code&&/pre&&/div&&p&这里只是简单的在魔法球与其他物品发生碰撞以后,将魔法球自身从场景中删除掉。这个括号里的条件是碰撞另一方(other)不可以是游戏的主角,因为如果没有这个条件的话,在魔法球发出的一瞬间就会和主角自身发生碰撞而被删除了。之后测试如下:&/p&&figure&&img src=&https://pic1.zhimg.com/1f3a2fe227c6b5b3dc7c95e53879d35a_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic1.zhimg.com/1f3a2fe227c6b5b3dc7c95e53879d35a_r.jpg&&&/figure&&p&我们看到魔法球正确的与场景中的障碍物发生了碰撞后消失掉。但是却穿过了恶魔行者的身体,而没有击中他!原因是我们将所有人物的碰撞形状设置成了他们脚下的一小块矩形,这么做的理由又是为了在人物行走时能够正确的与场景中的物品碰撞。但开始做攻击判定时,却需要全身的碰撞才能实现我们想要的功能!&/p&&p&这个问题曾经困扰了我一段时间,最初我尝试在官网上找到能够为一个人物设置两个碰撞形状的方法,但结果是GMS并不支持这样。后来在一个论坛里看到了一个实现思路,顺着这个思路才完美实现了使用脚下的小块矩形做为行走的碰撞检测,而用等身大小的矩形做攻击的碰撞检测。这个思路的核心就是用另一个紧紧跟随人物移动的矩形物体来做碰撞检测,然后将碰撞结果通知人物。&/p&&h4&添加全身碰撞&/h4&&p&恶魔行者的人物大小约为宽40、高80的矩形,我们按照这个大小建立一个矩形的 Sprite,命名为 spr_hitbox(hitbox——碰撞盒):&/p&&figure&&img src=&https://pic2.zhimg.com/ecd3d96acfa02e_b.jpg& data-rawwidth=&1286& data-rawheight=&864& class=&origin_image zh-lightbox-thumb& width=&1286& data-original=&https://pic2.zhimg.com/ecd3d96acfa02e_r.jpg&&&/figure&&p&在此 Sprite 的基础上再建立一个名为 obj_hitbox 的物体,并设置物理属性。对于恶魔行者来说,他的 hitbox 可以放在 DevilCreate 脚本中生成,代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_attachedHitbox = instance_create(x, y, obj_hitbox);
m_attachedHitbox.m_attachedParent =
&/code&&/pre&&/div&&p&其作用是首先为恶魔行者创建一个碰撞盒(obj_hitbox)并存放于变量 m_attachedHitbox,其次是为这个碰撞盒的实例添加一个变量 m_attachedParent 用于引用恶魔行者自身,因为在碰撞盒的 Step 更新函数中,需要知道恶魔行者的位置用以更新自己的位置。&/p&&p&还需要为 obj_hitbox 创建两个脚本 HitBoxCreate 和 HitBoxStep,分别对应 Create 事件和 Step 事件,它们的内容如下:&/p&&p&HitBoxCreate脚本:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_attachedParent =//声明变量
phy_fixed_rotation =//固定旋转
&/code&&/pre&&/div&&p&HitBoxStep脚本:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//碰撞盒的坐标总是跟随恶魔行者的坐标
if(instance_exists(m_attachedParent)){
phy_position_x = m_attachedParent.phy_position_x;
phy_position_y = m_attachedParent.phy_position_y;
&/code&&/pre&&/div&&p&通过运行游戏实际测试来调整碰撞块的原点坐标,使得在游戏中人物和它能够尽量的重合,那么这一步就接近完成了:&/p&&figure&&img src=&https://pic1.zhimg.com/b5cd6013d6dff19f1eddf_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic1.zhimg.com/b5cd6013d6dff19f1eddf_r.jpg&&&/figure&&p&这里可以看到碰撞盒已经可以跟随恶魔行者移动。最后,只要再在 obj_ysera_magic_bullet 的事件中加上与碰撞盒的碰撞检测,这样魔法球就可以和人物的碰撞盒产生碰撞并消失了:&/p&&h4&碰撞处理函数&/h4&&p&由于是恶魔行者身上的碰撞盒与魔法球发生了碰撞,因此还需要需要将碰撞事件通知到恶魔行者。整个消息的传递通过这样的方式进行,首先也为碰撞盒添加与魔法球的碰撞事件,并建立一个新的脚本 HitBoxOnDamage 用于处理这个事件。然后再为恶魔行者也建立一个 DevilOnDamage 脚本。暂时我们只在 DevilOnDamage 脚本里面加上一个调试语句,用于在运行是检查是不是正确调用到了这个函数:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&show_debug_message(&Devil on damage.&);
&/code&&/pre&&/div&&p&show_debug_message 函数的作用是在调试窗口中输出一条信息。&br&而碰撞盒的 HitBoxOnDamage 脚本的内容是这样的:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(instance_exists(m_attachedParent)){
with(m_attachedParent){
DevilOnDamage();
&/code&&/pre&&/div&&p&这里的 m_attachedParent 变量是在碰撞盒被创建时就把恶魔行者对象赋值给了它,这里我们先做一个安全检查,验证这个恶魔行者是否还存在,如果存在的话,就调用他的 DevilOnDamage 函数。&br&前面的教程我们提到过,GML 语言并不是一种面向对象的语言,但提供了一些机制来模拟面向对象。在这个脚本中的关键词“with”就是起到这个作用,通常对于 java、c++ 或者 c# 这样的语言来说,想要调用一个对象的成员函数,通常的格式是 object.DoSomthing(),而对于 GMS 来说它的格式是:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&with(object){
DoSomething();
&/code&&/pre&&/div&&p&这时,采用调试模式运行游戏(Run game in debug mode)进行测试的话,会在魔法球打到恶魔行者身上的碰撞盒时,看到调试信息。&/p&&h4&生命值与死亡&/h4&&p&现在我们可以为恶魔行者加入用于表示生命值和是否死亡的变量了,在 DevilCreate 中添加:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_hp = 2;
m_isDead =
&/code&&/pre&&/div&&p&然后在之前添加的DevilOnDamage中删去调试信息,加入代码使得每次被魔法球打中时生命值减少1:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_hp = m_hp - 1;
&/code&&/pre&&/div&&p&最后,在 DevilStep 中检查他的生命值是否等于 0,如果等于 0 则播放死亡动画,并将标记该人物是否死亡的变量m_isDead 设置为 true。下面这段代码是加入到 DevilStep 脚本的最开始:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_isDead)
if(m_hp == 0){
m_isDead =
sprite_index = spr_devil_
&/code&&/pre&&/div&&p&值得注意的是这两个 return 返回语句,这是因为当人物处于死亡状态后,我们就不希望继续进行下面的人物移动代码了。&/p&&p&另外在 DevilAnimationEnd 脚本中加入:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_isDead){
image_speed = 0;
image_index = image_number - 1;
&/code&&/pre&&/div&&p&因为在 GMS 中是默认所有的动画都是无限循环播放的,这段代码让人物的死亡动画播放完一遍后,就停止动画的播放(通过把动画播放速度image_speed设置为0),同时让人物停留在死亡动画的最后一帧。&br&最后我们再把 spr_hitbox 的透明度设置为0:&/p&&figure&&img src=&https://pic4.zhimg.com/cb396efbce97cb36cf47b7ff_b.jpg& data-rawwidth=&1236& data-rawheight=&830& class=&origin_image zh-lightbox-thumb& width=&1236& data-original=&https://pic4.zhimg.com/cb396efbce97cb36cf47b7ff_r.jpg&&&/figure&&p&就可以测试最终的结果了:&/p&&figure&&img src=&https://pic1.zhimg.com/00b325fdad346a54d53cd8_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic1.zhimg.com/00b325fdad346a54d53cd8_r.jpg&&&/figure&&p&好,现在主角的攻击就完成了,我们下次再来完善恶魔行者的攻击并加入更加有趣的 AI!&/p&
编者按indienova 会员为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程。本期教程将在游戏中添加一名敌人,并为主角的攻击动作加入逻辑。教程目标在上期教程中,我们向游戏添加了《冰杖秘闻》女主角伊瑟拉的魔法攻击和特殊技能…
&figure&&img src=&https://pic2.zhimg.com/fdaac7faccab992cd93a9d4b_b.jpg& data-rawwidth=&800& data-rawheight=&450& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic2.zhimg.com/fdaac7faccab992cd93a9d4b_r.jpg&&&/figure&&h4&编者按&/h4&&p&indienova 会员&a href=&https://link.zhihu.com/?target=http%3A//indienova.com/u/%59D%5E9%59C%59A%5E5%5BB%583%2525B3& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&青铜的幻想&/a&为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程。&/p&&p&本栏目已经有了专门的专题页面,请参看&a href=&https://link.zhihu.com/?target=http%3A//indienova.com/column/7& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&/a&。根据教程四的结尾处的小调查,吃瓜群众们表示对人物的攻击技能和敌人 AI 十分有兴趣,因此接下来的几次教程将围绕这个相关方向展开。&/p&&p&查看部分动图请点击图片,由于知乎专栏不支持代码高亮,推荐&a href=&https://link.zhihu.com/?target=http%3A//indienova.com/indie-game-development/gamemaker-studio-6-characters-and-skills/%23iah-0& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&移步原文&/a&阅读。&/p&&p&欢迎读者朋友在文章后留言,以便作者能够继续针对性地安排接下来的教程内容。&/p&&h4&教程目标&/h4&&p&添加伊瑟拉的普通攻击和技能。&/p&&h4&准备工作&/h4&&p&这个系列教程的项目/代码及原始美术素材全部都在 &a href=&https://link.zhihu.com/?target=https%3A//github.com/akane2002/GMS_TUT& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GitHub 项目库&/a&。这个教程的内容基于目录 GMS_TUT_04 下的内容开始,完成后的项目文件放在目录 GMS_TUT_05。此次教程所需的美术资源可以在这里下载。&/p&&h4&导入美术资源&/h4&&p&首先需要导入相关的人物动画,讲到这里我很开心。因为呢,我对我们这个游戏《冰杖秘闻》的像素美术是十分欣赏的,也很希望能够分享更多的人物动画出来(更多视频及原画请移步官网欣赏)。这里是本次教程将要用到的两套动画——伊瑟拉的普通攻击(侧面、正面和背面)以及技能攻击:&/p&&br&&figure&&img src=&https://pic1.zhimg.com/3e2aa9c0a7e_b.jpg& data-rawwidth=&670& data-rawheight=&128& class=&origin_image zh-lightbox-thumb& width=&670& data-original=&https://pic1.zhimg.com/3e2aa9c0a7e_r.jpg&&&/figure&&p&在策划上,普通攻击是用手里的法杖发出一个魔法球,而技能攻击是一个伤害较低的范围减速,于是我们还需要一个飞行的魔法球以及范围减速效果的动画:&/p&&figure&&img src=&https://pic2.zhimg.com/fba0d1ecd7db7f8758133ffc4ff7560c_b.jpg& data-rawwidth=&513& data-rawheight=&563& class=&origin_image zh-lightbox-thumb& width=&513& data-original=&https://pic2.zhimg.com/fba0d1ecd7db7f8758133ffc4ff7560c_r.jpg&&&/figure&&br&&p&以上就是这次教程里用到的所有美术资源的动图,现在需要把它们导入 GMS 中去。&/p&&p&具体的导入操作在教程二中有详细的描述,特别要记得正确的把 Sprite 的原点设置在人物的脚下。下面是我所采用的 Sprite 命名,我建议的是所有的 Sprite 资源名称都以 spr_ 开头,同时尽量让名称能够表示该资源的用途或含义:&/p&&ul&&li&普通攻击侧面: spr_ysera_attack_side&/li&&li&普通攻击正面: spr_ysera_attack_front&/li&&li&普通攻击背面: spr_ysera_attack_back&/li&&li&特殊技能: spr_ysera_skill&/li&&li&魔法球: spr_ysera_magic_bullet&/li&&li&特殊技能效果: spr_ysera_skill_effect&/li&&/ul&&p&将这些资源都放入 Sprites 分类里之前建立的 Character 目录中(红框中的是这次新导入的 Sprite 资源):&/p&温馨小提示&p&如果你觉得在左侧的资源导航栏中,所显示的资源图片太小不容易看清,你可以通过以下设置来显示资源的大图标。在 File(文件)菜单的 Preferences(选项)中,勾选 Big Resource Tree Icons(资源树中显示大图标):&/p&&figure&&img src=&https://pic2.zhimg.com/de6e0d9b143aa88bc817ccee_b.jpg& data-rawwidth=&320& data-rawheight=&231& class=&content_image& width=&320&&&/figure&&p&然后重启 GMS,图标就会放大一些,更容易看清对应的人物动作一点:&/p&&figure&&img src=&https://pic2.zhimg.com/719a0ee26e9faab56f243d6e836c8ca1_b.jpg& data-rawwidth=&320& data-rawheight=&359& class=&content_image& width=&320&&&/figure&&p&但这样也使得每屏能显示的资源数量变少了,所以路怎么走,你们自己选。&/p&&h4&建立人物控制的脚本&/h4&&p&在开始敲代码之前先回顾一下之前的人物控制相关的脚本,我们打开obj_ysera,可以看到当前她响应三个事件Create、Step以及与obj_scene_base的碰撞:&/p&&figure&&img src=&https://pic3.zhimg.com/b899d3a27527ebd55c5b5a0ea7a49406_b.jpg& data-rawwidth=&879& data-rawheight=&433& class=&origin_image zh-lightbox-thumb& width=&879& data-original=&https://pic3.zhimg.com/b899d3a27527ebd55c5b5a0ea7a49406_r.jpg&&&/figure&&p&其中 Create 事件对应的脚本是:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&phy_fixed_rotation = 1; //为了不让人物发生旋转
&/code&&/pre&&/div&&p&Step 事件对应的脚本是:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&event_inherited();
YseraStep();
&/code&&/pre&&/div&&p&而碰撞事件只是为了注册人物和物体间的碰撞,暂时并没有脚本需要运行。&/p&&p&关于这几个脚本我要做的第一件事是在资源导航栏的 Scripts 分类中建立一个新的名为 YseraCreate 的脚本文件,并将phy_fixed_rotation = 1 这句代码从 Create 事件的脚本移入这个脚本,然后将 Create 事件的脚本修改至如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&event_inherited();
YseraCreate();
&/code&&/pre&&/div&&p&这个改动的目的如下:&/p&&ol&&li&obj_ysera 作为“场景中的动态物体”(obj_scene_dynamic)的子类,理应执行任何在父类的 Create 事件中执行的脚本(通过调用 event_inherited);&/li&&li&将脚本内容移至 YseraCreate 脚本文件中,这样可以避免一部分代码放在事件窗口中,一部分代码放在脚本文件里的混乱。在这样调整之后,我们就可以很清楚的知道,所有和伊瑟拉这个人物相关的脚本是 YseraCreate 和 YseraStep 这两个脚本文件,下面的改动也都在这两个脚本文件中进行。&/li&&/ol&&h4&定义人物的朝向&/h4&&p&再回顾一下 YseraCreate 和 YseraStep 这两个脚本的内容,是这样的。&/p&&p&YseraCreate:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&phy_fixed_rotation = 1;
&/code&&/pre&&/div&&p&YseraStep:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&image_speed = 0.25;
if(keyboard_check(ord('A'))){
phy_position_x = phy_position_x - 4;
sprite_index = spr_ysera_walk_
image_xscale = 1;
else if(keyboard_check(ord('D'))){
phy_position_x = phy_position_x + 4;
sprite_index = spr_ysera_walk_
image_xscale = -1;
else if(keyboard_check(ord('W'))){
phy_position_y = phy_position_y - 4;
sprite_index = spr_ysera_walk_
else if(keyboard_check(ord('S'))){
phy_position_y = phy_position_y + 4;
sprite_index = spr_ysera_walk_
sprite_index = spr_ysera_
&/code&&/pre&&/div&&p&接下来,首先我想要在YseraCreate脚本中加一个枚举(enum)来定义人物当前的朝向,因为在目前的代码中,人物移动时 phy_position_x 和 phy_position_y 的加减、人物在 x 轴的缩放 image_xscale、移动时需要采用的动画这几个因素都是和人物的朝向有关的,而且可以想象将要添加的攻击动画、魔法球的生成位置、魔法球的飞行方向还与朝向有关。&/p&&p&因为我们只有四个方向的动画,所以朝向定义为四个——上、下、左、右,然后在枚举定义后添加用于记录人物朝向的变量 m_playerDirection:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&enum PlayerDirection{
m_playerDirection = PlayerDirection.DOWN;
&/code&&/pre&&/div&&p&(注释:为便于查看,此次教程中新增加的代码用浅橙色高亮显示)&/p&&p&默认的朝向设置成下方,因为我们设置的人物的初始图像是朝下的。然后我们在 YseraStep 脚本中根据玩家按键,将朝向设置成对应的值,例如玩家按A键时方向应设置为向左:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(keyboard_check(ord('A')))
phy_position_x = phy_position_x - 4;
sprite_index = spr_ysera_walk_
image_xscale = 1;
m_playerDirection = PlayerDirection.LEFT;
&/code&&/pre&&/div&&h4&播放攻击与技能动画&/h4&&p&在记录了人物的朝向以后,我们就可以正确的选择当前的攻击动画了,所以接下来加入的功能是在玩家按键时播放攻击和技能的动画。&/p&&p&我不记得有没有在之前的教程里说过这个小提示,就是假如你当前还不清楚一个功能要怎么下手实现,你可以先尝试做些简单的改动让你的游戏看起来有这个功能。在尝试的过程中你就会更清楚下一步要怎样做。例如现在我们要做的就是这样,如果还不清楚人物的攻击过程一共包含哪些要做的步骤,那至少可以先播放一个人物动画。&/p&&p&还可以举一个例子,比如你在做游戏的背包系统,如果你觉得不知从何开始,那么不如先做一个最简单的功能就是当玩家按下打开背包的按键时,显示一个背包的 UI 出来。接下来再看这个 UI 需要哪些玩家数据以及如何让这个UI正确关联到玩家数据。至少这种方法对我个人在开发过程中一直有很好的效果。&/p&&p&接下来回到我们教程的项目,我们选择 J和 K 键分别作为攻击和技能的按键,那么在 YseraStep 脚本中响应的代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(keyboard_check(ord('J')))
switch(m_playerDirection)
case PlayerDirection.UP:
sprite_index = spr_ysera_attack_
case PlayerDirection.DOWN:
sprite_index = spr_ysera_attack_
case PlayerDirection.LEFT:
sprite_index = spr_ysera_attack_
case PlayerDirection.RIGHT:
sprite_index = spr_ysera_attack_
image_index = 0;
}else if(keyboard_check(ord('K')))
sprite_index = spr_ysera_
image_index = 0;
&/code&&/pre&&/div&&p&为不同的情况设置 sprite_index,即当前需要播放的动画。其中语句 image_index=0 的意思是每次攻击和技能的动画的从第一帧开始播放。&/p&&h4&增加人物状态变量&/h4&&p&但如果仅仅是把这段代码加进 YseraStep 脚本中,你会发现人物还是没法正确的播放攻击和技能动画,这是因为之前控制人物移动的代码中的存在一个 else 语句会在玩家没有任何按键的时候播放玩家的站立动画,这样会导致在按下 J 键后,玩家正要播放攻击动画时,这个站立动画会立刻取代攻击动画。&/p&&p&所以这里需要再次整理一下我们想要的逻辑。之前的行走控制代码的逻辑是,当玩家按下四个方向的行走按键时,移动角色并播放相应的行走动画,若玩家没有按下任何按键,则播放站立动画。在加入了攻击和技能动画后,这个逻辑被打破了,因为在播放攻击和技能动画的过程中,即使这时玩家没有再按键,也应当等到动画播放完毕才能回到站立动画。与此同时,在攻击和释放技能的过程中,人物也应该无法移动。(在攻击的过程中无法移动会影响操作手感,此处为了教程内容的安排简化处理)&/p&&p&根据以上的逻辑,需要再为人物增加两个新的变量用来表示人物是否正在攻击过程中(m_isAttacking)和人物是否正在释放技能过程中(m_isInSkill)。在 YseraCreate 脚本中初始化这两个变量:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_isAttacking =
m_isInSkill =
&/code&&/pre&&/div&&p&同时在YseraStep脚本处理攻击和技能按键的代码处设置它们的值为 true:&/p&&p&&br&if(keyboard_check(ord('J')))&br&{&br&&em&m_isAttacking =&/em&&br&…...&br&}&br&else if(keyboard_check(ord('K')))&br&{&br&&em&m_isInSkill =&/em&&br&…...&br&}&/p&&p&&br&那么如何将这两个变量的值在动画播放完毕后设置成 false呢?这里需要引入一个新的事件类型——动画结束(Animation end)。这里对事件与脚本的处理与之前一致,我们需要做的是建立一个名为 YseraAnimationEnd 的脚本,并与 Animation end 事件关联:&/p&&figure&&img src=&https://pic1.zhimg.com/5a481f4e8f8eaf0623d5bacef647648b_b.jpg& data-rawwidth=&1290& data-rawheight=&680& class=&origin_image zh-lightbox-thumb& width=&1290& data-original=&https://pic1.zhimg.com/5a481f4e8f8eaf0623d5bacef647648b_r.jpg&&&/figure&&p&然后在 YseraAnimationEnd 脚本中添加如下代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_isAttacking && sprite_index == spr_ysera_attack_side
|| sprite_index == spr_ysera_attack_front
|| sprite_index == spr_ysera_attack_back){
m_isAttacking =
}if(m_isInSkill && sprite_index == spr_ysera_skill){
m_isInSkill =
&/code&&/pre&&/div&&p&这里的含义是,如果角色处在攻击状态且攻击的动画结束了,那么取消角色的攻击状态,对释放技能这里同理。到此时为止,m_isAttacking(是否在攻击)和 m_isInSkill(是否在施法)这两个变量的值已经能够正确的反映角色状态了。&/p&&p&在具有了这两个变量以后,就可以用来改写攻击与技能动画的播放代码了,由于代码过长因此这里只写出简化的代码结构:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(m_isAttacking == false && m_isInSkill == false)
if(按下攻击键)
播放攻击动画
else if(按下技能键)
播放技能动画
else if(按下A键)
播放向左行走动画
else if(按下D键)
播放向右行走动画
else if(按下W键)
播放向上行走动画
else if(按下S键)
播放向下行走动画
播放站立动画
&/code&&/pre&&/div&&p&在完成这部分代码后,人物应该可以正常的播放攻击和技能动画了:&/p&&figure&&img src=&https://pic2.zhimg.com/ad5e887fba5eeae0fa64de83_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic2.zhimg.com/ad5e887fba5eeae0fa64de83_r.jpg&&&/figure&&br&&h4&生成魔法球与技能效果&/h4&&p&接下来要做的是配合动画,在合适的时间释放出魔法球和技能效果。首先为它们建立两个 Object:obj_ysera_magic_bullet 和 obj_ysera_skill_effect,并分别将 spr_ysera_magic_bullet 和spr_ysera_skill_effect 作为它们的 Sprite。&/p&&p&根据美术上的设定,释放魔法球和技能效果的出现都是在动画的第二帧,因此在 YseraStep 脚本的最后加上这段代码来生成它们:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(sprite_index == spr_ysera_attack_side
|| sprite_index == spr_ysera_attack_front
|| sprite_index == spr_ysera_attack_back){
if(image_index & 2 && m_fired == false){
instance_create(x, y, obj_ysera_magic_bullet);
}if(sprite_index == spr_ysera_skill){
if(image_index & 2 && m_fired == false){
instance_create(x, y, obj_ysera_skill_effect);
m_fired = true
&/code&&/pre&&/div&&p&这段代码的意思是,如果当前在播放攻击动画,那么在大于第二帧的时候,就在玩家的位置生成一个魔法球,对于技能动画同理。这里用到了一个新的函数 instance_create,它的作用是在指定的位置生成一个 Object。&/p&&p&其实当初我在实现这个功能的时候踩了一个坑,我最早的代码是这样写的:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(在播放攻击动画时){
if(image_index
生成魔法球
&/code&&/pre&&/div&&p&然后这段代码的结果是什么都没有出现。为什么呢?秘密在这里,因为 image_index 并不是一个整数,这意味着image_index==2 这个条件可能永远都不会实现。所以应该把条件改成 image_index & 2。但如果只是把这个条件改掉:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if(在播放攻击动画时){
if(image_index
生成魔法球
&/code&&/pre&&/div&&p&那结果会在第二帧以后产生许多个魔法球,因此还需要添加一个变量 m_fired 来标记这次攻击动画过程中是否已经生成过魔法球了,这样就变成了上面那段完整代码所表达的逻辑。&/p&&p&另外在代码实现上还需要在 YseraCreate 脚本里初始化变量:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
&/code&&/pre&&/div&&p&然后在每次按键开始播放攻击动画的时候将这个变量重置为 false:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
if(keyboard_check(ord('J')))
else if(keyboard_check(ord('K')))
&/code&&/pre&&/div&&p&好吧,敲完了这么多代码终于又可以运行测试一下了:&/p&&figure&&img src=&https://pic3.zhimg.com/685e88d8b982f94c25c2cc6a_b.jpg& data-rawwidth=&1040& data-rawheight=&806& class=&origin_image zh-lightbox-thumb& width=&1040& data-original=&https://pic3.zhimg.com/685e88d8b982f94c25c2cc6a_r.jpg&&&/figure&&p&这个结果是不是你预想之中的呢?因为我们只是生成了魔法球和技能效果,就没有再管它们了啊。在游戏开发中,对于每一个在场景中动态生成的物体,由于是我们通过代码创建的,因此我们就需要我们自己来关注在何时销毁它们。而与之相对的是,通过关卡编辑器预先放置在场景中的墙、木桶什么的,就不需要我们操心了,引擎会负责它们的创建和销毁。&/p&&p&那么对于这两个物体来说,它们的逻辑又各有不同。其中魔法球应该在生成后沿着射出的方向飞行,并在飞出屏幕以后自动消除;而技能效果只需要在播放完一遍以后消失就可以了。&/p&&h4&魔法球的飞行与消除&/h4&&p&首先为物体 obj_ysera_magic_bullet 的 Create、Step 和 Outside Room 事件分别建立三个脚本 MagicBulletCreate、MagicBulletStep 和 MagicBulletOutsideRoom。你们看,一切都是套路,和之前对 obj_ysera 做的事情差不多,唯一的不同是多了一个 Outside Room 事件,这个事件是指物体移动到了房间以外。&/p&&p&魔法球的飞行过程需要两个变量:飞行方向和速度。在实现上,其实这两个变量可以合并在一起变成m_speedX和m_speedY——飞行速度在X轴和Y轴的分量。在MagicBulletCreate脚本中初始化这两个变量:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&m_speedX = 0;m_speedY = 0;}

我要回帖

更多关于 你给这个游戏打多少分 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信