From b3b04fcbdad0474ab5dcf408faf247ca3cb956d9 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 30 Jul 2026 15:40:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(homing):=20bullet=5Fmanager=20=E5=8F=97?= =?UTF-8?q?=E9=99=90=E8=A7=92=E9=80=9F=E5=BA=A6=E8=BD=AC=E5=90=91=20+=20?= =?UTF-8?q?=E9=94=81=E5=AE=9A=E5=BC=8F=E7=9B=AE=E6=A0=87=E9=87=8D=E9=80=89?= =?UTF-8?q?=20+=20bounce=20=E5=8D=8F=E5=90=8C;=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=B6=88=E8=B4=B9=E7=9A=84=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _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 --- scripts/autoloads/bullet_manager.gd | 40 ++++++++++++++++++++++++++--- scripts/autoloads/enemy_manager.gd | 7 ----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/scripts/autoloads/bullet_manager.gd b/scripts/autoloads/bullet_manager.gd index fe3bf75..d0cda60 100644 --- a/scripts/autoloads/bullet_manager.gd +++ b/scripts/autoloads/bullet_manager.gd @@ -18,9 +18,6 @@ var _active_count: int = 0 # 冷数据(非热路径) var _bullet_contexts: Dictionary = {} -# 敌人位置快照(homing 共用,由 _physics_process 帧头建立) -var _enemy_pos_snapshot: PackedVector2Array = PackedVector2Array() - var _cs_node: Node = null func _ready() -> void: @@ -28,15 +25,17 @@ func _ready() -> void: _data.fill(0.0) func _physics_process(delta: float) -> void: - EnemyManager.fill_pos_snapshot(_enemy_pos_snapshot) if not _cs_node: _gd_integrate(delta) # GDScript 回退路径(C# 未就绪时 / S0/S1 基线验证) func _gd_integrate(delta: float) -> void: + var has_cold: bool = not _bullet_contexts.is_empty() var i: int = 0 while i < _active_count: 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 + 1] += _data[base + 3] * delta # y += vy * 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 + 3] = dir.y * spd _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["visited_targets"] = visited _bullet_contexts[bullet_idx] = cold @@ -214,6 +215,37 @@ func _find_nearest_unvisited(origin: Vector2, radius: float, visited: Array) -> best_id = eid 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: return EnemyManager.get_nearest_pos(origin, max_dist) diff --git a/scripts/autoloads/enemy_manager.gd b/scripts/autoloads/enemy_manager.gd index 4a34811..e54e3f8 100644 --- a/scripts/autoloads/enemy_manager.gd +++ b/scripts/autoloads/enemy_manager.gd @@ -342,13 +342,6 @@ func get_nearest_pos(origin: Vector2, max_dist: float = 9999.0) -> Vector2: best = Vector2(_data[base + 0], _data[base + 1]) 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) func sync_node_positions(enemy_nodes: Array) -> void: for i in min(_active_count, enemy_nodes.size()):