Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/CameraControl/CameraControl.cs

288 lines
11 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 Base;
using Map;
using UnityEngine;
namespace CameraControl
{
/// <summary>
/// 控制游戏摄像机的移动、缩放和维度切换。
/// 继承自 MonoSingleton 以确保场景中只有一个实例,并实现 ITick 接口以在游戏循环中更新。
/// </summary>
public class CameraControl : Utils.MonoSingleton<CameraControl>, ITick
{
// 摄像机移动相关变量
[SerializeField] private float _zoomSpeed = 5f; // 摄像机缩放速度
[SerializeField] private float _minZoom = 2f; // 摄像机最小缩放级别(正交摄像机的 orthographicSize
[SerializeField] private float _maxZoom = 20f; // 摄像机最大缩放级别
[SerializeField] private float _focusLerpSpeed = 5f; // 摄像机跟随目标时的平滑插值速度
private Vector3 _dragOrigin; // 拖拽操作的起始世界坐标
private bool _isDragging; // 标记摄像机是否正在被拖拽
private Camera _cachedCamera; // 缓存的场景摄像机引用
/// <summary>
/// 获取当前生效的场景摄像机。懒加载并缓存。
/// </summary>
public Camera CurrentCamera
{
get
{
if (!_cachedCamera)
{
_cachedCamera = Camera.main; // 尝试获取主摄像机
if (!_cachedCamera) // 如果主摄像机不存在或未标记,则尝试查找场景中的第一个摄像机
{
Debug.LogWarning("[CameraControl] 未找到主摄像机正在尝试查找场景中第一个Camera类型的对象。");
_cachedCamera = FindFirstObjectByType<Camera>();
}
if (!_cachedCamera)
{
Debug.LogError("[CameraControl] 场景中未找到可用的摄像机!摄像机控制功能将受限。");
}
}
return _cachedCamera;
}
}
private int dimensionId; // 当前摄像机控制器关注的维度索引
private string[] dimensionList; // 维度名称列表
/// <summary>
/// MonoSingleton 的 OnStart 方法,在单例首次创建并激活时调用,早于普通的 Start 方法。
/// 用于初始化摄像机缓存。
/// </summary>
protected override void OnStart()
{
Program.Instance.OnFocusedDimensionChanged += Init;
}
/// <summary>
/// 当脚本实例被销毁时调用。
/// 用于取消订阅 Program.Instance 的事件,防止内存泄漏。
/// </summary>
private void OnDestroy()
{
Program.Instance.OnFocusedDimensionChanged -= Init;
}
/// <summary>
/// 根据指定的维度对象初始化摄像机控制器。
/// 主要用于在聚焦维度改变时更新摄像机状态和内部的维度ID。
/// </summary>
/// <param name="obj">当前聚焦的维度对象。</param>
private void Init(Dimension obj)
{
dimensionList = Program.Instance.Dimensions;
if (!CurrentCamera) // 如果摄像机仍未找到,记录错误并返回
{
Debug.LogError("[CameraControl] 未找到摄像机!摄像机控制功能将无法完全初始化。");
return;
}
// 处理 obj 为 null 的情况 - 此时 dimensionList 已更新
if (!obj)
{
Debug.LogWarning("[CameraControl] Init方法在聚焦维度为空时调用。维度列表已更新但摄像机状态未基于特定维度设置。");
return;
}
// 根据当前聚焦维度同步 CameraControl 内部的 dimensionId
int focusedIndex = System.Array.IndexOf(dimensionList, obj.name);
if (focusedIndex != -1)
{
dimensionId = focusedIndex;
}
else
{
Debug.LogWarning($"[CameraControl] 聚焦维度 '{obj.name}' 未在维度列表中找到。回退到ID 0。");
dimensionId = 0; // 找不到时,回退到第一个维度,避免数组越界
}
// 设置摄像机位置到当前聚焦维度的位置
if (Program.Instance.FocusedDimension != null)
{
CurrentCamera.transform.position = Program.Instance.FocusedDimension.cameraPosition;
}
else
{
Debug.LogWarning("[CameraControl] 尝试设置摄像机位置时,聚焦维度为空。摄像机位置未明确根据维度更新。");
}
}
/// <summary>
/// 调用带参数的 Init 方法,使用 Program.Instance 中当前聚焦的维度。
/// </summary>
private void Init()
{
Init(Program.Instance.FocusedDimension);
}
/// <summary>
/// 切换到下一个维度。
/// 会保存当前摄像机位置到当前维度,然后加载下一个维度的摄像机位置。
/// </summary>
public void NextDimension()
{
if (!CurrentCamera)
{
Debug.LogWarning("[CameraControl] 摄像机引用为空,无法切换维度。");
return;
}
if (dimensionList == null || dimensionList.Length == 0)
{
Debug.LogWarning("[CameraControl] 维度列表为空或未初始化,无法切换维度。");
return;
}
// 1. 保存当前摄像机的实际位置到当前维度
if (dimensionId >= 0 && dimensionId < dimensionList.Length)
{
var currentDimension = Program.Instance.GetDimension(dimensionList[dimensionId]);
if (currentDimension != null)
{
currentDimension.cameraPosition = CurrentCamera.transform.position;
}
else
{
Debug.LogWarning(
$"[CameraControl] 无法找到ID为 {dimensionId} ({dimensionList[dimensionId]}) 的维度对象,无法保存摄像机位置。");
}
}
else
{
Debug.LogWarning(
$"[CameraControl] 当前维度ID ({dimensionId}) 超出范围,无法保存摄像机位置(维度列表长度: {dimensionList.Length})。");
}
// 2. 更新 dimensionId, 形成循环切换
dimensionId = (dimensionId + 1) % dimensionList.Length;
// 3. 更新聚焦维度,摄像机位置的更新将由事件触发的 Init 方法处理
SetFocusedDimensionById(dimensionId);
}
/// <summary>
/// 设置 Program.Instance 中聚焦的维度。
/// 摄像机位置的实际更新将通过 Program.Instance.OnFocusedDimensionChanged 事件在 Init(Dimension obj) 方法中处理。
/// </summary>
/// <param name="id">要设置的维度ID。</param>
private void SetFocusedDimensionById(int id)
{
if (!CurrentCamera)
{
Debug.LogWarning("[CameraControl] 摄像机引用为空,无法设置摄像机位置。");
return;
}
if (dimensionList == null || id < 0 || id >= dimensionList.Length)
{
Debug.LogWarning($"[CameraControl] 维度ID {id} 超出范围或维度列表为空,无法设置聚焦维度。");
return;
}
Program.Instance.SetFocusedDimension(dimensionList[id]);
}
/// <summary>
/// 在游戏循环中每帧调用,用于处理摄像机跟随聚焦实体和维度切换。
/// </summary>
public void Tick()
{
if (!CurrentCamera) return; // 确保相机存在
// 当没有拖拽且存在聚焦实体时,摄像机跟随聚焦实体
if (!_isDragging && Program.Instance.FocusedEntity)
{
var targetPosition = new Vector3(
Program.Instance.FocusedEntity.Position.x,
Program.Instance.FocusedEntity.Position.y,
CurrentCamera.transform.position.z);
// 使用 deltaTime 进行平滑的摄像机跟随
CurrentCamera.transform.position = Vector3.Lerp(
CurrentCamera.transform.position,
targetPosition,
Time.deltaTime * _focusLerpSpeed);
}
HandleMiddleMouseDrag(); // 处理鼠标中键拖拽
// HandleMouseZoom(); // 处理鼠标滚轮缩放
// 按下 Tab 键时切换到下一个维度
if (Input.GetKeyDown(KeyCode.Tab))
{
NextDimension();
}
}
/// <summary>
/// 设置摄像机的世界坐标位置。
/// </summary>
/// <param name="position">要设置的摄像机位置。</param>
public void SetPosition(Vector3 position)
{
if (CurrentCamera)
{
CurrentCamera.transform.position = position;
}
else
{
Debug.LogWarning("[CameraControl] 摄像机引用为空,无法设置位置。");
}
}
/// <summary>
/// 处理鼠标中键拖拽摄像机的逻辑。
/// </summary>
private void HandleMiddleMouseDrag()
{
if (!CurrentCamera) return; // 确保相机存在
// 开始拖拽:检测鼠标中键按下
if (Input.GetMouseButtonDown(2)) // 鼠标中键
{
_dragOrigin = CurrentCamera.ScreenToWorldPoint(Input.mousePosition);
_isDragging = true;
// 如果有聚焦实体,则在开始拖拽时取消聚焦,暂停跟随
if (Program.Instance.FocusedEntity)
{
Program.Instance.SetFocusedEntity(null);
}
}
// 拖拽中:根据鼠标移动更新摄像机位置
if (Input.GetMouseButton(2) && _isDragging)
{
var difference = _dragOrigin - CurrentCamera.ScreenToWorldPoint(Input.mousePosition);
CurrentCamera.transform.position += difference;
}
// 结束拖拽:检测鼠标中键抬起
if (Input.GetMouseButtonUp(2))
{
_isDragging = false;
}
}
/// <summary>
/// 处理鼠标滚轮缩放摄像机的逻辑。
/// </summary>
private void HandleMouseZoom()
{
if (!CurrentCamera) return; // 确保相机存在
var scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll == 0) return; // 没有滚轮滚动,则返回
// 根据滚轮输入调整正交摄像机的尺寸,并限制在最小和最大缩放之间
var newSize = CurrentCamera.orthographicSize - scroll * _zoomSpeed;
CurrentCamera.orthographicSize = Mathf.Clamp(newSize, _minZoom, _maxZoom);
}
}
}