游戏开发论坛

 找回密码
 立即注册
搜索
查看: 2969|回复: 0

GAMEMAKER STUDIO 系列:简单状态机

[复制链接]

8364

主题

8525

帖子

1万

积分

版主

Rank: 7Rank: 7Rank: 7

积分
14833
发表于 2018-12-13 10:36:28 | 显示全部楼层 |阅读模式
文/Nathan Ranney
译/highway

搜狗截图18年12月13日1034_1.jpg

按照设计,状态机一次只能处于一种状态。 由我们来定义对我们的情况有意义的状态,以及它们之间的关系。 在本文中,我们将使用状态机来控制在任何给定时间可用的玩家操作,允许我们设置角色并定义角色可以执行的操作。

大家好, 今天我想告诉你如何设置一个简单的状态机。 状态机是一种数据结构,顾名思义,它跟踪不同的状态。 例如,我们的游戏可能有三种状态:“游戏运行”,“游戏暂停”和“游戏结束”。我们可能会使用状态机来记住哪一个处于活动状态,并定义如何从一个状态转换到另一个状态(请参阅 上面的图片)。

基础设置

在开始之前,你需要将这些动画添加到工程中。 从此链接下载精灵并将其添加到你的项目中。 我已经恰当地命名了文件,因此只要确保精灵的名称与文件名相匹配,就可以将其添加到 GMS 中。 继续添加所有精灵 - 甚至是敌人的精灵 - 因为我们将在以后的文章中需要这些精灵。 确保每个精灵的原点是 (16,32)


枚举,控制器和持久性

为了设置我们的状态机,我们首先确定哪些状态是可能的,以及我们如何在代码中识别它们。 由于这个例子都是关于角色动作的,所以让我们定义那些动作是什么,并给每个动作一个整数 id。 最简单的方法是使用 enum(枚举,Enumeration 的缩写),它是在自定义变量类型下保存的常量的集合。 如果你熟悉 Gamemaker 中的 Macros(宏),那么枚举就是这样的。 我更喜欢使用枚举,因为它们比宏更容易管理和跟踪。

  1. 创建一个脚本并将其命名为enum_init。 添加以下行。
  2. //states enum states
  3. {     
  4.     normal,     
  5.     crouch,      
  6.     attack,     
  7.     hit
  8. }
复制代码

请注意,我们不必设置每个条目的值,枚举自动将值0指定为“正常”,然后在每个条目后递增。 我们可以随时获得值

  1. var example = states.attack;
  2. show_message(string(example));  //output: 3
复制代码

实际上,你可以通过为每个条目指定值来覆盖枚举的自动编号,但重要的是要重申枚举是常量。 它们定义后无法更改!

枚举也是全局的,这意味着任何对象都可以访问它们。 这对我们的状态机来说非常完美。

现在我们有了枚举,我们在哪里实例化它? 从非持久对象调用这些变量,比如我们的 oPlayer 对象,不是最好的主意。 我们想要做的是创建一个持久的控制器对象(它始终存在),以管理许多对象可以访问的枚举和其他数据类型之类的东西。 继续创建一个新对象并将其命名为“con”(译注:建议还是 oController 这样与其他对象保持相同的命名前缀,在看代码时会比较易读。)。 我喜欢保持我的控制器名称简短,因为它更容易返回。选中新对象上的“持久(Persistent)”框。 最后,将 Create 事件添加到对象,并添加以下面的代码:

  1. ///init 初始化
  2. enum_init();
复制代码

con 对象放在你的房间里。 由于此对象是持久的,因此除非您明确销毁,否则它将继续存在! 无需在每个房间放置此物体。

Switch cases

既然我们已经在枚举中定义了状态,我们就可以从我们的玩家对象中访问它们了。 打开我们在上一个条目中创建的 oPlayer 对象,并将以下行添加到 create 事件中。

  1. attack = false;

  2. //states
  3. currentState = 0;
  4. lastState = 0;

  5. //movement
  6. xSpeed = 0;
  7. ySpeed = 0;
  8. lastSprite = sprite;
复制代码

End Step 事件添加到 oPlayer,然后添加一些代码。

  1. xPos = x;
  2. yPos = y;

  3. x += xSpeed;
  4. y += ySpeed;  

  5. //animation
  6. frame_reset();
复制代码

现在让我们跳到 step 事件。 我们可以删除我们在之前那篇文章中添加的几乎所有代码,因为大多数代码只是为了展示 draw_sprite_ext 的不同部分。 查看下面的代码,并确保你的 step 事件看起来完全相同。

  1. //buttons
  2. player_buttons();

  3. //animation
  4. frame_counter();  

  5. //state switch
  6. switch currentState
  7. {     
  8.     case states.normal:         
  9.         normal_state();     
  10.     break;      

  11.     case states.crouch:         
  12.         crouch_state();     
  13.     break;      

  14.     case states.attack:         
  15.         attack_state();     
  16.     break;
  17. }
复制代码

如果你之前从未见过 switch 语句,你可能会想知道到底发生了什么。 我稍后会解释,但首先我们需要创建三个新脚本:normal_state,crouch_state attack_state。 我喜欢使用不同状态的脚本,因为它使代码更容易阅读。 你可以弹出所需的任何脚本(译注:在 GMS2 中,在代码中的脚本上按下鼠标中键即可弹出对应的脚本,并链接在当前对象窗口),并在该特定状态下工作。

