【Godot4自学手册】第二十七节自定义状态机完成看守地宫怪物

本节,我将使用自定义状态机实现看守地宫怪物,完成了基础类State,状态机类StateMachine的编码,实现了怪物的闲置巡逻类、追踪类和攻击类,以及对应动画等。这节代码有点多,不过还好,代码比较简单。最终效果如下:
请添加图片描述

一、基本概念

状态机(State Machine)是有限状态自动机的简称,是指一个数学模型,通常体现为一张状态转换图。

基本组成

有限状态机主要由以下几个部分组成:
1.状态(State): 状态是有限状态机的一个基本元素,代表了系统在某一时刻的一种情况。
2.输入(Input): 输入是系统从一个状态转换到另一个状态的触发条件。有限状态机在接收到特定的输入时,会从当前状态转换到另一个状态。
3.输出(Output): 输出是状态机在执行某个动作或处理某个输入时产生的结果。在状态转换中可能会伴随有输出。
4.状态转换(Transition): 状态转换描述了状态机在接收到特定输入时,从当前状态迁移到下一个状态的过程。
5.初始状态(Initial State): 初始状态是系统开始运行时的状态。
6.终态(Final State): 终态(也称为接受状态或终止状态)是系统执行完毕或处理结束后的状态。

工作原理

有限状态机的工作原理可以概括为:
1.系统开始于初始状态。
2.接收到输入后,根据当前状态和输入,确定下一个状态。
3.进入新状态后,执行该状态对应的动作(如果有)。
4.重复上述过程,直到到达终态。

应用

有限状态机因其结构简单、逻辑清晰,在多个领域有广泛的应用:
计算机科学: 在编译原理中用于词法分析,在操作系统中管理进程状态等。
控制理论: 在自动化控制系统中,用来设计控制器。
通信系统: 在数字通信中,用于编码和同步。
软件工程: 在游戏开发、用户界面设计等领域中管理复杂的逻辑。

有限状态机是理解复杂系统行为的一种强有力的工具,通过将系统的行为分解为一系列的状态和转换,可以更容易地分析和设计系统。

二、基础代码编写

1.添加状态类代码

新建脚本文件保存在Class文件夹下命名为:State.gd,这是个状态类基础代码,定义了一个状态改变信号Transitioned,和四个基本函数进入函数Enter()、退出函数Exit()、更新函数Update()、物理更新函数Physics_Update(),只是定义了函数没有函数内容,当继承时具体填写内容。代码如下:

extends Node
class_name  State
signal  Transitioned
func Enter():
	pass
func Exit():
	pass	
func Update(delta:float):
	pass
func Physics_Update(delta:float):
	pass
2.添加有限状态机代码

新建脚本文件保存在Class文件夹下命名为:StateMachine.gd。顾名思义,这是对敌人的各种状态进行管理的一个类,代码中有具体功能注释。代码如下:

extends Node
@export var inital_state:State  #初始状态
var current_state:State  #当前状态
var states:Dictionary={}  #状态字典

func _ready():
	#完成状态字典数据
	for child in get_children():
		if child is State:
			states[child.name.to_lower()]= child
			child.Transitioned.connect(on_child_transition)  #连接信号到本页脚本
	#设置初始状态
	if inital_state:
		inital_state.Enter() #调用状态进入函数
		current_state = inital_state
	pass 


func _process(delta):
	#调用当前状态更新函数
	if current_state:
		current_state.Update(delta)
		
func _physics_process(delta):
	#调用当前状态物理更新函数
	if current_state:
		current_state.Physics_Update(delta)

#状态改变信号调用函数,第一个参数表示目前处于状态,也就是进入新的状态有哪个状态发起的;第二个参数表示要进行新状态的名称
func on_child_transition(state,new_state_name):
	#如果传入的状态部署当前状态,退出信号
	if state!=current_state:
		return
	#根据状态名称调出状态数据字典中对应的状态
	var new_state = states.get(new_state_name.to_lower())
	#如果状态数据字典中不存在对应的状态退出
	if !new_state:
		return
	#退出当前状态,调用状态退出函数
	if current_state:
		current_state.Exit()
	#进入新的状态,调用进入函数
	new_state.Enter()
	#将当前状态设置为新的状态
	current_state = new_state	

