diff --git a/project.godot b/project.godot index 7e2def0..d83824c 100644 --- a/project.godot +++ b/project.godot @@ -55,6 +55,7 @@ SettingsManager="*res://scripts/autoloads/settings_manager.gd" AudioManager="*res://scripts/autoloads/audio_manager.gd" SceneManager="*res://scripts/autoloads/scene_manager.gd" MetaProgress="*res://scripts/autoloads/meta_progress.gd" +EnemyBulletManager="*res://scripts/autoloads/enemy_bullet_manager.gd" MCPScreenshot="*res://addons/godot_mcp/mcp_screenshot_service.gd" MCPInputService="*res://addons/godot_mcp/mcp_input_service.gd" MCPGameInspector="*res://addons/godot_mcp/mcp_game_inspector_service.gd" diff --git a/scripts/autoloads/enemy_bullet_manager.gd b/scripts/autoloads/enemy_bullet_manager.gd new file mode 100644 index 0000000..7dc90f6 --- /dev/null +++ b/scripts/autoloads/enemy_bullet_manager.gd @@ -0,0 +1,82 @@ +## EnemyBulletManager — 敌方子弹管理器 Autoload(伤害玩家) +## 平行于 BulletManager(玩家子弹 vs 敌人);本池每帧检测 vs 玩家。 +## SoA 布局(ENEMY_BULLET_STRIDE=7):[ x, y, vx, vy, lifetime, radius, damage ] +## 0 1 2 3 4 5 6 +extends Node + +const ENEMY_BULLET_STRIDE: int = 7 # 禁裸整数 +const MAX_ENEMY_BULLETS: int = 1024 +const PLAYER_HIT_RADIUS: float = 12.0 # 玩家碰撞半径估算(与玩家标记视觉一致) + +var _data: PackedFloat32Array = PackedFloat32Array() +var _active_count: int = 0 + +func _ready() -> void: + _data.resize(MAX_ENEMY_BULLETS * ENEMY_BULLET_STRIDE) + _data.fill(0.0) + +func _physics_process(delta: float) -> void: + var player: Vector2 = PlayerManager.get_position() + var i: int = 0 + while i < _active_count: + var base: int = i * ENEMY_BULLET_STRIDE + _data[base + 0] += _data[base + 2] * delta + _data[base + 1] += _data[base + 3] * delta + _data[base + 4] -= delta + if _data[base + 4] <= 0.0: + _swap_and_pop(i) + continue + var dx: float = _data[base + 0] - player.x + var dy: float = _data[base + 1] - player.y + var rr: float = _data[base + 5] + PLAYER_HIT_RADIUS + if dx * dx + dy * dy <= rr * rr: + PlayerStats.take_damage(_data[base + 6]) + VFXManager.play("hit_spark", Vector2(_data[base + 0], _data[base + 1])) + _swap_and_pop(i) + continue + i += 1 + +func _swap_and_pop(idx: int) -> void: + var last: int = _active_count - 1 + if idx != last: + var b_i: int = idx * ENEMY_BULLET_STRIDE + var b_l: int = last * ENEMY_BULLET_STRIDE + for s in ENEMY_BULLET_STRIDE: + _data[b_i + s] = _data[b_l + s] + _active_count -= 1 + +## 生成敌方子弹,返回 id(满容返回 -1) +func spawn_bullet(pos: Vector2, vel: Vector2, lifetime: float, radius: float, damage: float) -> int: + if _active_count >= MAX_ENEMY_BULLETS: + return -1 + var id: int = _active_count + var base: int = id * ENEMY_BULLET_STRIDE + _data[base + 0] = pos.x + _data[base + 1] = pos.y + _data[base + 2] = vel.x + _data[base + 3] = vel.y + _data[base + 4] = lifetime + _data[base + 5] = radius + _data[base + 6] = damage + _active_count += 1 + return id + +func get_active_count() -> int: + return _active_count + +## 渲染:SoA 位置 → MultiMesh(与 BulletManager.sync_multimesh 同法) +func sync_multimesh(mm: MultiMesh) -> void: + if mm == null: + return + var n: int = min(_active_count, mm.instance_count) + mm.visible_instance_count = n + for i in n: + var base: int = i * ENEMY_BULLET_STRIDE + var r: float = _data[base + 5] + mm.set_instance_transform_2d(i, Transform2D( + 0.0, Vector2(r * 2.0, r * 2.0), 0.0, + Vector2(_data[base + 0], _data[base + 1]))) + +func reset() -> void: + _active_count = 0 + _data.fill(0.0) diff --git a/scripts/autoloads/enemy_bullet_manager.gd.uid b/scripts/autoloads/enemy_bullet_manager.gd.uid new file mode 100644 index 0000000..a2a9ecb --- /dev/null +++ b/scripts/autoloads/enemy_bullet_manager.gd.uid @@ -0,0 +1 @@ +uid://ctot52kayppgs