千里之行,始于足下
零、 笔记
学习了解 碰撞/受击箱,并根据其原理实现可砍伐的树。
一、碰撞箱与受击箱
图中玩家斧头砍下时,出现的 红色区域 为 碰撞箱 ,树木带有的 紫色区域 为 受击箱 ,因此砍树的过程,就是两者 检测碰撞 后响应的过程。
二、创建树木
第一步、以树(StaticBody2D)为根节点,Sprite2D 、CollisionShape2D 为子节点,创建场景如图:
第二步,将当前场景保存到路径 res://场景/物品 下;
第三步,选中 Sprite2D 节点,在 检查器->Texture 新建 AtlasTexture;
第四步,从路径 res://资产/Sprout Lands - Sprites - Basic pack/Objects 导入 Atlas ;
第五步,点击 编辑区域 ;
第六步,在弹窗中改变吸附模式为 像素吸附 ,框选中想要的树木后关闭;
第七步,选中 CollisionShape2D 节点,在检查器中新建 长方形碰撞 ,并覆盖树干作为树的碰撞体;
三、创建受击组件
第一步,以受击组件( Area2D )为根节点,创建场景如图;
第二步,保存至路径 res://场景/组件/ 下;
第三步,在 项目->项目设置->常规->层名称->Layer 8 中添加层名字:受击 ;
第四步,选中 受击组件 节点,在 检查器->Collision 选择Layer 8,Mask 7;
第五步,添加脚本;
class_name 受击组件
extends Area2D
signal 已受击
func _on_area_entered(area: Area2D) -> void:
已受击.emit() # 识别工具后发射信号
四、创建碰撞组件
第一步,以碰撞组件( Area2D )为根节点,创建场景如图;
第二步,保存至路径 res://场景/组件/ 下;
第三步,在 项目->项目设置->常规->层名称->Layer 7 中添加层名字:碰撞 ;
第四步,选中 碰撞组件 节点,在 检查器->Collision 选择Layer 7,Mask 8;
第五步,添加脚本;
class_name 碰撞组件
extends Area2D
五、可砍伐的树
第一步,切换到树场景, 将 受击组件 实例化为子节点,并添加 碰撞形状 ;
第二步,选中受击组件,连接信号 area_entered(area: Area2D) 至 受击组件 ;
第三步,在路径 res://脚本/ 下新建 工具箱 文件夹,并新建 工具箱.gd 脚本文件;
class_name 工具箱
extends Node
static var 工具: String
第四步,切换回 树.tscn 场景选中 树 节点,添加脚本;
extends StaticBody2D
var 生命值: int = 2
@onready var 受击组件: Area2D = $"受击组件"
func _ready() -> void:
受击组件.已受击.connect(树木消失)
func 树木消失() ->void:
if 工具箱.工具 == "砍树":
生命值 -= 1
if 生命值 == 0:
queue_free()
六、使玩家可砍伐
第一步 ,在 玩家1 场景中,添加 碰撞组件 实例化节点;
第二步 ,为碰撞组件添加 CollisionShape2D 子节点,以定义碰撞组件的形状;
第三步,选中CollsionShape2D节点,在检查器中 新建圆形碰撞形状 ;
第四步,在2D界面中,手动调节碰撞形状的 大小、位置 ,使其大概贴合玩家砍伐的范围,这里以玩家 砍树_前 动画为例;
第五步,选中CollsionShape2D节点,在 检查器->Node2D->Transform->Position 中记录此时碰撞形状的 位置(之后要用到);
第六步,同理,调节碰撞形状位置,并记录 砍树_左、砍树_右、砍树_后 不同动画下,碰撞形状的位置;
这里给出表格:
动画方向 | 碰撞位置 |
---|---|
砍树_左 | (-9,3) |
砍树_右 | (9,3) |
砍树_后 | (0,-11) |
砍树_前 | (0,10) |
第七步,不同碰撞形状混在一起时,容易辨识不清,而从 CollisionShape->Debug Color 更改颜色可以更好识别;
第八步,更新 玩家1.gd 脚本;
class_name 玩家1
extends CharacterBody2D
const 速度: float = 80.0
var 玩家方向: Vector2 = Vector2.DOWN # 默认朝向下方
var 工具启用: bool = false # 正在使用工具的标志
var 碰撞位置: Dictionary = {
"_后": Vector2(0, -11), # 面朝后
"_前": Vector2(0, 10), # 面朝前
"_左": Vector2(-9, 3), # 面朝左
"_右": Vector2(9, 3), # 面朝右
}
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var collision_shape_2d: CollisionShape2D = $"碰撞组件/CollisionShape2D"
func _ready():
# 连接动画结束信号
animated_sprite_2d.animation_finished.connect(_on_animation_finished)
collision_shape_2d.disabled = true
func _physics_process(_delta: float) -> void:
if 工具启用:
velocity = Vector2.ZERO # 使用工具时停止移动
move_and_slide()
return
# 正常移动逻辑
var 向量 = Input.get_vector("左", "右", "上", "下")
处理动画(向量)
velocity = 速度 * 向量
move_and_slide()
# 检测工具按键(U=砍树,I=浇水,O=锄地)
if Input.is_action_just_pressed("砍树"):
工具箱.工具 = "砍树" # 切换工具砍树
使用工具("砍树")
elif Input.is_action_just_pressed("浇水"):
工具箱.工具 = "浇水" # 切换工具浇水
使用工具("浇水")
elif Input.is_action_just_pressed("锄地"):
工具箱.工具 = "锄地" # 切换工具锄地
使用工具("锄地")
func 处理动画(向量: Vector2):
# 更新玩家朝向
if 向量 != Vector2.ZERO:
玩家方向 = 向量
# 根据方向设置动画
var 方向后缀 = 获取方向后缀()
if 向量 != Vector2.ZERO:
animated_sprite_2d.play("行走" + 方向后缀)
else:
animated_sprite_2d.play("空闲" + 方向后缀)
func 使用工具(行动名: String):
if 工具启用:
return
var 方向后缀 = 获取方向后缀()
# 设置碰撞形状位置
if 碰撞位置.has(方向后缀):
collision_shape_2d.position = 碰撞位置[方向后缀]
# 构建动画名称(例如:"砍树_下")
var 工具动画名 = 行动名 + 获取方向后缀()
if animated_sprite_2d.sprite_frames.has_animation(工具动画名):
工具启用 = true
collision_shape_2d.disabled = false
animated_sprite_2d.play(工具动画名)
#print("当前方向: ", 方向后缀, " 碰撞位置: ", collision_shape_2d.position)
func 获取方向后缀() -> String:
# 根据最后朝向返回方向后缀
if 玩家方向.x > 0:
return "_右"
if 玩家方向.x < 0:
return "_左"
if 玩家方向.y > 0:
return "_前"
return "_后" # 默认朝向下方
func _on_animation_finished():
# 当工具动画播放完毕时恢复状态
工具启用 = false
collision_shape_2d.disabled = true
# 保持最后朝向的静止动画
animated_sprite_2d.play("空闲" + 获取方向后缀())
七、测试1
第一步,将路径 res://场景/测试 下的 测试_基本地形 复制为 测试_可砍伐的树 ;
第二步,将 树.tscn 实例化为当前场景子节点,并调整到合适位置便于测试;
第三步,测试;
测试1完成!
八、添加掉落物
第一步,新建场景 木头.tscn,以 木头(Sprite2D)作为根节点;
第二步,保存当前场景至路径 res://场景/物品 下;
第三步,添加 可收集组件,见文章Godot4.3类星露谷游戏开发之【可收集物品】;
第四步,选中 木头(Sprite2D)节点,新建 AtlasTexture ,导入路径 res://资产/Sprout Lands - Sprites - Basic pack/Objects/ 下的精灵表图片;
第五步,点击 编辑区域 后,框选中木头图片后关闭;
第六步,切换到 树.tscn 场景,更新脚本;
extends StaticBody2D
const 木头 = preload("res://场景/物品/木头.tscn")
var 生命值: int = 2
@onready var 受击组件: Area2D = $"受击组件"
func _ready() -> void:
受击组件.已受击.connect(树木消失)
func 树木消失() ->void:
if 工具箱.工具 == "砍树":
生命值 -= 1
if 生命值 == 0:
var 掉落的木头: Node = 木头.instantiate()
掉落的木头.global_position = global_position
get_parent().add_child(掉落的木头)
queue_free()
九、测试2
运行 测试_可砍伐的树.tscn 场景,开始测试;
测试2完成!
十、免费开源资产包
某开源网站精灵图资源包链接: 点击此处
- 进入链接后点击下图按钮;
- 然后点击【No thanks,just take me to the downloads】(不了谢谢,只想下载);
- 最后点击下图按钮完成下载(注意导入前需解压缩);