这样我们有效状态机的基础代码就写好了。

三、敌人的各种状态代码

在我们的文件系统重新建一个States文件夹用来保存各种状态。在该文件下新建EnemyState文件夹来保存敌人的状态代码。

1、空闲巡逻代码

新建脚本文件保存在States->EnemyState文件夹下命名为:EnemyIdle.gd。代码如下:

extends  State  #继承基本状态类
class_name  EnemyIdle  #类名称
@export var enemy:CharacterBody2D  #敌人,出现在该类的检查器,可拖入敌人的CharacterBody2D对象
@export var move_speed:=30.0  #敌人移动速度,出现在该类的检查器
@export var anima:AnimatedSprite2D #敌人播放动画类,出现在该类的检查器
var player:CharacterBody2D  #玩家对象
var move_direction:Vector2  #敌人移动方向
var wander_time  #敌人巡逻时间
#随机巡逻函数,产生随机方向和巡逻时间
func randomize_wander():
	#产出敌人随机移动方向
	move_direction = Vector2(randi_range(-1,1),randi_range(-1,1)).normalized()
	#敌人此方向随机巡逻时间
	wander_time = randf_range(1,3)
#状态进入时调用的函数
func Enter():
	#在主目录中根据分组查询主人公对象
	player = get_tree().get_first_node_in_group("Player")
	randomize_wander()#调用随机巡逻函数

func Update(delta:float):
	if wander_time>0:#如果该方向巡逻时间大于0,巡逻时间减去delta时间
		wander_time -=delta
	else:#如果敌人在方向巡逻时间完成,从新产生巡逻随机方向和时间
		randomize_wander()

func Physics_Update(delta:float):
	#获取敌人和主人公之间的方向和距离
	var direction= player.global_position-enemy.global_position	
	#如果敌人和主人公之间的方向和距离大于跟踪距离,敌人进行巡逻状态
	if direction.length()<25:
		Transitioned.emit(self,"Attack")#如果敌人和主人公之间的方向和距离小于攻击距离,发出攻击信号
		return
	elif direction.length()<100:
		Transitioned.emit(self,"Follow")#如果敌人和主人公之间的方向和距离处于跟踪距离,发出跟踪信号
		return
	if enemy:
		enemy.velocity = move_direction * move_speed#设置敌人的速度
		if enemy.velocity==Vector2.ZERO:#如果敌人的速度为0,播放休闲动画
			anima.play("Idle")
		else:#如果敌人的速度不为0,播放行走动画
			anima.play("Walk")
2、跟踪状态代码

新建脚本文件保存在States->EnemyState文件夹下命名为:EnemyFollow.gd。代码如下:

extends State  #继承基本状态类
class_name  EnemyFollow   #类名称
@export var enemy:CharacterBody2D #敌人,出现在该类的检查器,可拖入敌人的CharacterBody2D对象
@export var move_speed:=30.0 #敌人移动速度,出现在该类的检查器
@export var anima:AnimatedSprite2D  #敌人播放动画类,出现在该类的检查器
var player:CharacterBody2D #玩家对象
#状态进入时调用的函数
func Enter():
	#在主目录中根据分组查询主人公对象
	player = get_tree().get_first_node_in_group("Player")
	pass
	
func Update(delta:float):
	pass

func  Physics_Update(delta:float):		
	#计算敌人与主人公之间的方向和距离
	var direction= player.global_position-enemy.global_position
	if direction.length()>100:#如果敌人与主人公之间的距离未达到跟踪范围,发出空闲巡逻状态信号
		Transitioned.emit(self,"Idle")
		return
	if direction.length()<25:#如果敌人与主人公之间的距离进入攻击范围,发出攻击状态信号
		Transitioned.emit(self,"Attack")
		return
	if anima:#如果动画设置不为空,播放行走动画
		anima.play("Run")
	enemy.velocity = direction.normalized() * move_speed #设置行走速度	
