其实,游戏中的寻路方法非常之多,我所见到过的就有好几种,这些方法有难有易,具体实现机制见仁见智,我现在将自己熟悉的几种方式写出来,比较其优缺点,并和大家一起讨论讨论,如何避免下图中的尴尬。当然,如果您有其他更好的方法请务必留言告诉我,非常感谢!
func _findMoveDirection(delta : float, target : Node2D) -> Vector2: var dir := Vector2.ZERO if _navigation == null: return dir # 使用导航的方法找出可行路径 var path := _navigation.get_simple_path(self.position, target.position) _path = path # 注意:第一个点可能是AI自身所在点,这时候会返回 Vector2.ZERO 导致不移动 while dir == Vector2.ZERO && ! path.empty(): dir = (path[0] - self.position).normalized() path.remove(0) return dir
# 射线类,检测玩家是否可以移动的射线,用于记录射线状态 # 比重越高,选择该射线方向进行移动的可能性越大 class Ray: var length := 0.0 # 长度 var dir := Vector2.ZERO # 方向 var canMove := true # 玩家是否可以移动 var playerWeight := 0.0 # 相对于玩家的比重 var moveonWeight := 0.0 # 相对当前移动方向的比重
# 查找可行的移动方向,父类方法 func _findMoveDirection(delta : float, target : Node2D) -> Vector2: var vector := target.global_position - self.global_position var dir := vector.normalized() var length := vector.length() _updateRays(delta, vector, _currentRay.dir) # 更新射线当前帧状态 _findRayDirection() # 在更新状态后找出合适的方向 return _currentRay.dir if _currentRay else Vector2.ZERO # 更新射线碰撞状态、射线比重 func _updateRays(delta : float, targetDir : Vector2, moveDir : Vector2) -> void: # 获取 world space state 用于发射射线 var state := self.get_world_2d().direct_space_state for ray in _rays: # 使用 world space state 发射射线检测是否碰撞 var collision := state.intersect_ray(self.global_position, self.global_position + ray.dir * ray.length, [], 0x1) if collision: ray.canMove = false else: # 射线没有碰撞前提下测试该射线方向是否可以移动 ray.canMove = ! self.test_move(self.global_transform, self.moveSpeed * delta * ray.dir) # 射线的玩家比重为:方向向量点乘玩家方向向量 ray.playerWeight = targetDir.dot(ray.dir) # 射线的移动比重为:方向向量点乘当前移动方向向量 ray.moveonWeight = moveDir.dot(ray.dir) # 查询合适的用于跟踪移动的射线 func _findRayDirection() -> void: var raysSameSide := [] # 与当前移动方向角度不大于90度的无碰撞射线集合 var raysOtherSide := [] # 与当前移动方向角度超过90度的无碰撞射线集合 for ray in _rays: if ray.canMove && ray.dir.dot(_currentRay.dir) > 0: raysSameSide.append(ray) elif ray.canMove: raysOtherSide.append(ray) # 当前射线没有发生碰撞则找出与玩家方向最合适的射线 if _currentRay.canMove: for ray in _rays: if ray.canMove && ray.dir.dot(_currentRay.dir) > 0: raysSameSide.append(ray) for ray in raysSameSide: if ray.playerWeight >= _currentRay.playerWeight: _currentRay = ray # 当前射线发生碰撞或者不能移动,找出能移动的合适射线 else: var newRay : Ray = _currentRay # 优先检测同一方向的射线 if ! raysSameSide.empty(): newRay = raysSameSide[0] for ray in raysSameSide: if ray.moveonWeight > newRay.moveonWeight: newRay = ray # 如果同一方向的射线全部发生碰撞,则检测另一方向 elif ! raysOtherSide.empty(): newRay = raysOtherSide[0] for ray in raysOtherSide: if ray.playerWeight > newRay.playerWeight: newRay = ray _currentRay = newRay
export(float, 0.0, 10.0) var recordTimeInterval = 0.1 # 记录跟踪目标位置的时间间隔 export(int, 1, 100) var maxTargetPositionRecords = 8 # 记录位置点的最大数量 export(float, 0.0, 100.0) var minDistanceToRecord = 1.0 # 允许记录位置距离上一点的最小距离 onready var _raycastTarget = $RayCastTarget as RayCast2D # 直接指向目标的检测射线 onready var _raycastStatic = $RayCastStatic as RayCast2D # 指向记录下的目标移动点的射线 onready var _trackTimer := $TrackTimer as Timer # 跟踪记录位置计时器 var _trackPoints := [] # 跟踪目标的位置点集合 var _trackTarget : Node2D = null # 跟踪目标,也可以用父类中的 target.get_ref() 代替 func _findMoveDirection(delta: float, target : Node2D) -> Vector2: _trackTarget = target if _trackTimer.is_stopped(): # 开启记录计时器 _trackTimer.start() var dir := target.global_position - self.global_position # 更新射线的指向,强制更新检测结果,如果没有碰撞则优先按此方向移动 _raycastTarget.cast_to = dir _raycastTarget.force_raycast_update() # 如果AI与目标之间有碰撞或者不能移动,则开始检测记录下的目标行踪点数组 if _raycastTarget.is_colliding() && _raycastTarget.get_collider() != target || self.test_move(self.transform, moveSpeed * delta * dir.normalized()): # 循环遍历所有记录点,寻找可以移动的点 for point in _trackPoints: var newDir = point - self.global_position # 更新射线指向记录点,强制更新检测结果 _raycastStatic.cast_to = newDir _raycastStatic.force_raycast_update() # 如果指向该点的射线有发生碰撞,可以移动,那么按该方向移动 if ! _raycastStatic.is_colliding() && ! self.test_move(self.transform, moveSpeed * delta * newDir.normalized()): dir = newDir break return dir.normalized()