Godot4.3类星露谷游戏开发之【小鸡NPC】

千里之行,始于足下

零、 笔记

创建可路径导航、动态避障的小鸡

一、认识导航节点

为了实现小鸡路径导航、动态避障的效果,要用到三种导航节点:

(一)智能驾驶汽车

装小鸡的智能驾驶汽车 -> NavigationAgent2D

(二)人工智能

汽车注册使用的人工智能,用于规划路径 -> NavigationServer2D

(三) 场地

导航区域,确定汽车行驶范围 -> NavigationRegion2D

二、创建小鸡

(一)创建小鸡动画

第一步,创建新场景,以 CharectorBody2D 作为场景根节点,改名为:小鸡

根节点
第二步,分别添加 AnimationSprite2D 动画节点和 CollisionShape2D 碰撞节点作为子节点;

子节点
第三步,选中 AnimationSprite2D 动画节点,在检查器中新建 SpriteFrames 动画帧;

SpriteFrames
第四步,在编辑器 SpriteFrames 底栏中使用精灵表创建动画帧;

创建动画帧
在文件系统 res://资产/Sprout Lands - Sprites - Basic pack/Characters 路径下打开 Free Chicken Sprites.png 文件夹;

打开Free Chicken Sprites.png
第五步,依图中顺序点击添加,创建动画(Tips:Ctrl + 鼠标滚轮 可以调整视图大小) ;

创建动画
第六步,设置为自动播放,调节帧速,并命名为 空闲

空闲
第七步,同上,设置如图,创建行走动画;

行走

(二)添加碰撞体

第一步,选中 CollisionShape2D 节点,并在检查器中新建 圆形碰撞

圆形碰撞
第二步,调整 圆形碰撞 至比小鸡略小;

比小鸡略小
第三步,新建【 res://场景/NPC/小鸡 】文件夹,并保存 小鸡 文件;

res://场景/NPC/小鸡
新建res://场景/NPC/小鸡文件夹
第四步,打开 项目设置 ,为2D物理层命名;

为物理层命名
第五步,分别设置小鸡和玩家的物理层;

设置小鸡和玩家的物理层

三、让小鸡动起来

(一)行走/空闲状态切换

在小鸡真正动起来前,给它一个计时器 Timer ,告诉它什么时候走,什么时候停:

行走/空闲状态切换
第一步,给小鸡一个计时器 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 ShotAutostart

检查器中设置

第四步,将 测试_基本地形 复制为 测试_小鸡NPC

测试_小鸡NPC
第五步,打开 测试_小鸡NPC 场景文件,按下图修改,以便于测试小鸡NPC;

修改
第六步,将小鸡拖入 测试_小鸡NPC 场景中,并运行观察;

运行观察
小鸡每5秒切换一次状态,测试完成!

(二)自动导航

第一步,我们请小鸡上车,为小鸡添加 NavigationAgent2D 节点;

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

第五步,选中 NavigationRegion2D 节点,在检查器中新建 NavigationPolygon

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 作为子节点的角色;
  • 如果想做用单元格寻路的游戏,请检索 Astar2DAstarGrid2D 节点;

(三)运行测试

避障测试
测试完成!

五、免费开源资产包

某开源网站精灵图资源包链接: 点击此处

  1. 进入链接后点击下图按钮
    下载

  2. 然后点击【No thanks,just take me to the downloads】(不了谢谢,只想下载)
    No thanks,just take me to the downloads

  3. 最后点击下图按钮完成下载(注意导入前需解压缩)
    下载