3、攻击状态代码

新建脚本文件保存在States->EnemyState文件夹下命名为:EnemyAttack.gd。代码如下:

extends State #继承基本状态类
class_name EnemyAttack  #类名称
@export var enemy:CharacterBody2D #敌人,出现在该类的检查器,可拖入敌人的CharacterBody2D对象
@export var anima:AnimatedSprite2D #敌人播放动画类,出现在该类的检查器
var player:CharacterBody2D #玩家对象
#状态进入时调用的函数
func Enter():
	#在主目录中根据分组查询主人公对象
	player = get_tree().get_first_node_in_group("Player")

func  Physics_Update(delta:float):	
	enemy.velocity= Vector2()
	#计算敌人与主人公之间的方向和距离
	var direction= player.global_position-enemy.global_position
	#如果敌人与主人公之间的距离大于100,发出空闲巡逻状态信号
	if direction.length()>100:
		Transitioned.emit(self,"Idle")
	elif direction.length()>25:#如果敌人与主人公之间的距离达到跟踪范围,发出跟踪状态信号
		Transitioned.emit(self,"Follow")
	if anima:#播放攻击动画
		anima.play("Attack")

四、应用到场景中

新建CharacterBody2D场景,存到Scenes文件夹下,命名为Monster。为场景添加相关节点。

1.添加AnimatedSprite2D节点。

添加AnimatedSprite2D节点,命名为Anima。在其检查器中,选择Animation->Sprite Frames属性,下拉菜单中选择新建SpriteFrames。选中该属性,在动画帧面板中讲default命名为Idle,单击从精灵表中添加动画帧按钮,在弹出的打开文件对话框中选择我们准备的敌人图片素材,如下:
请添加图片描述

在弹出的选择帧面板中将水平设置为4,垂直设为5,这是根据我们敌人图片对应进行设置的,因为我们的敌人图片正好是5行4列。然后选择0-3帧图片,最后单击添加帧按钮。
请添加图片描述

然后在动画帧面板中开启循环和自动播放按钮,如下:
请添加图片描述

这样就完成了等待动画。下面单击添加动画按钮,命名为Walk,然后跟制作等待动画类似完成行走动画;依此类推完成跑动动画Run、攻击动画Attack,这里面有个细节需要说一下,行走动画为图片素材的第2行、跑步动画为图片素材的第3行;攻击动画为图片素材的4和5行。这3个动画都不需要开启自动播放,但是行走动画、跑步动画需要开启循环,攻击动画不需要开启循环。跑步动画和攻击动画设为8FPS,动画变快,游戏显得更合理些。
请添加图片描述

2.添加CollisionShape2D节点

添加CollisionShape2D节点,命名为Collision。在其检查器中,选择CollisionShape2D->Shape属性选择新建CapsuleShape2D(椭圆形碰撞),然后在场景中将椭圆形调整合适大小和位置。
请添加图片描述

3.添加Node2D节点

一是添加Node2D节点,命名为StateMachine。然后单击为选中节点创建或设置脚本按钮,选择我们前面编写好的代码StateMachine.gd。
请添加图片描述

然后在检查器中,将Inital State设置为Idle状态。
请添加图片描述

二是选择StateMachine节点,单击添加子节点按钮,然后在创建节点对话框中选择EnemyIdle节点,该节点重命名为Idle。
请添加图片描述

在检查其中将Enemy设置成Monster根节点;Anima设置成该场景中Anima节点。
请添加图片描述

三是与二方法类似添加EnemyFollow和EnemyAttack节点,重命名为Follow和Attack。在检查器中对应设置Enemy和Anima属性,最终节点目录如下:
请添加图片描述

4.根节点添加脚本

选择Monster跟节点,单击为选中节点创建或设置脚本按钮,把脚本保存到Scripts目录,命名为Monster.gd。编写如下代码:

