5. 关卡设计
2026/4/14大约 3 分钟
5. 关卡设计
5.1 横版关卡的结构
地下城与勇士的关卡是横向延伸的,玩家从左向右推进。每个关卡由多个战斗区域串联而成:
[入口] ──→ [区域A] ──→ [区域B] ──→ [区域C] ──→ [Boss房间]
小怪 精英怪 小怪+陷阱 Boss关卡的节点结构
Stage01 (Node2D)
├── Background (ParallaxBackground) # 视差滚动背景(多层)
│ ├── Layer_Far (ParallaxLayer) # 远景(云、山,移动最慢)
│ ├── Layer_Mid (ParallaxLayer) # 中景(树木、建筑)
│ └── Layer_Near (ParallaxLayer) # 近景(前景装饰)
├── TileMap (TileMapLayer) # 地形(地面、平台、墙壁)
├── BattleZones (Node2D) # 所有战斗区域
│ ├── ZoneA (BattleZone)
│ ├── ZoneB (BattleZone)
│ └── ZoneC (BattleZone)
├── BossRoom (BossZone) # Boss房间
├── Camera2D # 跟随摄像机
└── SpawnPoints (Node2D) # 玩家出生点5.2 战斗区域(BattleZone)
战斗区域是关卡的核心单元。进入后锁住玩家,消灭全部怪物后解锁:
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// 战斗区域——进入后锁定,消灭所有怪物后解锁
/// </summary>
public partial class BattleZone : Node2D
{
[Signal] public delegate void ZoneClearedEventHandler();
[Export] public int BaseEnemyCount { get; set; } = 5;
private readonly List<Node2D> _enemies = new();
private bool _isActive = false;
private bool _isCleared = false;
// 左右两端的屏障(消灭怪物前阻挡玩家)
[Export] public Node2D LeftBarrier { get; set; }
[Export] public Node2D RightBarrier { get; set; }
/// <summary>
/// 玩家进入区域时触发
/// </summary>
public void OnPlayerEntered()
{
if (_isCleared || _isActive) return;
_isActive = true;
ActivateBarriers();
SpawnEnemies();
}
/// <summary>
/// 激活屏障——锁住玩家
/// </summary>
private void ActivateBarriers()
{
LeftBarrier?.Show();
RightBarrier?.Show();
}
/// <summary>
/// 生成怪物(根据玩家人数动态调整数量)
/// </summary>
private void SpawnEnemies()
{
int count = DifficultyManager.Instance?.ScaleEnemyCount(BaseEnemyCount) ?? BaseEnemyCount;
// 实际项目:根据count实例化怪物场景,添加到场景树
GD.Print($"生成 {count} 只怪物");
}
/// <summary>
/// 怪物死亡时调用
/// </summary>
public void OnEnemyDied(Node2D enemy)
{
_enemies.Remove(enemy);
if (_enemies.Count == 0)
ClearZone();
}
/// <summary>
/// 区域清空
/// </summary>
private void ClearZone()
{
_isCleared = true;
LeftBarrier?.Hide();
RightBarrier?.Hide();
EmitSignal(SignalName.ZoneCleared);
}
}GDScript
extends Node2D
class_name BattleZone
## 战斗区域——进入后锁定,消灭所有怪物后解锁
signal zone_cleared
@export var base_enemy_count: int = 5
@export var left_barrier: Node2D
@export var right_barrier: Node2D
var _enemies: Array[Node2D] = []
var _is_active: bool = false
var _is_cleared: bool = false
## 玩家进入区域时触发
func on_player_entered() -> void:
if _is_cleared or _is_active:
return
_is_active = true
_activate_barriers()
_spawn_enemies()
## 激活屏障
func _activate_barriers() -> void:
if left_barrier:
left_barrier.show()
if right_barrier:
right_barrier.show()
## 生成怪物
func _spawn_enemies() -> void:
var count: int = base_enemy_count
if DifficultyManager.instance:
count = DifficultyManager.instance.scale_enemy_count(base_enemy_count)
print("生成 %d 只怪物" % count)
## 怪物死亡时调用
func on_enemy_died(enemy: Node2D) -> void:
_enemies.erase(enemy)
if _enemies.is_empty():
_clear_zone()
## 区域清空
func _clear_zone() -> void:
_is_cleared = true
if left_barrier:
left_barrier.hide()
if right_barrier:
right_barrier.hide()
zone_cleared.emit()5.3 摄像机跟随
摄像机需要跟随玩家移动,但同时限制在关卡边界内:
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// 跟随多个玩家的摄像机——保持所有玩家都在视野内
/// </summary>
public partial class StageCamera : Camera2D
{
[Export] public float FollowSpeed { get; set; } = 5f;
[Export] public float MinX { get; set; } = 0f;
[Export] public float MaxX { get; set; } = 5000f;
private List<Node2D> _targets = new();
public void AddTarget(Node2D target) => _targets.Add(target);
public void RemoveTarget(Node2D target) => _targets.Remove(target);
public override void _Process(double delta)
{
if (_targets.Count == 0) return;
// 计算所有玩家的中心点
var center = CalculateCenter();
// 限制在关卡边界内
center.X = Mathf.Clamp(center.X, MinX, MaxX);
// 平滑移动到目标位置
GlobalPosition = GlobalPosition.Lerp(center, FollowSpeed * (float)delta);
}
private Vector2 CalculateCenter()
{
var sum = Vector2.Zero;
foreach (var t in _targets)
sum += t.GlobalPosition;
return sum / _targets.Count;
}
}GDScript
extends Camera2D
class_name StageCamera
## 跟随多个玩家的摄像机
@export var follow_speed: float = 5.0
@export var min_x: float = 0.0
@export var max_x: float = 5000.0
var _targets: Array[Node2D] = []
func add_target(target: Node2D) -> void:
_targets.append(target)
func remove_target(target: Node2D) -> void:
_targets.erase(target)
func _process(delta: float) -> void:
if _targets.is_empty():
return
var center: Vector2 = _calculate_center()
center.x = clampf(center.x, min_x, max_x)
global_position = global_position.lerp(center, follow_speed * delta)
func _calculate_center() -> Vector2:
var sum: Vector2 = Vector2.ZERO
for t in _targets:
sum += t.global_position
return sum / _targets.size()5.4 本章小结
| 概念 | 说明 |
|---|---|
| 战斗区域 | 进入触发,消灭怪物后解锁,推进到下一区域 |
| 屏障 | 锁住玩家不能逃跑,强制战斗 |
| 动态生成 | 怪物数量根据玩家人数由 DifficultyManager 决定 |
| 视差背景 | 多层背景不同速度滚动,营造纵深感 |
| 摄像机 | 跟随所有玩家的中心点,平滑移动 |
下一章我们将实现敌人AI,让怪物会主动追击玩家并攻击。
