225 lines
8.0 KiB
C#
225 lines
8.0 KiB
C#
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<Node2D>("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();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平滑插值函数(类似Unity的SmoothDamp)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新实际使用的边界(考虑缩放)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 钳制位置在边界范围内
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置摄像机边界(简化API)
|
||
/// </summary>
|
||
public void SetBounds(Rect2 bounds, bool updateImmediately = true)
|
||
{
|
||
CameraBounds = bounds;
|
||
EnableBounds = true;
|
||
if (updateImmediately)
|
||
UpdateEffectiveBounds();
|
||
}
|
||
}
|
||
} |