extends CharacterBody2D
@onready var anima = $Anima  #获取动画
func _physics_process(delta):
	if velocity.x<0:#如果速度小于0,翻转动画
		anima.flip_h=true
	else:
		anima.flip_h= false
	move_and_slide()
5.主场景中调用

切换到Main主场景中,单击实例化子场景,选择Monster场景。
请添加图片描述

然后调整到需要的位置。
请添加图片描述

最后看一下效果:
请添加图片描述

这节就到这了,下节见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/489592.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言 汉诺塔问题

目录 1.前言 2.问题描述 3.问题分析 4.定义一个主函数 5.再定义一个hanoi函数 6.所有代码 7.结语 1.前言 汉诺塔问题&#xff0c;是心理学实验研究常用的任务之一。该问题的主要材料包括三根高度相同的柱子和一些大小及颜色不同的圆盘&#xff0c;三根柱子分别为起始柱A…

大数据入门(一)

大数据主要要解决&#xff1a;海量数据的采集&#xff0c;存储&#xff0c;分析计算问题。 大数据的特点&#xff1a;大量&#xff08;数据量大&#xff09;&#xff0c;高速&#xff08;数据量的累积越来越快&#xff09;&#xff0c;多样&#xff08;结构化数据和非结构化数…

基于nodejs+vue医院综合管理系统实现与设计python-flask-django-php

第一&#xff0c;研究分析当下主流的nodejs技术&#xff0c;结合医院日常管理方式&#xff0c;进行医院综合管理系统的数据库设计&#xff0c;设计医院综合管理系统功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架构、系统搭建采用的服务…

【办公类-50-01】20240326判断随机写的“日期”是否是双休日

背景需求&#xff1a; 领导让我做设计本学期的科研培训方案。 我在2-6月随机写每月的培训日期&#xff0c;重新制定了主题 因为科研培训不可能在双休日&#xff0c;因此我希望本次活动的随机写的日期&#xff0c;不能是双休日。 我想用Python判断一下这些预设的日期是否是双休…

SpringBoot—@ConditionalOnBean与@ConditionalOnClass

一、ConditionalOnBean概念 需求场景 比如下面一种场景&#xff0c;我在实例化People对象的时候&#xff0c;需要注入一个City对象。这个时候问题来了&#xff0c;如果city没有实例化&#xff0c;那么下面就会报空指针或者直接报错。 所以这里需求很简单&#xff0c;就是当前c…

JS加密解密之应用如何保存到桌面书签

前言 事情起因是这样的&#xff0c;有个客户解密了一个js&#xff0c;然后又看不懂里边的一些逻辑&#xff0c;想知道它是如何自动拉起谷歌浏览器和如何保存应用到书签的&#xff0c;以及如何下载应用的。继而诞生了这篇文章&#xff0c;讲解一下他的基本原理。 渐进式Web应用…

电源模块 YULIN俞霖科技DC/DC电源模块 直流升压 高压稳压

Features 最低工作电压&#xff1a;0.7V电压隔离&#xff1a;1000VDC /3000VDC 平均无故障时间&#xff1a; > 800,000 小时短路与电弧保护无最低负载要求&#xff1a;可空载工作输入电压&#xff1a;5、12、15、24VDCOutput 100,200、300、400、500 、600、800、 1000、1…

kubernetes-k9s一个基于Linux 终端的集群管理工具

效果预览 下载 github 版本 此文档使用的版本是 v0.32.4&#xff0c;下载地址&#xff1a; https://github.com/derailed/k9s/releases/download/v0.32.4/k9s_linux_amd64.rpm 安装 rpm -ivh k9s_linux_amd64.rpm使用 启动 终端直接执行命令 k9s k9s基本操作 1 选择目…

魔众文库后台显示多少条,这个在那里文件修改?

