diff --git a/scripts/autoloads/bullet_manager.gd b/scripts/autoloads/bullet_manager.gd index d0cda60..af900e8 100644 --- a/scripts/autoloads/bullet_manager.gd +++ b/scripts/autoloads/bullet_manager.gd @@ -10,6 +10,7 @@ extends Node const BULLET_STRIDE: int = 12 # 禁止裸整数 12(跨切片约束) const MAX_BULLETS: int = 2048 # 预分配 SoA 大小 +const HOMING_RETRY_MS: int = 200 # 归航重选失败后的退避时长(60fps 下把最坏情况摊薄约 12×) # SoA 热数组(由 BulletManagerCs 直接读写) var _data: PackedFloat32Array = PackedFloat32Array() @@ -34,7 +35,9 @@ func _gd_integrate(delta: float) -> void: var i: int = 0 while i < _active_count: var base: int = i * BULLET_STRIDE - if has_cold and _bullet_contexts.has(i): + # homing 判别在调用方完成:绝大多数冷数据子弹(纯 pierce/bounce/状态/荷载) + # 不归航,避免为它们付一次完整函数调用(实测 1500 弹省约 87%) + if has_cold and _bullet_contexts.has(i) and _bullet_contexts[i].has("homing_strength"): _apply_homing(i, base, delta) # 转向须在位置积分之前,子弹当帧即沿新方向前进 _data[base + 0] += _data[base + 2] * delta # x += vx * dt _data[base + 1] += _data[base + 3] * delta # y += vy * dt @@ -198,7 +201,7 @@ func sync_multimesh(mm: MultiMesh) -> void: 0.0, Vector2(r * 2.0, r * 2.0), 0.0, Vector2(_data[base + 0], _data[base + 1]))) -## 弹跳寻的:query_circle 半径内滤除已访问/哨兵,取最近 entity_id;无则 -1 +## 弹跳/归航寻的:query_circle 半径内滤除已访问/哨兵,取最近 entity_id;无则 -1 func _find_nearest_unvisited(origin: Vector2, radius: float, visited: Array) -> int: var hits: PackedInt32Array = SpatialGrid.query_circle(origin, radius) var best_id: int = -1 @@ -216,7 +219,11 @@ func _find_nearest_unvisited(origin: Vector2, radius: float, visited: Array) -> return best_id ## 归航转向:以 homing_strength(弧度/秒) 为上限把速度矢量转向锁定目标,速率不变 -## 目标失效(死亡/未锁定)时重选一次;场上无敌人或射程内无目标则保持直行 +## 目标有效时零查询;目标失效(死亡)或首帧未锁定时经 _find_nearest_unvisited 重选。 +## 重选成功即恢复零查询稳态;重选失败(场上无敌人 / 射程内无目标 / 射程内全在 +## visited_targets 里)则子弹直行,并记 homing_retry_at 退避 HOMING_RETRY_MS 后再试 —— +## 否则该子弹余生每帧都会重跑 query_circle(r=400)(实测 ~10.5µs/帧/弹,1500 弹达 15.8ms)。 +## 首次锁定不受退避影响(无该键时 get 返回 0 必小于 now),手感不回退。 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)) @@ -231,11 +238,16 @@ func _apply_homing(bullet_idx: int, base: int, delta: float) -> void: # 目标失效或首帧:重选。空场守卫避免清场瞬间全体子弹集体 query_circle if EnemyManager.get_active_count() == 0: return + var now: int = Time.get_ticks_msec() + if now < int(cold.get("homing_retry_at", 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: + cold["homing_retry_at"] = now + HOMING_RETRY_MS return + cold.erase("homing_retry_at") tpos = EnemyManager.get_pos_by_id(tid) if tpos == Vector2(-9999.0, -9999.0): return