Files
godot-------/Script/Pawn/CameraControl.cs

225 lines
8.0 KiB
C#
Raw Normal View History

2025-07-12 11:30:22 +08:00
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();
}
}
}