好吧,所有这一切究竟意味着什么呢? 什么是 switch 语句以及它是如何工作的? 将 switch 语句视为 if 语句的更具体版本。if 语句用于布尔值检查,条件满足则执行,switch 语句用于根据变量的值执行代码。 看看下面的代码块。

  1. //if statement
  2. if(currentState == states.normal)
  3. {     
  4.     normal_state();
  5. } else if(currentState == states.crouch) {     
  6.     crouch_state();
  7. }  

  8. //switch statement
  9. switch currentState
  10. {     
  11.     case states.normal:         
  12.         normal_state();     
  13.     break;  

  14.     case states.crouch:         
  15.         crouch_state();     
  16.     break;
  17. }
复制代码

这两段代码在功能上都是相同的。 它们都将根据 currentState 变量的当前值运行我们想要的脚本,但 switch 语句要清晰得多。 当我们添加状态时,使用 if 语句变得难以管理。 switch 语句更容易管理。

最后,我们需要在 player_buttons 脚本中添加一个新的按钮变量。 打开该脚本并添加此行:

  1. attack = keyboard_check_pressed(ord("Z"));
复制代码

状态机

我们已经定义了一个可能状态的枚举,以及变量 currentState 来跟踪哪个状态是活动的。 现在我们知道了 switch 语句的工作原理,我们可以创建在每个状态下执行的代码,以及在它们之间进行转换的规则。 switch 语句可以很容易地显示我们的状态机是什么以及它正在做什么。 如果我们的 currentState 变量等于语句中的一个 case,则执行与该 case 相关的代码。 由于我们为每个状态创建了脚本,因此请继续打开 normal_state 脚本并添加以下代码

  1. //移动
  2. if(left)
  3. {     
  4.     xSpeed = -2;
  5. } else if(right) {     
  6.     xSpeed = 2;
  7. } else {     
  8.     xSpeed = 0;
  9. }

  10. //切换到下蹲状态
  11. if(down)
  12. {     
  13.     currentState = states.crouch;
  14. }

  15. //切换到攻击状态
  16. if(attack)
  17. {     
  18.     currentState = states.attack;
  19. }
复制代码

这段代码非常简单。 对我来说,正常状态意味着角色的默认状态。 他们没有执行任何特殊操作,例如攻击或使用道具,玩家可以完全控制角色。 在这里,我们有左右移动,并转换到蹲和攻击状态。 如果你现在运行游戏,你将无法看到我们的状态机的全部效果。 如果你按 Z,你将改变状态,不再能够移动。 接下来让我们定义蹲状态。 打开 crouch_state 脚本并添加以下代码:

  1. xSpeed = 0;  
  2. if(!down)
  3. {     
  4.     currentState = states.normal;
  5. }
复制代码

蹲下时(按住向下箭头键)我们停止玩家的水平移动(xSpeed = 0)。 如果他们释放向下键,我们将返回正常状态。 这将是一个在蹲下时添加不同动作的好地方,比如爬行或者可能是蹲下的攻击。
打开我们创建的最后一个状态脚本,attack_state,并添加以下代码:

  1. xSpeed = 0;  

  2. if(frame > sprite_get_number(sprite) - 1)
  3. {     
  4.     currentState = states.normal;
  5. }
复制代码

我们再次将水平速度归零,并且当动画结束时我们将玩家状态设置回正常。 但是......我们还没有设置我们的动画,是吗? 动画控制是状态机和 switch 的另一个重要用途! 创建一个新脚本并将其命名为 animation_control。 添加以下代码:

  1. xScale = approach(xScale,1,0.03);
  2. yScale = approach(yScale,1,0.03);  

  3. //动画控制
  4. switch currentState
  5. {     
  6.     case states.normal:         
  7.         if(left)
  8.         {            
  9.             facing = -1;         
  10.         } else if(right) {            
  11.             facing = 1;         
  12.         }                  
  13.         if(left || right)
  14.         {            
  15.             sprite = sprPlayer_Run;         
  16.         } else {            
  17.             sprite = sprPlayer_Idle;         
  18.         }     
  19.     break;

  20.     case states.crouch:         
  21.         sprite = sprPlayer_Crouch;     
  22.     break;      

  23.     case states.attack:         
  24.         sprite = sprPlayer_Attack;     
  25.     break;
  26. }

  27. //如果精灵更改,则将帧重置为0
  28. if (lastSprite != sprite)
  29. {     
  30.     lastSprite = sprite;     
  31.     frame = 0;
  32. }
复制代码

通过使用另一个 switch 语句,我们可以轻松控制播放器动画。 请注意,我们可以在 switch case 中使用 if 语句! 我们没有将动画控制与我们创建的初始 switch 语句组合在一起的原因有几个。 首先,我们希望我们的动画在所有代码的最后发生。 动画是之前发生的一切的结果! 其次,它让代码更好读。 上面代码底部的最后一个表达式会在精灵更改时将帧重置为 0。 这可以防止在更改精灵时动画在错误的帧上启动。

请注意,我们将 xScaleyScale 代码移动到 animation_control 的顶部。 这对以后很重要。

oPlayer 对象中打开 end step 事件,并将以下行添加到代码的底部。 这将确保它在其他一切之后发生。

  1. animation_control();
复制代码

来吧,运行游戏。 你应该有一个能够左右奔跑,空闲,蹲和攻击的角色了! 我们能够根据当前状态区分角色的行为。 除了管理优势之外,设置状态机还可以更轻松地跟踪错误,添加新行为以及跟踪对象的整体结构。 我经常使用状态机和 switch 语句来控制比如要显示的菜单屏幕,当前的游戏模式以及定义给敌人的 AI 类型等内容。

谢谢阅读! 在 Twitter上关注我,并在我的网站上关注更多与游戏开发相关的内容。

来源:INDIENOVA
地址:https://www.indienova.com/indie-game-development/gamemaker-studio-simple-finite-state-machine/


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-4-19 08:16

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表