using Godot; using System; namespace Cosmobox { public partial class CameraControl : Camera2D { [Export] public Node2D Target; // 要跟随的目标(通常为玩家角色) [Export] public bool EnableSmoothing = true; // 启用平滑跟随 [Export(PropertyHint.Range, "0.1,10,0.1")] public float SmoothingSpeed = 2.5f; // 平滑跟随速度(推荐值2.0-4.0) [Export] public bool EnableBounds = false; // 启用边界限制 [Export] public Rect2 CameraBounds; // 摄像机移动边界 [Export] public Vector2 PositionOffset = Vector2.Zero; // 目标位置偏移 [Export] public bool ScaleWithZoom = true; // 边界是否随缩放调整 [Export] public float DeadZoneRadius = 0; // 摄像机不移动的半径(减少微小移动) [Export] public float ZoomSensitivity = 0.1f; // 缩放灵敏度 [Export] public Vector2 MinZoom = new Vector2(0.5f, 0.5f); // 最小缩放 [Export] public Vector2 MaxZoom = new Vector2(2.0f, 2.0f); // 最大缩放 private Vector2 _targetPosition; private Vector2 _currentVelocity = Vector2.Zero; // 用于平滑插值 private Rect2 _effectiveBounds; // 实际使用的边界(考虑缩放) public override void _Ready() { if (Target == null) { // 自动查找玩家作为目标 Target = GetTree().Root.GetNodeOrNull("Player"); if (Target == null) { GD.PrintErr("CameraControl: Failed to find Player node. Assign a Target manually."); } } // 确保摄像机位置初始化 if (Target != null) { _targetPosition = Target.GlobalPosition + PositionOffset; GlobalPosition = _targetPosition; } // 初始化有效边界 UpdateEffectiveBounds(); } public override void _PhysicsProcess(double delta) { if (Target == null) return; float deltaFloat = (float)delta; // 更新目标位置(考虑偏移) Vector2 newTarget = Target.GlobalPosition + PositionOffset; // 应用死区减少微小移动 if (DeadZoneRadius > 0 && newTarget.DistanceTo(_targetPosition) < DeadZoneRadius) { newTarget = _targetPosition; } else { _targetPosition = newTarget; } // 应用边界限制 if (EnableBounds) { UpdateEffectiveBounds(); _targetPosition = ClampPosition(_targetPosition); } // 应用平滑过渡或直接跟随 Vector2 newPosition; if (EnableSmoothing && SmoothingSpeed > 0) { // 使用SmoothDamp获得更自然的缓动效果 newPosition = SmoothDamp(GlobalPosition, _targetPosition, ref _currentVelocity, 1 / SmoothingSpeed, deltaFloat); } else { newPosition = _targetPosition; } // 最终位置赋值 GlobalPosition = newPosition; } public override void _UnhandledInput(InputEvent @event) { // 鼠标滚轮缩放控制 if (@event is InputEventMouseButton mouseEvent) { if (mouseEvent.IsPressed()) { Vector2 zoomFactor = Zoom; // // 滚轮向上 - 放大 // if (mouseEvent.ButtonIndex == MouseButton.WheelUp) // { // zoomFactor -= Vector2.One * ZoomSensitivity; // } // // 滚轮向下 - 缩小 // else if (mouseEvent.ButtonIndex == MouseButton.WheelDown) // { // zoomFactor += Vector2.One * ZoomSensitivity; // } // // 钳制缩放值 // zoomFactor.X = Mathf.Clamp(zoomFactor.X, MinZoom.X, MaxZoom.X); // zoomFactor.Y = Mathf.Clamp(zoomFactor.Y, MinZoom.Y, MaxZoom.Y); if (zoomFactor != Zoom) { Zoom = zoomFactor; UpdateEffectiveBounds(); } } } } /// /// 平滑插值函数(类似Unity的SmoothDamp) /// private Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 currentVelocity, float smoothTime, float deltaTime, float maxSpeed = float.MaxValue) { float omega = 2f / Mathf.Max(0.0001f, smoothTime); float x = omega * deltaTime; float exp = 1f / (1f + x + 0.48f * x * x + 0.235f * x * x * x); Vector2 change = current - target; Vector2 originalTarget = target; float maxChange = maxSpeed * smoothTime; // 使用自定义方法替代ClampMagnitude float magnitude = change.Length(); if (magnitude > maxChange && maxChange > 0) { change = change.Normalized() * maxChange; } target = current - change; Vector2 temp = (currentVelocity + omega * change) * deltaTime; currentVelocity = (currentVelocity - omega * temp) * exp; Vector2 result = target + (change + temp) * exp; // 防止过冲 Vector2 toOriginalTarget = originalTarget - current; Vector2 toResult = result - originalTarget; if (toOriginalTarget.Dot(toResult) > 0) { result = originalTarget; currentVelocity = (result - originalTarget) / deltaTime; } return result; } /// /// 更新实际使用的边界(考虑缩放) /// private void UpdateEffectiveBounds() { if (!EnableBounds || !ScaleWithZoom) { _effectiveBounds = CameraBounds; return; } // 根据缩放比例调整边界 Vector2 halfViewSize = GetViewportRect().Size * 0.5f / Zoom; Vector2 min = CameraBounds.Position + halfViewSize; Vector2 max = CameraBounds.End - halfViewSize; // 确保有效边界有效 if (min.X > max.X) min.X = max.X = (min.X + max.X) * 0.5f; if (min.Y > max.Y) min.Y = max.Y = (min.Y + max.Y) * 0.5f; _effectiveBounds = new Rect2(min, max - min); } /// /// 钳制位置在边界范围内 /// private Vector2 ClampPosition(Vector2 position) { return new Vector2( Mathf.Clamp(position.X, _effectiveBounds.Position.X, _effectiveBounds.End.X), Mathf.Clamp(position.Y, _effectiveBounds.Position.Y, _effectiveBounds.End.Y) ); } // 调试:在编辑器中绘制边界可视化 public override void _Draw() { if (Engine.IsEditorHint() && EnableBounds) { // 绘制主边界 var rect = new Rect2(_effectiveBounds.Position - GlobalPosition, _effectiveBounds.Size); DrawRect(rect, Colors.Red, false, 2.0f); // 绘制原始边界参考线 var origRect = new Rect2(CameraBounds.Position - GlobalPosition, CameraBounds.Size); DrawRect(origRect, Colors.Yellow, false, 1.0f); } } /// /// 设置摄像机边界(简化API) /// public void SetBounds(Rect2 bounds, bool updateImmediately = true) { CameraBounds = bounds; EnableBounds = true; if (updateImmediately) UpdateEffectiveBounds(); } } }