无望其速成,无诱于势利。
目录
- 一、展示
- 二、复刻
- (一)节点结构
- 1. player.tscn
- 2. test_scene.tscn
- (二)独立资源/脚本
- 1. state.gd(状态基类)
- 2. game_input.gd
- (三)附加脚本
- 1. player.gd(玩家脚本)
- 2. state_machine.gd(状态管理器)
- 3. idle.gd
- 4. run.gd
- 5. jump.gd
- (四)其他设置
- 0. 导入资产
- 1. 新建动画
- 2. 添加角色碰撞
- 3. 添加碰撞平台
- 4. 显示碰撞区域
- 5. 添加导出属性
- 6. 设置输入映射
- 7. 显示画面太小
- 8. 画面模糊
- 三、运行测试
- 四、自查对照
- 五、免费开源资产包
一、展示
本文实现简易有限状态机—— 空闲/行走/跳跃。
二、复刻
(一)节点结构
1. player.tscn
- Player(CharacterBody2D)
- AnimateSprite2D
- CollisionShape2D
- NodeState(Node)
- Idle(Node)
- Walk(Node)
- Jump(Node)
2. test_scene.tscn
- TestScene(Node2D)
- Player(player.tscn):见1
- Camera2D
- StaticBody2D
- CollisionShape2D
- Player(player.tscn):见1
(二)独立资源/脚本
1. state.gd(状态基类)
功能:被所有状态子类继承的父类模板,状态子类在此基础上重写方法
class_name State
extends Node
## 状态基类
## 用于状态模式中表示一个状态,子类应继承此类并实现具体状态逻辑@warning_ignore("unused_signal")
signal transition # 当前状态发出转换信号 # 状态处理函数(每帧调用)
func _on_process(_delta : float) -> void:pass# 物理处理函数(固定时间步长调用)
func _on_physics_process(_delta : float) -> void:pass# 状态转换条件检测(由状态管理器定期调用)
func _on_next_transitions() -> void:pass# 状态进入时回调
# 当状态到此状态时自动调用,用于初始化状态相关逻辑
func _on_enter() -> void:pass# 状态退出时回调
# 当状态机切换出此状态时自动调用,用于清理状态相关资源
func _on_exit() -> void:pass
2. game_input.gd
功能:管理游戏输入逻辑
class_name GameInput
extends Nodestatic func movement_input() ->float:# 获取键盘输入方向:-1/0/1var direction = Input.get_axis("left", "right")return directionstatic func is_movement_input() ->bool:if movement_input() == 0.0:return falsereturn true
(三)附加脚本
1. player.gd(玩家脚本)
附加到player.tscn中的 Player(CharacterBody2D)节点:
class_name Player
extends CharacterBody2Dconst SPEED: float = 100.0var direction: float
2. state_machine.gd(状态管理器)
附加到player.tscn中的 StateMachine(Node)节点:
class_name StateMachine
extends Node@export var initial_state : Statevar states : Dictionary = {}
var current_state : State
var current_state_name : String
var parent_name: Stringfunc _ready() -> void:parent_name = get_parent().name# 获取状态管理器下所有状态# 加入字典、连接信号for child in get_children():if child is State:states[child.name.to_lower()] = childchild.transition.connect(transition_to)# 进入初始状态if initial_state:initial_state._on_enter()current_state = initial_statecurrent_state_name = current_state.name.to_lower()# _process 和 _physics_process
# 执行当前状态的行为(如空闲:播放空闲动画)
func _process(delta : float) -> void:if current_state:current_state._on_process(delta)func _physics_process(delta: float) -> void:if current_state:current_state._on_physics_process(delta)current_state._on_next_transitions()#print(parent_name, " Current State: ", current_state_name)# 状态转变信号的回调函数
# 状态转换
func transition_to(state_name : String) -> void:if state_name == current_state.name.to_lower():returnvar new_state = states.get(state_name.to_lower())if !new_state:returnif current_state:current_state._on_exit()new_state._on_enter()current_state = new_statecurrent_state_name = current_state.name.to_lower()#print("Current State: ", current_state_name)
3. idle.gd
附加到player.tscn中的 Idle(Node)节点:
extends State
## 空闲状态@export var player: Player
@export var animated_sprite_2d: AnimatedSprite2Dfunc _on_process(_delta : float) -> void:passfunc _on_physics_process(delta : float) -> void:# 播放空闲动画if player.direction != 0.0:animated_sprite_2d.flip_h = player.direction < 0animated_sprite_2d.play("idle")# 添加重力if not player.is_on_floor():player.velocity += player.get_gravity() * deltaplayer.move_and_slide()func _on_next_transitions() -> void:# 按下跳跃键转为跳跃状态if Input.is_action_just_pressed("jump") and player.is_on_floor():transition.emit("jump")# 有方向输入转为行走状态elif GameInput.is_movement_input():transition.emit("Run")func _on_enter() -> void:passfunc _on_exit() -> void:animated_sprite_2d.stop()
4. run.gd
附加到player.tscn中的 Run(Node)节点:
extends State
## 行走状态@export var player: Player
@export var animated_sprite_2d: AnimatedSprite2Dfunc _on_process(_delta : float) -> void:passfunc _on_physics_process(delta : float) -> void:# 获取输入var direction: float = GameInput.movement_input()# 播放移动动画if direction != 0:animated_sprite_2d.flip_h = direction < 0player.direction = directionanimated_sprite_2d.play("run")# 控制移动if direction:player.velocity.x = direction * player.SPEEDelse:player.velocity.x = 0# 添加重力if not player.is_on_floor():player.velocity += player.get_gravity() * deltaplayer.move_and_slide()func _on_next_transitions() -> void:# 按下跳跃键转为跳跃状态if Input.is_action_just_pressed("jump") and player.is_on_floor():transition.emit("jump")# 无方向输入转为空闲状态elif not GameInput.is_movement_input():transition.emit("idle")func _on_enter() -> void:passfunc _on_exit() -> void:animated_sprite_2d.stop()
5. jump.gd
附加到player.tscn中的 Jump(Node)节点:
extends State
## 跳跃状态@export var player: Player
@export var animated_sprite_2d: AnimatedSprite2Dconst JUMP_VELOCITY = -300.0func _on_process(_delta : float) -> void:passfunc _on_physics_process(delta : float) -> void:# 添加重力player.velocity += player.get_gravity() * deltaplayer.move_and_slide()func _on_next_transitions() -> void:# 着陆后根据输入选择状态if player.is_on_floor():if GameInput.is_movement_input():transition.emit("run")else:transition.emit("idle")func _on_enter() -> void:# 播放跳跃动画animated_sprite_2d.flip_h = player.direction < 0animated_sprite_2d.play("jump")# 控制跳跃player.velocity.y = JUMP_VELOCITYfunc _on_exit() -> void:animated_sprite_2d.stop()player.velocity.x = 0
(四)其他设置
0. 导入资产
从文末链接下载资产源后,导入Godot引擎。
1. 新建动画
选中 player.tscn 场景下的 AnimateSprite2D 节点,在检查器中新建SpriteFrames;
以 从精灵表添加帧 的方式:
新建 idle 动画;
新建 run 动画;
新建 jump 动画;
2. 添加角色碰撞
选中 player.tscn 场景下的 CollisionShape2D 节点,在检查器中为角色添加圆形碰撞;
3. 添加碰撞平台
选中 test_scene.tscn 场景下的 CollisionShape2D 节点,在检查器中设置碰撞 WorldBoundaryShape2D ,并拖动到合适位置;
4. 显示碰撞区域
调试->显示碰撞区域,勾选后可见碰撞体;
5. 添加导出属性
选中 player.tscn 场景下的 StateMachine 节点,在检查器中选择Idle节点;
分别选中 player.tscn 场景下的 Idle、Run、Jump 节点,在检查器中选择Player、AnimateSprite2D节点;
6. 设置输入映射
项目设置->输入映射:添加 A、W、D 控制左右移动和跳跃;
7. 显示画面太小
在项目设置->显示->窗口:缩放设置为4.0;
8. 画面模糊
在项目设置->渲染->纹理->画布纹理:默认纹理过滤设置为Nearest;
三、运行测试
搭建测试场景如下:
测试完成!
四、自查对照
问题现象 | 关键检查点 | 解决方案 |
---|---|---|
The InputMap action “left” doesn’t exist. Did you mean “ui_down”? | 检查输入映射是否存在left ,right ,jump 动作 | 项目设置→输入映射→添加left ,right ,jump 并绑定键盘A、D、W键 |
画面太小 | 检查项目设置->常规->显示->拉伸->缩放 | 调整缩放大小 |
角色像素纹理不清晰 | 检查项目设置->渲染->纹理->画布纹理->默认纹理过滤 | 在默认纹理过滤设置为Nearest |
@ _on_physics_process(): There is no animation with name ‘idle’. | 检查AnimateSprite2D的底栏动画是否创建,若已创建,则查看命名有无问题 | 创建idle动画,或者修改动画名为idle |
五、免费开源资产包
某开源网站精灵图资源包链接: 点击此处
- 进入链接后点击下图按钮
- 然后点击【No thanks,just take me to the downloads】(不了谢谢,只想下载)
- 最后点击下图按钮完成下载(注意导入前需解压缩)