Files
godot-------/Script/Pawn/CameraControl.cs
m0_75251201 7700703099 初次提交
2025-07-12 11:30:22 +08:00

225 lines
8.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
}