显示多少条是那个文件修改的&#xff0c;显示1000条服务器比较差&#xff0c;加载太慢了。想要修改小一点。 这个是全局的显示配置&#xff0c;在文件 module/Wenku/Admin/Controller/WenkuDocController.php 中。 ->pageSizes([10, 100, 1000])

Redis中RDB的dirty机制和AOF中的后台重写机制

RDB的dirty计数器和lastsave属性 服务器除了维护saveparams数组之外&#xff0c;还维持着一个dirty计数器,以及一个lastsave属性: 1.dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后&#xff0c;服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括…

[Android]模拟器登录Google Play失败

问题&#xff1a; 模拟器登录Google Play失败&#xff0c;提示couldnt sign in there was a problem communicating with google servers. try again later. 原因&#xff1a; 原因是模拟器没有连接到互联网&#xff0c;打开模拟器中Google浏览器进行搜索一样不行。 解决&am…

LED和数码管及按键

目录 LED LED灯亮的原理图 LED灯光闪烁 电路设计 keil文件 LED流水灯的实现 keil文件 数码管 显示的基本原理 LED数码管的显示方式 静态显示方式 动态显示方式 具体案例 数码管静态显示 电路图 keil文件 数码管动态显示 电路图 keil文件 74LS138译码器 译…

【Java程序设计】【C00367】基于(JavaWeb)Springboot的粮仓管理系统(有论文)

TOC 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击卡片…

Pandoc下载和安装笔记

目录 一、下载 二、安装 1、安装软件 2、测试是否安装成功 Pandoc 的作者是 John MacFarlane&#xff0c;John MacFarlane是美国加州大学伯克利分校的哲学系的一位教授。编写Pandoc 用来生成讲义、课件和网站等。程序开源免费&#xff0c;目前以 GPL 协议托管在 Github 网站…

国内用户掌握ChatGPT,你已超越万人!

在数字时代&#xff0c;掌握前沿技术往往意味着拥有更多的机遇和可能。ChatGPT&#xff0c;作为当前最热门的人工智能技术之一&#xff0c;已经证明了其在各个领域的广泛应用价值。但在中国&#xff0c;能熟练使用ChatGPT的人究竟领先了多少人&#xff1f;让我们深入探讨。>…

hbuilderx打包苹果证书获取步骤

简介&#xff1a; 目前app开发&#xff0c;很多企业都用H5框架来开发&#xff0c;而uniapp又是这些h5框架里面最成熟的&#xff0c;因此hbuilderx就成为了开发者的首选。然而,打包APP是需要证书的&#xff0c;那么这个证书又是如何获得呢&#xff1f; 生成苹果证书相对复杂一些…

一本通差分约束入门题

最关键的就是找好所有的要满足的不等式条件&#xff0c;注意隐含的条件还有一点就是注意没有源点 建立源点 #2436. 「SCOI2011」糖果 #include<bits/stdc.h> using namespace std; using ll long long; using pii pair<int,int>; #define int long long const in…

随身wifi排行榜前三名大对比,格行vs华为vs中兴随身wifi谁是你心中的第一名?

第一名&#xff1a;格行随身wifi 品牌实力&#xff1a;随身wifi国内领跑品牌&#xff0c;深耕物联网15年&#xff0c;专注研发随身wifi&#xff0c;国内市场占有率较高&#xff0c;综合实力和口碑领先行业其他品牌。 产品优势&#xff1a;小巧便捷&#xff0c;彩屏显示&#…

SGE 如何影响 SEO?

虽然谷歌的 “Search Generative Experience”&#xff08;SGE&#xff09;并不保证一定会推出&#xff08;谷歌以其废弃项目的坟场而闻名&#xff09;&#xff0c;但 SEO 人员不能忽视它&#xff0c;因为它预计会对有机搜索产生负面影响&#xff1a; 可见性流量转化率收入 S…

Vue js封装接口

天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/ 1.安装axios npm install axios -g 2.在src下新建一个Api文件夹,再创建一个js文件 import axios from axios let configuration {url:"http://localhost:9090" } /*** 请求项目数据的请求体*/ async function h…