一、原视频
- Preventing Invalid Spawning
二、原代码
python
func get_spawn_position():
var player = get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return Vector2.ZERO
var spawn_position = Vector2.ZERO
var random_direction = Vector2.RIGHT.rotated(randf_range(0, TAU))
for i in 4:
spawn_position = player.global_position + ( random_direction * SPAWN_RADIUS )
var query_parameters = PhysicsRayQueryParameters2D.create(player.global_position, spawn_position, 1)
var result: Dictionary = get_tree().root.world_2d.direct_space_state.intersect_ray(query_parameters)
if result.is_empty():
# 射线检测结果中没有碰撞
break
else:
# 射线检测结果中有碰撞
random_direction = random_direction.rotated(deg_to_rad(90))
return spawn_position
三、原视频未修复证据
https://www.bilibili.com/video/BV1s8411v7nE/?p=35&t=479
老鼠穿越岔劈了,给镶进墙里了,哈哈哈哈哈
四、修复代码
注:解决方法有很多,但是这个只会修改get_spawn_position本身,以免把项目改得乱糟糟
python
const ENEMY_SPAWN_WALL_CLEARANCE = 36 # 这是墙的厚度,让敌人生成时不要卡墙里面,直角边32和直角边16的斜边向上取整就是36
func get_spawn_position_v2():
var player = get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return Vector2.ZERO
var player_pos = player.global_position
var RayQueryParams2D = PhysicsRayQueryParameters2D
var spawn_position = Vector2.ZERO
var original_direction = Vector2.RIGHT.rotated(randf_range(0, TAU))
spawn_position = player_pos + (original_direction * SPAWN_RADIUS)
var original_query_parameters = RayQueryParams2D.create(player_pos, spawn_position, 1)
var original_raycast_result: Dictionary = get_tree().root.world_2d.direct_space_state.intersect_ray(original_query_parameters)
var direction_to_player_pos = (player_pos - spawn_position).normalized()
spawn_position = spawn_position + (direction_to_player_pos * ENEMY_SPAWN_WALL_CLEARANCE)
if not original_raycast_result.is_empty():
# 射线检测结果中有碰撞
# 遇到这种情况,说明SPAWN_RADIUS不适配你的围墙尺寸,(偏移90°, 180°, 270°)全都算完一起做比对
var wall_positions: Array[Vector2] = []
wall_positions.append(original_raycast_result["position"])
# 准备3个可能的方向(原始方向 + 90°, 180°, 270°)
var directions = [
original_direction.rotated(deg_to_rad(90)),
original_direction.rotated(deg_to_rad(180)),
original_direction.rotated(deg_to_rad(270))
]
# 对每个方向进行射线检测
for dir in directions:
var test_position = player_pos + (dir * SPAWN_RADIUS)
var query_parameters = RayQueryParams2D.create(player_pos, test_position, 1)
var raycast_result = get_tree().root.world_2d.direct_space_state.intersect_ray(query_parameters)
if raycast_result.is_empty():
wall_positions.append(test_position)
else:
wall_positions.append(raycast_result["position"])
# 找到距离玩家最远的点
var farthest_pos = wall_positions[0]
for pos in wall_positions:
if pos.distance_squared_to(player_pos) > farthest_pos.distance_squared_to(player_pos):
farthest_pos = pos
# 将这个点向玩家附近的重心点的方向移动36
# (该重心点受地图碰撞影响,不会紧贴地图边缘)
# (36是墙厚度,避免卡墙里)
var centroid_pos = calculate_centroid(player_pos, 1)
var direction_to_centroid_pos = (centroid_pos - farthest_pos).normalized()
spawn_position = farthest_pos + (direction_to_centroid_pos * ENEMY_SPAWN_WALL_CLEARANCE)
return spawn_position
# 给定坐标为中心,均匀向四周发出 9 条射线,计算这 9 个碰撞点的质心
func calculate_centroid(position: Vector2, mask: int) -> Vector2:
var points = []
var ray_length = 300
var angle_step = 2 * PI / 8 # 将 360 度(即 2π 弧度)均分为 8 份,得到每份的角度
for i in range(9):
var angle = i * angle_step
var ray_direction = Vector2.RIGHT.rotated(angle) # 生成射线方向
var ray_end = position + ray_direction * ray_length # 计算射线终点
var query_parameters = PhysicsRayQueryParameters2D.create(position, ray_end, mask)
var result = get_tree().root.world_2d.direct_space_state.intersect_ray(query_parameters)
if result.is_empty():
# 如果没有碰撞,添加射线终点
points.append(ray_end)
else:
points.append(result["position"]) # 添加碰撞点
# 计算质心
var centroid = Vector2.ZERO
for p in points:
centroid += p
centroid /= points.size()
return centroid
五、效果比对
<>
<>
<>
<>
<>