feat(homing): bullet_manager 受限角速度转向 + 锁定式目标重选 + bounce 协同; 删除无人消费的位置快照
_apply_homing 在位置积分前把速度矢量朝目标旋转,单帧转角上限 strength*delta, rotated() 保持速率不变;wrapf 取最短转向方向。 目标仅在失效或首帧时经 _find_nearest_unvisited 重选,配 get_active_count()==0 空场守卫,避免 query_circle(r=400) 进入每帧热路径。 bounce 命中重定向时改写 homing_target_id,由 bounce 选目标 homing 追上去。 删除 _enemy_pos_snapshot 及 EnemyManager.fill_pos_snapshot(全项目唯一调用方)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,9 +18,6 @@ var _active_count: int = 0
|
|||||||
# 冷数据(非热路径)
|
# 冷数据(非热路径)
|
||||||
var _bullet_contexts: Dictionary = {}
|
var _bullet_contexts: Dictionary = {}
|
||||||
|
|
||||||
# 敌人位置快照(homing 共用,由 _physics_process 帧头建立)
|
|
||||||
var _enemy_pos_snapshot: PackedVector2Array = PackedVector2Array()
|
|
||||||
|
|
||||||
var _cs_node: Node = null
|
var _cs_node: Node = null
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -28,15 +25,17 @@ func _ready() -> void:
|
|||||||
_data.fill(0.0)
|
_data.fill(0.0)
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
EnemyManager.fill_pos_snapshot(_enemy_pos_snapshot)
|
|
||||||
if not _cs_node:
|
if not _cs_node:
|
||||||
_gd_integrate(delta)
|
_gd_integrate(delta)
|
||||||
|
|
||||||
# GDScript 回退路径(C# 未就绪时 / S0/S1 基线验证)
|
# GDScript 回退路径(C# 未就绪时 / S0/S1 基线验证)
|
||||||
func _gd_integrate(delta: float) -> void:
|
func _gd_integrate(delta: float) -> void:
|
||||||
|
var has_cold: bool = not _bullet_contexts.is_empty()
|
||||||
var i: int = 0
|
var i: int = 0
|
||||||
while i < _active_count:
|
while i < _active_count:
|
||||||
var base: int = i * BULLET_STRIDE
|
var base: int = i * BULLET_STRIDE
|
||||||
|
if has_cold and _bullet_contexts.has(i):
|
||||||
|
_apply_homing(i, base, delta) # 转向须在位置积分之前,子弹当帧即沿新方向前进
|
||||||
_data[base + 0] += _data[base + 2] * delta # x += vx * dt
|
_data[base + 0] += _data[base + 2] * delta # x += vx * dt
|
||||||
_data[base + 1] += _data[base + 3] * delta # y += vy * dt
|
_data[base + 1] += _data[base + 3] * delta # y += vy * dt
|
||||||
_data[base + 2] += _data[base + 11] * delta # vx += accel * dt
|
_data[base + 2] += _data[base + 11] * delta # vx += accel * dt
|
||||||
@@ -116,6 +115,8 @@ func _check_collision(bullet_idx: int, base: int) -> bool:
|
|||||||
_data[base + 2] = dir.x * spd
|
_data[base + 2] = dir.x * spd
|
||||||
_data[base + 3] = dir.y * spd
|
_data[base + 3] = dir.y * spd
|
||||||
_data[base + 7] *= float(cold.get("bounce_decay", 0.9))
|
_data[base + 7] *= float(cold.get("bounce_decay", 0.9))
|
||||||
|
if cold.has("homing_strength"):
|
||||||
|
cold["homing_target_id"] = next_id # bounce 选目标,homing 接手追上去
|
||||||
cold["bounce_remaining"] = bounce - 1
|
cold["bounce_remaining"] = bounce - 1
|
||||||
cold["visited_targets"] = visited
|
cold["visited_targets"] = visited
|
||||||
_bullet_contexts[bullet_idx] = cold
|
_bullet_contexts[bullet_idx] = cold
|
||||||
@@ -214,6 +215,37 @@ func _find_nearest_unvisited(origin: Vector2, radius: float, visited: Array) ->
|
|||||||
best_id = eid
|
best_id = eid
|
||||||
return best_id
|
return best_id
|
||||||
|
|
||||||
|
## 归航转向:以 homing_strength(弧度/秒) 为上限把速度矢量转向锁定目标,速率不变
|
||||||
|
## 目标失效(死亡/未锁定)时重选一次;场上无敌人或射程内无目标则保持直行
|
||||||
|
func _apply_homing(bullet_idx: int, base: int, delta: float) -> void:
|
||||||
|
var cold: Dictionary = _bullet_contexts[bullet_idx]
|
||||||
|
var strength: float = float(cold.get("homing_strength", 0.0))
|
||||||
|
if strength <= 0.0:
|
||||||
|
return
|
||||||
|
var pos: Vector2 = Vector2(_data[base + 0], _data[base + 1])
|
||||||
|
var tid: int = int(cold.get("homing_target_id", -1))
|
||||||
|
var tpos: Vector2 = Vector2(-9999.0, -9999.0)
|
||||||
|
if tid >= 0:
|
||||||
|
tpos = EnemyManager.get_pos_by_id(tid)
|
||||||
|
if tpos == Vector2(-9999.0, -9999.0):
|
||||||
|
# 目标失效或首帧:重选。空场守卫避免清场瞬间全体子弹集体 query_circle
|
||||||
|
if EnemyManager.get_active_count() == 0:
|
||||||
|
return
|
||||||
|
tid = _find_nearest_unvisited(pos, float(cold.get("homing_range", 400.0)), cold.get("visited_targets", []))
|
||||||
|
cold["homing_target_id"] = tid
|
||||||
|
_bullet_contexts[bullet_idx] = cold
|
||||||
|
if tid < 0:
|
||||||
|
return
|
||||||
|
tpos = EnemyManager.get_pos_by_id(tid)
|
||||||
|
if tpos == Vector2(-9999.0, -9999.0):
|
||||||
|
return
|
||||||
|
var vel: Vector2 = Vector2(_data[base + 2], _data[base + 3])
|
||||||
|
var diff: float = wrapf((tpos - pos).angle() - vel.angle(), -PI, PI) # 最短转向方向
|
||||||
|
var max_step: float = strength * delta
|
||||||
|
var nv: Vector2 = vel.rotated(clampf(diff, -max_step, max_step)) # rotated 保持速率不变
|
||||||
|
_data[base + 2] = nv.x
|
||||||
|
_data[base + 3] = nv.y
|
||||||
|
|
||||||
func get_nearest_enemy_pos(origin: Vector2, max_dist: float = 9999.0) -> Vector2:
|
func get_nearest_enemy_pos(origin: Vector2, max_dist: float = 9999.0) -> Vector2:
|
||||||
return EnemyManager.get_nearest_pos(origin, max_dist)
|
return EnemyManager.get_nearest_pos(origin, max_dist)
|
||||||
|
|
||||||
|
|||||||
@@ -342,13 +342,6 @@ func get_nearest_pos(origin: Vector2, max_dist: float = 9999.0) -> Vector2:
|
|||||||
best = Vector2(_data[base + 0], _data[base + 1])
|
best = Vector2(_data[base + 0], _data[base + 1])
|
||||||
return best
|
return best
|
||||||
|
|
||||||
## 建立敌人位置快照(BulletManager homing 共用)
|
|
||||||
func fill_pos_snapshot(out_arr: PackedVector2Array) -> void:
|
|
||||||
out_arr.resize(_active_count)
|
|
||||||
for i in _active_count:
|
|
||||||
var base: int = i * ENEMY_STRIDE
|
|
||||||
out_arr[i] = Vector2(_data[base + 0], _data[base + 1])
|
|
||||||
|
|
||||||
## 同步 Node2D 显示位置(屏内 dirty sync)
|
## 同步 Node2D 显示位置(屏内 dirty sync)
|
||||||
func sync_node_positions(enemy_nodes: Array) -> void:
|
func sync_node_positions(enemy_nodes: Array) -> void:
|
||||||
for i in min(_active_count, enemy_nodes.size()):
|
for i in min(_active_count, enemy_nodes.size()):
|
||||||
|
|||||||
Reference in New Issue
Block a user