千里之行,始于足下
文章目录
零、 笔记
创建可路径导航、动态避障的小鸡
一、认识导航节点
为了实现小鸡路径导航、动态避障的效果,要用到三种导航节点:
(一)智能驾驶汽车
装小鸡的智能驾驶汽车 -> NavigationAgent2D
(二)人工智能
汽车注册使用的人工智能,用于规划路径 -> NavigationServer2D
(三) 场地
导航区域,确定汽车行驶范围 -> NavigationRegion2D
二、创建小鸡
(一)创建小鸡动画
第一步,创建新场景,以 CharectorBody2D 作为场景根节点,改名为:小鸡;
第二步,分别添加 AnimationSprite2D 动画节点和 CollisionShape2D 碰撞节点作为子节点;
第三步,选中 AnimationSprite2D 动画节点,在检查器中新建 SpriteFrames 动画帧;
第四步,在编辑器 SpriteFrames 底栏中使用精灵表创建动画帧;
在文件系统 res://资产/Sprout Lands - Sprites - Basic pack/Characters 路径下打开 Free Chicken Sprites.png 文件夹;
第五步,依图中顺序点击添加,创建动画(Tips:Ctrl + 鼠标滚轮 可以调整视图大小) ;
第六步,设置为自动播放,调节帧速,并命名为 空闲 ;
第七步,同上,设置如图,创建行走动画;
(二)添加碰撞体
第一步,选中 CollisionShape2D 节点,并在检查器中新建 圆形碰撞 ;
第二步,调整 圆形碰撞 至比小鸡略小;
第三步,新建【 res://场景/NPC/小鸡 】文件夹,并保存 小鸡 文件;
第四步,打开 项目设置 ,为2D物理层命名;
第五步,分别设置小鸡和玩家的物理层;
三、让小鸡动起来
(一)行走/空闲状态切换
在小鸡真正动起来前,给它一个计时器 Timer ,告诉它什么时候走,什么时候停:
第一步,给小鸡一个计时器 Timer ;
第二步,为 小鸡 节点附加脚本;
extends CharacterBody2D
@export var 等待时间: float = 5.0
var 当前状态: StringName = "空闲"
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var timer: Timer = $Timer
func _ready():
# 配置定时器
timer.timeout.connect(切换状态)
func 切换状态():
# 切换状态
if 当前状态 == "空闲":
当前状态 = "行走"
animated_sprite_2d.play("行走")
timer.start(等待时间)# 启动计时器
else:
当前状态 = "空闲"
animated_sprite_2d.play("空闲")
timer.start(等待时间)# 启动计时器
第三步,选中计时器 Timer 节点,并在检查器中设置 Wait Time 属性为5秒,勾选 One Shot ,Autostart ;
第四步,将 测试_基本地形 复制为 测试_小鸡NPC ;
第五步,打开 测试_小鸡NPC 场景文件,按下图修改,以便于测试小鸡NPC;
第六步,将小鸡拖入 测试_小鸡NPC 场景中,并运行观察;
小鸡每5秒切换一次状态,测试完成!
(二)自动导航
第一步,我们请小鸡上车,为小鸡添加 NavigationAgent2D 节点;
第二步,选中 NavigationAgent2D 节点,在检查器中启用调试,使导航路径可视;
第三步,更新小鸡节点下的脚本;
extends CharacterBody2D
@export var 等待时间: float = 5.0
@export var 最小速度: float = 10.0
@export var 最大速度: float = 15.0
@export var 最小行走次数: int = 1
@export var 最大行走次数: int = 3
var 当前状态: StringName = "空闲"
var 速度: float = 0.0
var 剩余行走次数: int = 0
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var timer: Timer = $Timer
@onready var navigation_agent_2d: NavigationAgent2D = $NavigationAgent2D
func _ready():
# 配置定时器
timer.timeout.connect(切换状态)
func _physics_process(delta):
# 每完成一次导航,剩余行走次数减一,重新设置新的导航目标
if 当前状态 == "行走":
if 剩余行走次数 > 0:
有向速度计算()
# 导航结束后重新设置导航目标
if navigation_agent_2d.is_navigation_finished():
剩余行走次数 -= 1
设置导航目标()
else:
# 行走次数为0,切换空闲状态
切换状态()
move_and_slide()
# 空闲/行走状态切换
func 切换状态() ->void:
# 切换状态
if 当前状态 == "空闲":
# 空闲->行走
当前状态 = "行走"
animated_sprite_2d.play("行走")
剩余行走次数 = randi_range(最小行走次数, 最大行走次数)
设置导航目标()
else:
# 行走->空闲
当前状态 = "空闲"
animated_sprite_2d.play("空闲")
navigation_agent_2d.velocity = Vector2.ZERO
timer.start(等待时间)# 只在空闲时启动计时器
func 设置导航目标() ->void:
# 获取地图随机目标点
var 地图 = navigation_agent_2d.get_navigation_map()
var 导航层 = navigation_agent_2d.navigation_layers
var 目标点 : Vector2 = NavigationServer2D.map_get_random_point(地图, 导航层, false)
# 配置导航代理
navigation_agent_2d.target_position = 目标点
# 设定该次导航速度数值大小
速度 = randf_range(最小速度, 最大速度)
func 有向速度计算() ->void:
var 目标 = navigation_agent_2d.get_next_path_position()
var 方向 = global_position.direction_to(目标)
var 有向速度: Vector2 = 速度 * 方向
# 根据导航速度水平翻转动画
animated_sprite_2d.flip_h = 方向.x < 0
velocity = 有向速度
第四步,切换到 测试_小鸡NPC 场景,添加 NavigationRegion2D 节点;
第五步,选中 NavigationRegion2D 节点,在检查器中新建 NavigationPolygon ;
第六步,在场景中尝试绘制一块限制区域,首尾相连后烘培;
第七步,在上方调试栏勾选 显示导航 后运行测试;
小鸡自动导航测试完成!
四、让小鸡避障
(一)更新小鸡脚本
小鸡告诉自动驾驶汽车(导航代理),要去哪里(target_position),以什么速度(velocity)去,再由自动驾驶汽车上传给人工智能(导航服务),人工智能计算出安全速度,帮助小鸡避障:
extends CharacterBody2D
@export var 等待时间: float = 5.0
@export var 最小速度: float = 10.0
@export var 最大速度: float = 15.0
@export var 最小行走次数: int = 1
@export var 最大行走次数: int = 3
var 当前状态: StringName = "空闲"
var 速度: float = 0.0
var 剩余行走次数: int = 0
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var timer: Timer = $Timer
@onready var navigation_agent_2d: NavigationAgent2D = $NavigationAgent2D
func _ready():
# 配置定时器
timer.timeout.connect(切换状态)
# 默认关闭避障
navigation_agent_2d.avoidance_enabled = false
# 计算避障速度
navigation_agent_2d.velocity_computed.connect(避障)
# 设置代理避障最大速度
navigation_agent_2d.set_max_speed(10)
func _physics_process(delta):
# 每完成一次导航,剩余行走次数减一,重新设置新的导航目标
if 当前状态 == "行走":
if 剩余行走次数 > 0:
#设置代理速度,用于安全速度计算
设置代理速度()
# 导航结束后重新设置导航目标
if navigation_agent_2d.is_navigation_finished():
剩余行走次数 -= 1
设置导航目标()
else:
# 行走次数为0,切换空闲状态
切换状态()
# 空闲/行走状态切换
func 切换状态() ->void:
# 切换状态
if 当前状态 == "空闲":
# 空闲->行走
当前状态 = "行走"
animated_sprite_2d.play("行走")
剩余行走次数 = randi_range(最小行走次数, 最大行走次数)
# 打开避障
navigation_agent_2d.avoidance_enabled = true
设置导航目标()
else:
# 行走->空闲
当前状态 = "空闲"
animated_sprite_2d.play("空闲")
navigation_agent_2d.velocity = Vector2.ZERO
# 关闭避障
navigation_agent_2d.avoidance_enabled = false
timer.start(等待时间)# 只在空闲时启动计时器
func 设置导航目标() ->void:
# 获取地图随机目标点
var 地图 = navigation_agent_2d.get_navigation_map()
var 导航层 = navigation_agent_2d.navigation_layers
var 目标点 : Vector2 = NavigationServer2D.map_get_random_point(地图, 导航层, false)
# 配置导航代理
navigation_agent_2d.target_position = 目标点
# 设定该次导航速度数值大小
速度 = randf_range(最小速度, 最大速度)
func 设置代理速度() ->void:
# 根据当前位置到目标位置的向量计算速度
var 目标 = navigation_agent_2d.get_next_path_position()
var 方向 = global_position.direction_to(目标)
var 有向速度: Vector2 = 速度 * 方向
if navigation_agent_2d.avoidance_enabled:
# 根据导航速度水平翻转动画
animated_sprite_2d.flip_h = 有向速度.x < 0
navigation_agent_2d.set_velocity(有向速度)
# 预设置 navigation_agent_2d 的 target_position 和 velocity 才可以计算【安全速度】
func 避障(安全速度: Vector2) -> void:
# 避障安全速度
velocity = 安全速度
# 根据安全速度水平翻转动画
animated_sprite_2d.flip_h = velocity.x < 0
move_and_slide()
(二)注意事项
- 如果使用的精灵图大小与本文所用不同,需要自行调整 NavigationAgent2D 参数以适用;
- 角色的碰撞体的大小和避障不存在任何关系!有需要请更改 NavigationAgent2D 中的避障半径;
- 避障只检测有 NavigationAgent2D 作为子节点的角色;
- 如果想做用单元格寻路的游戏,请检索 Astar2D ,AstarGrid2D 节点;
(三)运行测试
测试完成!
五、免费开源资产包
某开源网站精灵图资源包链接: 点击此处
-
进入链接后点击下图按钮
-
然后点击【No thanks,just take me to the downloads】(不了谢谢,只想下载)
-
最后点击下图按钮完成下载(注意导入前需解压缩)