-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathCameraZoneManager.cs
More file actions
287 lines (246 loc) · 12.7 KB
/
CameraZoneManager.cs
File metadata and controls
287 lines (246 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
using Godot;
using System.Collections.Generic;
using Kuros.Utils;
namespace Kuros.Managers
{
/// <summary>
/// 相机区域管理器 - 动态区域注册模式。
///
/// 工作流程:
/// 1. StageGeneratorManager 生成关卡后调用 SetGlobalBounds() 设置全局初始相机范围。
/// 2. 每个房间场景内的 CameraZoneArea 节点在玩家进入时调用 RegisterZoneAndSwitch(),
/// 相机限制切换到该房间的边界。
/// 3. 房间内的 BattleArena 检测到敌人时调用 CreateAndSwitchTemporaryCameraZone(),
/// 相机锁定到战斗区域。
/// 4. 战斗结束后调用 RemoveTemporaryCameraZone(),相机恢复到当前房间区域。
/// </summary>
public partial class CameraZoneManager : Node
{
/// <summary>相机区域定义。</summary>
public class CameraZone
{
public string Name { get; set; } = "Zone";
public int LimitLeft { get; set; } = 0;
public int LimitTop { get; set; } = 0;
public int LimitRight { get; set; } = 0;
public int LimitBottom { get; set; } = 0;
public float ZoomLevel { get; set; } = 0.43f;
}
[Export] public Camera2D? TargetCamera { get; set; }
[Export] public Node2D? Player { get; set; }
private CameraZone? _currentZone;
private readonly Dictionary<string, CameraZone> _registeredZones = new();
private readonly Dictionary<string, CameraZone> _temporaryCameraZones = new();
private string? _temporaryCameraZoneNameBeforeSwitch;
// 玩家当前身处的区域有序列表(进入时追加,退出时移除)
private readonly List<string> _activeZoneStack = new();
// 过场动画期间锁定,防止玩家被 Disabled 导致区域误退出
private bool _zoneLocked = false;
/// <summary>当前激活的相机区域名称。</summary>
public string? CurrentZoneName => _currentZone?.Name;
/// <summary>锁定区域切换,过场动画接管摄像机时调用。</summary>
public void LockZone()
{
_zoneLocked = true;
GameLogger.Debug(nameof(CameraZoneManager), $"区域锁定(当前: {_currentZone?.Name ?? "无"}),过场期间忽略区域进出");
}
/// <summary>解锁区域切换,过场动画结束时调用。</summary>
public void UnlockZone()
{
_zoneLocked = false;
GameLogger.Debug(nameof(CameraZoneManager), "区域解锁");
}
public override void _Ready()
{
if (TargetCamera == null)
{
GameLogger.Error(nameof(CameraZoneManager), "未设置目标相机!");
return;
}
if (Player == null)
{
Player = GetTree().GetFirstNodeInGroup("player") as Node2D;
if (Player == null)
GameLogger.Warn(nameof(CameraZoneManager), "未找到玩家节点,将在首次区域进入时自动查找。");
}
GameLogger.Info(nameof(CameraZoneManager), "相机区域管理器已初始化(动态区域注册模式)");
}
// ─── 区域注册 ──────────────────────────────────────────────────────────
/// <summary>
/// 注册一个命名相机区域(不切换)。
/// </summary>
public void RegisterZone(string name, Rect2 bounds, float zoomLevel = 0.43f)
{
_registeredZones[name] = BoundsToZone(name, bounds, zoomLevel);
GameLogger.Debug(nameof(CameraZoneManager), $"注册区域: {name} X[{(int)bounds.Position.X}, {(int)(bounds.Position.X + bounds.Size.X)}]");
}
/// <summary>
/// 玩家进入某区域时调用:将该区域推入活跃栈并切换相机。
/// </summary>
public void EnterZone(string name, Rect2 bounds, float zoomLevel = 0.43f)
{
RegisterZone(name, bounds, zoomLevel);
if (!_activeZoneStack.Contains(name))
_activeZoneStack.Add(name);
if (_zoneLocked)
{
GameLogger.Debug(nameof(CameraZoneManager), $"区域已锁定,忽略进入: {name}");
return;
}
SwitchToZone(name);
GameLogger.Debug(nameof(CameraZoneManager), $"进入区域: {name},栈: [{string.Join(", ", _activeZoneStack)}]");
}
/// <summary>
/// 玩家离开某区域时调用:从活跃栈移除,并切换到栈顶的上一个区域。
/// </summary>
public void ExitZone(string name)
{
_activeZoneStack.Remove(name);
GameLogger.Debug(nameof(CameraZoneManager), $"离开区域: {name},栈: [{string.Join(", ", _activeZoneStack)}]");
// 区域锁定期间不切换相机,仅维护栈
if (_zoneLocked)
{
GameLogger.Debug(nameof(CameraZoneManager), $"区域已锁定,忽略离开: {name}");
return;
}
// 当前相机正是该区域才需要切换
if (_currentZone?.Name == name)
{
if (_activeZoneStack.Count > 0)
SwitchToZone(_activeZoneStack[^1]); // 切回栈顶(最近进入的区域)
else
SwitchToZone("Stage_Global"); // 已无房间区域,回到全局
}
}
/// <summary>
/// 注销一个区域(房间离开场景树时调用)。
/// </summary>
public void UnregisterZone(string name)
{
_registeredZones.Remove(name);
_activeZoneStack.Remove(name);
}
/// <summary>
/// 注册并立即切换(向后兼容,内部调用 EnterZone)。
/// </summary>
public void RegisterZoneAndSwitch(string name, Rect2 bounds, float zoomLevel = 0.43f) => EnterZone(name, bounds, zoomLevel);
// ─── 区域切换 ──────────────────────────────────────────────────────────
/// <summary>
/// 切换到已注册的区域(含临时区域)。
/// </summary>
public void SwitchToZone(string zoneName)
{
if (_currentZone?.Name == zoneName) return;
CameraZone? zone = null;
if (!_registeredZones.TryGetValue(zoneName, out zone))
_temporaryCameraZones.TryGetValue(zoneName, out zone);
if (zone == null)
{
GameLogger.Warn(nameof(CameraZoneManager), $"区域 '{zoneName}' 未注册,切换失败。");
return;
}
_currentZone = zone;
ApplyZoneToCamera(zone);
GameLogger.Info(nameof(CameraZoneManager),
$"✓ 切换到区域: {zoneName} L:{zone.LimitLeft} R:{zone.LimitRight} T:{zone.LimitTop} B:{zone.LimitBottom}");
}
// ─── 临时区域(战斗锁定)──────────────────────────────────────────────
/// <summary>
/// 创建临时战斗相机区域并立即切换。同时记录当前区域以便战斗结束后恢复。
/// 由 BattleArena 在激活战斗时调用。
/// </summary>
public void CreateAndSwitchTemporaryCameraZone(Rect2 arenaRect, string zoneName, float zoomLevel = 0.43f)
{
_temporaryCameraZones[zoneName] = BoundsToZone(zoneName, arenaRect, zoomLevel);
_temporaryCameraZoneNameBeforeSwitch = _currentZone?.Name;
SwitchToZone(zoneName);
GameLogger.Info(nameof(CameraZoneManager), $"✓ 创建临时战斗区域: {zoneName},战斗结束后恢复至: {_temporaryCameraZoneNameBeforeSwitch ?? "无"}");
}
/// <summary>
/// 移除临时战斗区域并恢复至战斗前的区域。
/// 由 BattleArena 在战斗结束时调用。
/// </summary>
public void RemoveTemporaryCameraZone(string zoneName)
{
if (!_temporaryCameraZones.Remove(zoneName)) return;
if (_currentZone?.Name == zoneName)
{
if (!string.IsNullOrEmpty(_temporaryCameraZoneNameBeforeSwitch))
SwitchToZone(_temporaryCameraZoneNameBeforeSwitch);
_temporaryCameraZoneNameBeforeSwitch = null;
}
GameLogger.Info(nameof(CameraZoneManager), $"✓ 移除临时战斗区域: {zoneName}");
}
// ─── 全局边界(由 StageGeneratorManager 调用)──────────────────────────
/// <summary>
/// 设置关卡全局相机边界,关卡生成完毕后立即生效。
/// 此区域作为默认基础区域,CameraZoneArea 进入房间后会覆盖为房间边界。
/// </summary>
public void SetGlobalBounds(int limitLeft, int limitTop, int limitRight, int limitBottom, float zoomLevel = 0.43f)
{
if (TargetCamera == null) return;
var bounds = new Rect2(limitLeft, limitTop, limitRight - limitLeft, limitBottom - limitTop);
RegisterZoneAndSwitch("Stage_Global", bounds, zoomLevel);
GameLogger.Info(nameof(CameraZoneManager),
$"全局相机边界已设置:X[{limitLeft}, {limitRight}] Y[{limitTop}, {limitBottom}]");
}
// ─── 内部工具 ──────────────────────────────────────────────────────────
private static CameraZone BoundsToZone(string name, Rect2 bounds, float zoomLevel = 0.43f) => new CameraZone
{
Name = name,
LimitLeft = (int)bounds.Position.X,
LimitTop = (int)bounds.Position.Y,
LimitRight = (int)(bounds.Position.X + bounds.Size.X),
LimitBottom = (int)(bounds.Position.Y + bounds.Size.Y),
ZoomLevel = zoomLevel,
};
private void ApplyZoneToCamera(CameraZone zone)
{
if (TargetCamera == null) return;
TargetCamera.LimitLeft = zone.LimitLeft;
TargetCamera.LimitTop = zone.LimitTop;
TargetCamera.LimitRight = zone.LimitRight;
TargetCamera.LimitBottom = zone.LimitBottom;
SetZoom(zone.ZoomLevel);
}
// ─── 缩放 ──────────────────────────────────────────────────────────────
private Tween? _zoomTween;
/// <summary>
/// 平滑过渡相机 Zoom。<br/>
/// <paramref name="zoom"/> 为目标缩放值(X/Y 相同)。<br/>
/// <paramref name="duration"/> 为过渡时长(秒),0 表示立即生效。
/// </summary>
public void SetZoom(float zoom, float duration = 0.25f)
{
if (TargetCamera == null) return;
_zoomTween?.Kill();
var target = new Vector2(zoom, zoom);
if (duration <= 0f)
{
TargetCamera.Zoom = target;
return;
}
_zoomTween = TargetCamera.CreateTween();
_zoomTween.TweenProperty(TargetCamera, "zoom", target, duration)
.SetTrans(Tween.TransitionType.Sine)
.SetEase(Tween.EaseType.InOut);
}
// ─── 调试 ──────────────────────────────────────────────────────────────
public string GetDebugInfo()
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("=== CameraZoneManager ===");
sb.AppendLine($"当前区域: {(_currentZone?.Name ?? "无")}");
sb.AppendLine($"已注册区域 ({_registeredZones.Count}):");
foreach (var kv in _registeredZones)
sb.AppendLine($" {kv.Key}: L={kv.Value.LimitLeft} R={kv.Value.LimitRight}");
if (_temporaryCameraZones.Count > 0)
{
sb.AppendLine($"临时区域 ({_temporaryCameraZones.Count}):");
foreach (var kv in _temporaryCameraZones)
sb.AppendLine($" {kv.Key}: L={kv.Value.LimitLeft} R={kv.Value.LimitRight}");
}
return sb.ToString();
}
}
}