译文|GAMEMAKER STUDIO 系列:简单状态机

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

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

基础设置

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

枚举,控制器和持久性

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

创建一个脚本并将其命名为enum_init。 添加以下行。
//states enum states
{
    normal,
    crouch,
    attack,
    hit
}

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

var example = states.attack;
show_message(string(example));  //output: 3

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

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

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

///init 初始化
enum_init();

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

Switch cases

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

attack = false; 

//states
currentState = 0;
lastState = 0; 

//movement
xSpeed = 0;
ySpeed = 0;
lastSprite = sprite;

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

xPos = x;
yPos = y; 

x += xSpeed;
y += ySpeed;  

//animation
frame_reset();

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

//buttons
player_buttons(); 

//animation
frame_counter();  

//state switch
switch currentState
{
    case states.normal:
        normal_state();
    break;      

    case states.crouch:
        crouch_state();
    break;      

    case states.attack:
        attack_state();
    break;
}

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

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

//if statement
if(currentState == states.normal)
{
    normal_state();
} else if(currentState == states.crouch) {
    crouch_state();
}  

//switch statement
switch currentState
{
    case states.normal:
        normal_state();
    break;  

    case states.crouch:
        crouch_state();
    break;
}

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

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

attack = keyboard_check_pressed(ord("Z"));

状态机

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

//移动
if(left)
{
    xSpeed = -2;
} else if(right) {
    xSpeed = 2;
} else {
    xSpeed = 0;
}

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

//切换到攻击状态
if(attack)
{
    currentState = states.attack;
}

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

xSpeed = 0;
if(!down)
{
    currentState = states.normal;
}

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

xSpeed = 0;  

if(frame > sprite_get_number(sprite) - 1)
{
    currentState = states.normal;
}

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

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

//动画控制
switch currentState
{
    case states.normal:
        if(left)
        {
            facing = -1;
        } else if(right) {
            facing = 1;
        }
        if(left || right)
        {
            sprite = sprPlayer_Run;
        } else {
            sprite = sprPlayer_Idle;
        }
    break; 

    case states.crouch:
        sprite = sprPlayer_Crouch;
    break;      

    case states.attack:
        sprite = sprPlayer_Attack;
    break;
} 

//如果精灵更改,则将帧重置为0
if (lastSprite != sprite)
{
    lastSprite = sprite;
    frame = 0;
}

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

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

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

animation_control();

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

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

原文地址:https://www.cnblogs.com/vega1994/p/10123401.html

时间: 2024-10-11 05:13:13

译文|GAMEMAKER STUDIO 系列:简单状态机的相关文章

史上最详细的Android Studio系列教程一--下载和安装

链接地址:http://segmentfault.com/a/1190000002401964#articleHeader4 原文链接:http://stormzhang.com/devtools/2014/11/25/android-studio-tutorial1/ 背景 相信大家对Android Studio已经不陌生了,Android Studio是Google于2013 I/O大会针对Android开发推出的新的开发工具,目前很多开源项目都已经在采用,Google的更新速度也很快,明显

史上最详细的Android Studio系列教程五--Gradle命令详解与导入第三方包

Android Studio + Gradle的组合用起来非常方便,很多第三方开源项目也早都迁移到了Studio,为此今天就来介绍下查看.编译并导入第三方开源项目的方法. Sublime + Terminal编译并查看源码 首先来给大家介绍一种简便并且个人最喜欢的一种办法.很多时候我们在GitHub上看到一个不错的开源项目,一般有两种需求,阅读源码和查看运行效果,如果是单纯的查看源码我更喜欢用一些轻量级编辑器,如vim,sublime等,vim不是很熟练,所以个人一种都习惯用sublime来查看

技术系列之“状态机”

技术系列之“状态机” 作者: cppexplore  发布时间: 2015-09-13 11:03  阅读: 1194 次  推荐: 2                   原文链接   [收藏] 一.状态机描述 状态机理论最初的发展在数字电路设计领域.在数字电路方面,根据输出是否与输入信号有关,状态机可以划分为Mealy型和Moore型状态机:根据输出是否与输入信号同步,状态机可以划分为异步和同步状态机.而在软件设计领域,状态机设计的理论俨然已经自成一体.Moore型状态机的输出只和当前状态有

Android Studio系列教程一--下载与安装

Android Studio系列教程一--下载与安装 背景 相信大家对Android Studio已经不陌生了,Android Studio是Google于2013 I/O大会针对Android开发推出的新的开发工具,目前很多开源项目都已经在采用,Google的更新速度也很快,明显能感觉到这是Android开发的未来,那么我们还有什么理由不去拥抱未来呢? 虽然推出了很久,但是国内貌似普及的程度并不高,鉴于很多朋友求studio的详细教程,那么今天我就手把手教大家下载.安装.使用,Studio之路

史上最详细的Android Studio系列教程四--Gradle基础

史上最详细的Android Studio系列教程四--Gradle基础

使用Visual Studio创建简单的自定义Web Part 部件属性

使用Visual Studio创建简单的自定义Web Part 部件属性 自定义属性使用额外的选项和设置拓展你的Web part部件.本文主要讲解如何使用Visual Studio创建简单的自定义Web Part 部件属性. 1. 打开Visual Studio,点击文件--新建项目--空白SharePoint项目CustomWPProperties.部署为场解决方案. 2. 右击项目添加新项Web Part部件WPPropertyExample,点击添加. 3. 右击WPPropertyExa

一个异常与Android Studio系列教程参考

由于编译过程中遇到了错误: 报错信息: Error:(4, 6) No resource found that matches the given name: attr 'style'. Error:Execution failed for task ':app:processDebugResources'.> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: P

zend studio系列 --- 自己的常用总结

zend studio系列 --- 自己的常用总结 目录: 设置编辑器 编码 设置编辑器 编辑区背景色 设置编辑器 编辑区字体 安装SVN插件 一.设置编辑器编码: 路径:Window->perference->General->Workspace->Text file encoding ,设置成UTF-8 二.设置编辑器 编辑区背景色: 路径:Window->perference->General->Editors->Text Editors->Ap

Android Studio 系列教程(转载)

史上最详细的Android Studio系列教程一--下载和安装:http://segmentfault.com/a/1190000002401964史上最详细的Android Studio系列教程二--基本设置与运行:http://segmentfault.com/a/1190000002411926史上最详细的Android Studio系列教程三--快捷键:http://segmentfault.com/a/1190000002418983史上最详细的Android Studio系列教程四