在 Stage_2 场景中,找到敌人生成区域:
World
├── EnemySpawns
│ └── EnemySpawnManager_Zone1 ← 选中这个
在 Inspector 面板中设置:
Enemy (分类)
├─ EnemyScene = res://scenes/actors/enemies/Enemy_A1_zhuA.tscn
├─ SpawnCount = 3
└─ SpawnInterval = 0.15Trigger (分类)
├─ TriggerArea = 指向节点或留空以自动创建
├─ AutoConfigureAssignedTriggerArea = true
├─ TriggerGroupName = "player"
├─ TriggerSize = (320, 180)
├─ TriggerCollisionLayer = 1
└─ TriggerCollisionMask = 1Spawn Placement (分类)
├─ UseExplicitSpawnOffsets = false # 使用随机范围
├─ SpawnAreaExtents = (96, 48)
├─ EnableSmartSpawnPlacement = true
├─ ObstacleCheckMask = 1 # Layer 1(家具层)
└─ MaxSpawnAttempts = 10Spawn FX (分类)
├─ SpawnBackEffectScene = res://scenes/actors/etc/enemy_spaw_back.tscn
├─ SpawnFrontEffectScene = res://scenes/actors/etc/enemy_spawn_front.tscn
├─ EnemyAppearDelay = 0.2
└─ EnemyAppearGateMode = Delay # 或 BackEffectFrame / BackEffectFinished显示调试信息:
Debug (分类)
├─ ShowDebugOverlay = true
└─ ShowDebugOverlayInGame = true
运行游戏,你会看到触发区域和生成点的可视化。
using Godot;
using Kuros.Controllers;
public partial class BattleStarter : Node
{
public override void _Ready()
{
var spawnManager = GetNode<EnemySpawnManager>("EnemySpawnManager_Zone1");
// 配置敌人
spawnManager.EnemyScene = GD.Load<PackedScene>("res://scenes/actors/enemies/Enemy_A1_zhuA.tscn");
spawnManager.SpawnCount = 3;
spawnManager.SpawnInterval = 0.15f;
// 配置触发
spawnManager.TriggerGroupName = "player";
spawnManager.TriggerSize = new Vector2(320, 180);
// 配置生成位置
spawnManager.SpawnAreaExtents = new Vector2(96, 48);
spawnManager.EnableSmartSpawnPlacement = true;
// 订阅完成事件
spawnManager.SpawnCompleted += OnAllEnemiesSpawned;
}
private void OnAllEnemiesSpawned()
{
GD.Print("所有敌人生成完毕,战斗开始!");
// 启动 BGM、禁用逃脱等
}
}public partial class MixedWaveSpawner : Node
{
public override void _Ready()
{
var spawnManager = GetNode<EnemySpawnManager>("MultiEnemySpawner");
// 设置多个敌人类型
spawnManager.EnemyScenes = new Godot.Collections.Array<PackedScene>
{
GD.Load<PackedScene>("res://scenes/actors/enemies/Enemy_A1_zhuA.tscn"),
GD.Load<PackedScene>("res://scenes/actors/enemies/Enemy_B1_Thin.tscn"),
GD.Load<PackedScene>("res://scenes/actors/enemies/Enemy_C1_WaiterA.tscn")
};
spawnManager.MultiEnemySelectionMode = EnemySpawnManager.EnemySelectionMode.Random;
spawnManager.SpawnCount = 5;
// 自动生成 (不需要触发)
spawnManager.SpawnOnReady = true;
}
}public partial class EnemyTracker : Node
{
private List<Node> _spawnedEnemies = new();
public override void _Ready()
{
var spawnManager = GetNode<EnemySpawnManager>("EnemySpawnManager");
spawnManager.EnemySpawned += OnEnemySpawned;
}
private void OnEnemySpawned(Node enemy, int index)
{
GD.Print($"敌人 #{index} 生成: {enemy.Name}");
_spawnedEnemies.Add(enemy);
// 可以在这里添加特殊处理
if (enemy is GameActor actor)
{
actor.TakeDamage += (amount) =>
{
GD.Print($"敌人受伤 {amount} HP");
};
}
}
}Scene Tree
├── CameraZoneManager ← 选中这个节点
Exported Properties
├─ TargetCamera = 指向 MainCharacter/Camera2D
├─ Player = 指向 MainCharacter (或留空自动查找)相机区域配置 (相机区域配置)
├─ Zone1_LimitLeft = -9300
├─ Zone1_LimitTop = -1500
├─ Zone1_LimitRight = -3650
└─ Zone1_LimitBottom = 1500├─ Zone2_LimitLeft = -3650
├─ Zone2_LimitTop = -1500
├─ Zone2_LimitRight = 5000
└─ Zone2_LimitBottom = 1500Area2D 节点路径 (Area2D 节点路径)
├─ Zone1AreaPath = "World/CameraZones/Zone1_Area2D"
└─ Zone2AreaPath = "World/CameraZones/Zone2_Area2D"using Godot;
using Kuros.Managers;
public partial class CameraSetup : Node
{
public override void _Ready()
{
var cameraZoneManager = GetNode<CameraZoneManager>("CameraZoneManager");
// 获取相机和玩家引用
var camera = GetNode<Camera2D>("World/MainCharacter/Camera2D");
var player = GetNode<Node2D>("World/MainCharacter");
cameraZoneManager.TargetCamera = camera;
cameraZoneManager.Player = player;
// 手动初始化后调用 _Ready() 中的逻辑
// (通常不需要,编辑器会自动调用)
}
}public partial class DynamicZoneManager : Node
{
private CameraZoneManager _cameraZoneManager;
public override void _Ready()
{
_cameraZoneManager = GetNode<CameraZoneManager>("CameraZoneManager");
}
public void AddNewZone(string zoneName,
Vector2 limitTopLeft, Vector2 limitBottomRight)
{
var newZone = new CameraZoneManager.CameraZone
{
Name = zoneName,
LimitLeft = (int)limitTopLeft.X,
LimitTop = (int)limitTopLeft.Y,
LimitRight = (int)limitBottomRight.X,
LimitBottom = (int)limitBottomRight.Y
};
// 如果实现了 AddZone 方法
// _cameraZoneManager.AddZone(newZone);
GD.Print($"新增相机区域: {zoneName}");
}
public void ManualSwitchZone(int zoneIndex)
{
_cameraZoneManager.SwitchToZone(zoneIndex);
}
public void PrintDebugInfo()
{
GD.Print(_cameraZoneManager.GetDebugInfo());
}
}在项目中创建 scripts/core/PhysicsConstants.cs:
namespace Kuros.Core
{
/// <summary>
/// 物理碰撞层定义
/// </summary>
public static class PhysicsLayers
{
public const uint LAYER_PLAYER = 0; // 1 << 0 = 1
public const uint LAYER_FURNITURE = 1; // 1 << 1 = 2
public const uint LAYER_ENEMY = 2; // 1 << 2 = 4
public const uint LAYER_ATTACK_AREA = 3; // 1 << 3 = 8
public const uint LAYER_PICKUP_AREA = 4; // 1 << 4 = 16
public const uint LAYER_PROJECTILE = 5; // 1 << 5 = 32
// 掩码组合 (用于检测)
public const uint MASK_PLAYER = 1u << 0;
public const uint MASK_FURNITURE = 1u << 1;
public const uint MASK_ENEMY = 1u << 2;
public const uint MASK_ATTACK_AREA = 1u << 3;
public const uint MASK_PICKUP_AREA = 1u << 4;
public const uint MASK_PROJECTILE = 1u << 5;
}
}public partial class AttackAreaSetup : Node
{
public override void _Ready()
{
var attackArea = GetNode<Area2D>("AttackArea");
// 设置不在任何物理层(纯检测)
attackArea.CollisionLayer = 0;
// 检测敌人层
attackArea.CollisionMask = PhysicsLayers.MASK_ENEMY;
GD.Print($"AttackArea 设置完成: Layer={attackArea.CollisionLayer}, Mask={attackArea.CollisionMask}");
}
}public partial class EnemySetup : GameActor
{
public override void _Ready()
{
base._Ready();
// 敌人身体应该在敌人层
CollisionLayer = PhysicsLayers.LAYER_ENEMY;
// 敌人可以检测玩家和敌人(用于拥挤)
CollisionMask = PhysicsLayers.MASK_PLAYER
| PhysicsLayers.MASK_FURNITURE;
GD.Print($"敌人碰撞配置: Layer={CollisionLayer}, Mask={CollisionMask}");
}
}public partial class CollisionDebugger : Node
{
public override void _PhysicsProcess(double delta)
{
if (Input.IsActionJustPressed("ui_accept"))
{
var player = GetTree().GetFirstNodeInGroup("player") as Node2D;
if (player is CharacterBody2D charBody)
{
GD.Print($"玩家碰撞配置:");
GD.Print($" Layer: {charBody.CollisionLayer} (binary: {ToBinary(charBody.CollisionLayer)})");
GD.Print($" Mask: {charBody.CollisionMask} (binary: {ToBinary(charBody.CollisionMask)})");
}
var enemies = GetTree().GetNodesInGroup("enemy");
foreach (var enemy in enemies)
{
if (enemy is CharacterBody2D enemyBody)
{
GD.Print($"敌人 {enemy.Name} 碰撞配置:");
GD.Print($" Layer: {enemyBody.CollisionLayer}");
GD.Print($" Mask: {enemyBody.CollisionMask}");
}
}
}
}
private string ToBinary(uint value)
{
return System.Convert.ToString(value, 2).PadLeft(32, '0');
}
}创建 scripts/core/StageInitializer.cs:
using Godot;
using Kuros.Controllers;
using Kuros.Managers;
using System.Collections.Generic;
namespace Kuros.Core
{
/// <summary>
/// 完整关卡初始化器 - 整合敌人生成、摄像头、碰撞系统
/// </summary>
public partial class StageInitializer : Node
{
private CameraZoneManager _cameraZoneManager;
private List<EnemySpawnManager> _spawnManagers = new();
private Dictionary<string, int> _zoneEnemyCounts = new();
public override void _Ready()
{
GD.Print("=== 开始初始化关卡 ===");
InitializeCameraSystem();
InitializeEnemySpawners();
InitializeCollisionLayers();
GD.Print("=== 关卡初始化完成 ===");
}
/// <summary>
/// 初始化摄像头多区域系统
/// </summary>
private void InitializeCameraSystem()
{
_cameraZoneManager = GetNode<CameraZoneManager>("CameraZoneManager");
if (_cameraZoneManager == null)
{
GD.PushError("无法找到 CameraZoneManager!");
return;
}
var camera = GetNode<Camera2D>("World/MainCharacter/Camera2D");
var player = GetNode<Node2D>("World/MainCharacter");
_cameraZoneManager.TargetCamera = camera;
_cameraZoneManager.Player = player;
GD.Print($"✓ 摄像头系统已初始化,共 2 个相机区域");
}
/// <summary>
/// 初始化所有敌人生成管理器
/// </summary>
private void InitializeEnemySpawners()
{
var spawnContainer = GetNode("World/EnemySpawns");
foreach (var child in spawnContainer.GetChildren())
{
if (child is EnemySpawnManager spawnManager)
{
InitializeSpawner(spawnManager);
_spawnManagers.Add(spawnManager);
}
}
GD.Print($"✓ 敌人生成系统已初始化,共 {_spawnManagers.Count} 个生成管理器");
}
/// <summary>
/// 配置单个生成管理器
/// </summary>
private void InitializeSpawner(EnemySpawnManager spawnManager)
{
spawnManager.SpawnStarted += () =>
{
GD.Print($"[{spawnManager.Name}] 敌人波次开始生成");
};
spawnManager.EnemySpawned += (enemy, index) =>
{
GD.Print($"[{spawnManager.Name}] 敌人 #{index} 已生成: {enemy.Name}");
};
spawnManager.SpawnCompleted += () =>
{
GD.Print($"[{spawnManager.Name}] 敌人波次生成完成 ({spawnManager.SpawnCount} 个)");
OnWaveCompleted(spawnManager.Name);
};
}
/// <summary>
/// 初始化碰撞层
/// </summary>
private void InitializeCollisionLayers()
{
// 配置玩家
var player = GetNode<CharacterBody2D>("World/MainCharacter");
player.CollisionLayer = PhysicsLayers.LAYER_PLAYER;
player.CollisionMask = PhysicsLayers.MASK_FURNITURE
| PhysicsLayers.MASK_ENEMY;
// 配置敌人
var enemySpawns = GetNode("World/EnemySpawns");
foreach (var spawner in enemySpawns.GetChildren())
{
if (spawner is EnemySpawnManager esm)
{
esm.ObstacleCheckMask = PhysicsLayers.MASK_FURNITURE;
}
}
GD.Print("✓ 碰撞层已初始化");
}
/// <summary>
/// 敌人波次完成回调
/// </summary>
private void OnWaveCompleted(string spawnerName)
{
_zoneEnemyCounts[spawnerName] = 1;
// 检查所有波次是否完成
int completedWaves = 0;
foreach (var spawner in _spawnManagers)
{
if (_zoneEnemyCounts.ContainsKey(spawner.Name))
completedWaves++;
}
if (completedWaves == _spawnManagers.Count)
{
GD.Print(">>> 所有敌人波次已完成,战斗阶段开始!");
OnAllWavesCompleted();
}
}
/// <summary>
/// 所有敌人波次完成
/// </summary>
private void OnAllWavesCompleted()
{
// 启动音乐、禁用逃脱、启动战斗逻辑等
GD.Print("触发战斗开始事件...");
}
}
}1. 选中 Stage_2 (根节点)
2. 添加子节点 Node,命名为 "StageInitializer"
3. 附加脚本: StageInitializer.cs
4. 确保节点路径正确
5. 运行游戏
public partial class DebugEnableScript : Node
{
public override void _Ready()
{
// 启用 EnemySpawnManager 的详细日志
var spawnManager = GetNode<EnemySpawnManager>("EnemySpawnManager");
spawnManager.LogSpawnEffectPositions = true;
spawnManager.ShowDebugOverlayInGame = true;
GD.Print("调试模式已启用");
}
}public override void _PhysicsProcess(double delta)
{
if (Input.IsActionJustPressed("ui_accept"))
{
var enemies = GetTree().GetNodesInGroup("enemy");
GD.Print($"当前场景敌人数量: {enemies.Count}");
foreach (var enemy in enemies)
{
if (enemy is GameActor actor)
{
GD.Print($" - {actor.Name}: HP={actor.CurrentHealth}/{actor.MaxHealth}");
}
}
}
}var camera = GetNode<Camera2D>("Camera2D");
GD.Print($"相机限制: Left={camera.LimitLeft}, Right={camera.LimitRight}, Top={camera.LimitTop}, Bottom={camera.LimitBottom}");
GD.Print($"相机位置: {camera.GlobalPosition}");
GD.Print($"相机缩放: {camera.Zoom}");指南完成
最后更新:2026-04-30