diff --git a/Client/Assets/Scripts/CameraControl/CameraControl.cs b/Client/Assets/Scripts/CameraControl/CameraControl.cs index 13e6161..5bfbc0d 100644 --- a/Client/Assets/Scripts/CameraControl/CameraControl.cs +++ b/Client/Assets/Scripts/CameraControl/CameraControl.cs @@ -1,233 +1,303 @@ using Base; +using Map; using UnityEngine; -using UnityEngine.SceneManagement; namespace CameraControl { + /// + /// 控制游戏摄像机的移动、缩放和维度切换。 + /// 继承自 MonoSingleton 以确保场景中只有一个实例,并实现 ITick 和 ITickUI 接口以在游戏循环和UI循环中更新。 + /// public class CameraControl : Utils.MonoSingleton, ITick, ITickUI { - // Camera movement variables - [SerializeField] private float _zoomSpeed = 5f; - [SerializeField] private float _minZoom = 2f; - [SerializeField] private float _maxZoom = 20f; - [SerializeField] private float _focusLerpSpeed = 5f; + // 摄像机移动相关变量 + [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 = false; + private Vector3 _dragOrigin; // 拖拽操作的起始世界坐标 + private bool _isDragging; // 标记摄像机是否正在被拖拽 - private Camera _camera; + private Camera _camera; // 当前场景中的主摄像机引用 - private int dimensionId; - private string[] dimensionList; - + private int dimensionId; // 当前摄像机控制器关注的维度索引 + private string[] dimensionList; // 维度名称列表 + /// + /// 当脚本实例被销毁时调用。 + /// 用于取消订阅 Program.Instance 的事件,防止内存泄漏。 + /// private void OnDestroy() { - // 移除事件监听 - SceneManager.sceneLoaded -= OnSceneLoaded; - } - - protected override void OnStart() - { - // 注册场景加载事件。请注意,MonoSingleton中的OnStart通常由Awake调用。 - // 真正的Init调用已移至Start方法,以确保Program.Instance已完成其Awake阶段的初始化。 - SceneManager.sceneLoaded += OnSceneLoaded; - } - - private void Start() - { - // 在Start中调用Init,确保所有依赖的单例(如Program.Instance)已完成Awake阶段的初始化。 - Init(); - } - - private void OnSceneLoaded(Scene scene, LoadSceneMode mode) - { - // 场景加载完成后调用 Init 方法,重新获取相机引用并初始化维度状态。 - // 这确保了如果CameraControl是DontDestroyOnLoad,在切换场景后也能正确工作。 - Init(); + // 在OnDestroy时进行Program.Instance检查是合理的,因为Program实例可能已被销毁。 + if (Program.Instance != null) + { + Program.Instance.OnFocusedDimensionChanged -= Init; + } } - - private void Init() + /// + /// 脚本实例在第一次帧更新之前被启用时调用。 + /// 用于订阅 Program.Instance 的事件,并进行初始设置。 + /// + private void Start() { - // 确保相机引用有效 - if (_camera == null) + // 在Start时进行Program.Instance检查是合理的,防止CameraControl比Program实例更早启动。 + if (Program.Instance != null) + { + Program.Instance.OnFocusedDimensionChanged += Init; // 订阅聚焦维度改变事件 + Init(); // 首次调用初始化,根据当前聚焦维度设置摄像机状态 + } + else + { + Debug.LogError("CameraControl 的 Start 方法中 Program.Instance 为空。请检查 Program 单例的初始化顺序。摄像机控制功能将无法初始化。"); + } + } + + /// + /// 根据指定的维度对象初始化摄像机控制器。 + /// 主要用于在聚焦维度改变时更新摄像机状态和内部的维度ID。 + /// + /// 当前聚焦的维度对象。 + private void Init(Dimension obj) + { + // 修改点 1: 统一 Unity 对象 `null` 检查,移除冗余 `_camera == null` + // 确保相机引用有效,如果为空则尝试获取主相机 + if (!_camera) { _camera = Camera.main; - if (_camera == null) + if (!_camera) // 再次检查获取是否成功 { _camera = FindFirstObjectByType(); } } - if (_camera == null) + if (!_camera) // 如果到此为止仍未获取到相机,记录错误并返回 { - Debug.LogError("No Camera found in the scene! CameraControl functionalities will be limited."); - return; // 如果没有相机,则无法进行后续初始化 + Debug.LogError("场景中未找到摄像机!CameraControl 功能将无法完全初始化。"); + // 如果没有相机,后续依赖相机的逻辑都无法执行,直接返回。 + // dimensionList 的获取如果逻辑上不依赖相机,可以放在此处之外。 + // 但当前上下文中,Init的主要目的是初始化相机视角等,无相机则无意义。 + return; } - // 初始化维度数据 (假设 Program.Instance 总是已初始化) - dimensionId = 0; // 默认从第一个维度开始 - dimensionList = Program.Instance.Dimensions; - if (dimensionList != null && dimensionList.Length > 0) + // 修改点 2: 移除对 `Program.Instance` 的冗余 `null` 检查 + // 修改点 3: `Init` 方法中 `dimensionList` 的获取位置 + // 总是获取最新的维度列表,因为外部维度状态可能会变化 + dimensionList = Program.Instance.Dimensions; + + // 处理 obj 为 null 的情况 + if (!obj) { - SetCameraPositionForDimension(0); + Debug.LogWarning("Init 方法在聚焦维度为空的情况下被调用。维度列表已更新,但摄像机状态未根据特定维度设置。"); + // 此时 dimensionId 仍然是默认值或上次设置的值,不进行 cameraPosition 的设置。 + return; + } + + // 根据当前聚焦维度同步 CameraControl 内部的 dimensionId + int focusedIndex = System.Array.IndexOf(dimensionList, obj.name); + if (focusedIndex != -1) + { + dimensionId = focusedIndex; } else { - Debug.LogWarning("Dimension list is empty or null from Program.Instance. Cannot set initial camera position for dimensions."); + Debug.LogWarning($"聚焦维度 '{obj.name}' 未在维度列表中找到。回退到 ID 0。"); + dimensionId = 0; // 找不到时,回退到第一个维度,避免数组越界 } + + // 设置摄像机位置到当前聚焦维度的位置 (移除 Program.Instance 的 null 检查) + _camera.transform.position = Program.Instance.FocusedDimension.cameraPosition; + } + + /// + /// 调用带参数的 Init 方法,使用 Program.Instance 中当前聚焦的维度。 + /// + private void Init() + { + // 移除 Program.Instance 的 null 检查 + Init(Program.Instance.FocusedDimension); } + /// + /// 切换到下一个维度。 + /// 会保存当前摄像机位置到当前维度,然后加载下一个维度的摄像机位置。 + /// public void NextDimension() { - if (_camera == null) + // 修改点 1: 统一 Unity 对象 `null` 检查 + if (!_camera) { - Debug.LogWarning("Camera reference is null, cannot switch dimensions."); + Debug.LogWarning("摄像机引用为空,无法切换维度。"); return; } if (dimensionList == null || dimensionList.Length == 0) { - Debug.LogWarning("Dimension list is empty."); + Debug.LogWarning("维度列表为空。"); return; } - // 1. 保存当前相机的真实位置到当前维度 (假设 Program.Instance 总是已初始化) - // 维度ID范围检查仍然保留,因为这是数组访问的安全保障 + // 1. 保存当前摄像机的实际位置到当前维度 (移除 Program.Instance 的 null 检查) if (dimensionId >= 0 && dimensionId < dimensionList.Length) { - // Program.Instance 假设不会为 null var currentDimension = Program.Instance.GetDimension(dimensionList[dimensionId]); - if (currentDimension != null) + if (currentDimension != null) // currentDimension 可能是普通 C# 对象,所以此处 null 检查是必要的 { currentDimension.cameraPosition = _camera.transform.position; - // Debug.Log($"Saved camera position {currentDimension.cameraPosition} for dimension {dimensionList[dimensionId]} (ID: {dimensionId})"); } else { - Debug.LogWarning($"Could not find dimension object for ID {dimensionId} ({dimensionList[dimensionId]}) to save camera position."); + Debug.LogWarning($"无法找到 ID 为 {dimensionId} ({dimensionList[dimensionId]}) 的维度对象,无法保存摄像机位置。"); } } else { - Debug.LogWarning($"Current dimensionId ({dimensionId}) is out of bounds for saving camera position (dimensionList length: {dimensionList.Length})."); + Debug.LogWarning($"当前维度ID ({dimensionId}) 超出范围,无法保存摄像机位置 (维度列表长度: {dimensionList.Length})。"); } - // 2. 更新 dimensionId,形成循环 + // 2. 更新 dimensionId,形成循环切换 dimensionId = (dimensionId + 1) % dimensionList.Length; - // 3. 更新相机位置到新维度 + // 3. 更新聚焦维度,摄像机位置的更新将由事件触发的 Init 方法处理 SetCameraPositionForDimension(dimensionId); } + /// + /// 设置 Program.Instance 中聚焦的维度。 + /// 摄像机位置的实际更新将通过 Program.Instance.OnFocusedDimensionChanged 事件在 Init(Dimension obj) 方法中处理。 + /// + /// 要设置的维度ID。 private void SetCameraPositionForDimension(int id) { - if (_camera == null) + // 修改点 1: 统一 Unity 对象 `null` 检查 + if (!_camera) { - Debug.LogWarning("Camera reference is null, cannot set camera position."); + Debug.LogWarning("摄像机引用为空,无法设置摄像机位置。"); return; } if (dimensionList == null || id < 0 || id >= dimensionList.Length) { - Debug.LogWarning($"Dimension ID {id} is out of bounds or dimension list is null/empty."); + Debug.LogWarning($"维度ID {id} 超出范围或维度列表为空。"); return; } - // Program.Instance 假设不会为 null - - // 确保获取到的 Dimension 对象不为 null - var dimension = Program.Instance.GetDimension(dimensionList[id]); - if (dimension) - { - Vector3 cameraPosition = dimension.cameraPosition; - _camera.transform.position = cameraPosition; - } - else - { - Debug.LogWarning($"Dimension object for ID {id} ({dimensionList[id]}) is null. Cannot set camera position."); - } + // 移除 Program.Instance 的 null 检查 + Program.Instance.SetFocusedDimension(dimensionList[id]); } + /// + /// 在游戏循环中每帧调用,用于处理摄像机跟随聚焦实体和维度切换。 + /// public void Tick() { - if (_camera == null) return; // 确保相机存在 + // 修改点 1: 统一 Unity 对象 `null` 检查 + if (!_camera) return; // 确保相机存在 - // 假设 Program.Instance 总是已初始化 - if (Program.Instance.FocusedEntity) + // 当没有拖拽且存在聚焦实体时,摄像机跟随聚焦实体 (移除 Program.Instance 的 null 检查) + if (!_isDragging && Program.Instance.FocusedEntity) { var targetPosition = new Vector3( Program.Instance.FocusedEntity.Position.x, Program.Instance.FocusedEntity.Position.y, _camera.transform.position.z); - // 使用 deltaTime 进行平滑的相机跟随 + // 使用 deltaTime 进行平滑的摄像机跟随 _camera.transform.position = Vector3.Lerp( _camera.transform.position, targetPosition, Time.deltaTime * _focusLerpSpeed); } + // 按下 Tab 键时切换到下一个维度 if (Input.GetKeyDown(KeyCode.Tab)) { NextDimension(); } } + /// + /// 在UI更新循环中每帧调用,用于处理用户界面的摄像机操作,如鼠标拖拽和缩放。 + /// public void TickUI() { + // 修改点 1: 统一 Unity 对象 `null` 检查 if (!_camera) // 确保相机存在 return; - HandleMiddleMouseDrag(); - // HandleMouseZoom(); + HandleMiddleMouseDrag(); // 处理鼠标中键拖拽 + HandleMouseZoom(); // 处理鼠标滚轮缩放 } + /// + /// 设置摄像机的世界坐标位置。 + /// + /// 要设置的摄像机位置。 public void SetPosition(Vector3 position) { - if (_camera) + // 修改点 1: 统一 Unity 对象 `null` 检查 + if (_camera) // if (!_camera) 比 if (_camera == null) 更优雅 _camera.transform.position = position; else - Debug.LogWarning("Camera reference is null, cannot set position."); + Debug.LogWarning("摄像机引用为空,无法设置位置。"); } + /// + /// 处理鼠标中键拖拽摄像机的逻辑。 + /// private void HandleMiddleMouseDrag() { - if (_camera == null) return; // 确保相机存在 + // 修改点 1: 统一 Unity 对象 `null` 检查 + if (!_camera) return; // 确保相机存在 - // Start drag - if (Input.GetMouseButtonDown(2)) // Middle mouse button + // 开始拖拽:检测鼠标中键按下 + if (Input.GetMouseButtonDown(2)) // 鼠标中键 { _dragOrigin = _camera.ScreenToWorldPoint(Input.mousePosition); _isDragging = true; - // 假设 Program.Instance 总是已初始化 + // 如果有聚焦实体,则在开始拖拽时取消聚焦,暂停跟随 (移除 Program.Instance 的 null 检查) if (Program.Instance.FocusedEntity) { - Program.Instance.FocusedEntity.PlayerControlled = false; Program.Instance.SetFocusedEntity(null); } } - // During drag + // 拖拽中:根据鼠标移动更新摄像机位置 if (Input.GetMouseButton(2) && _isDragging) { var difference = _dragOrigin - _camera.ScreenToWorldPoint(Input.mousePosition); _camera.transform.position += difference; } - // End drag + // 结束拖拽:检测鼠标中键抬起 if (Input.GetMouseButtonUp(2)) { _isDragging = false; + // 拖拽结束后,当前代码不会自动重新聚焦实体。如果需要此行为,应在此处添加逻辑。 } } + /// + /// 处理鼠标滚轮缩放摄像机的逻辑。 + /// private void HandleMouseZoom() { - if (_camera == null) return; // 确保相机存在 + // 修改点 1: 统一 Unity 对象 `null` 检查 + if (!_camera) return; // 确保相机存在 var scroll = Input.GetAxis("Mouse ScrollWheel"); - if (scroll == 0) return; + if (scroll == 0) return; // 没有滚轮滚动,则返回 + + // 根据滚轮输入调整正交摄像机的尺寸,并限制在最小和最大缩放之间 var newSize = _camera.orthographicSize - scroll * _zoomSpeed; _camera.orthographicSize = Mathf.Clamp(newSize, _minZoom, _maxZoom); } + + /// + /// MonoSingleton 的 OnStart 方法,可在单例首次创建时添加额外初始化逻辑。 + /// + protected override void OnStart() + { + // 当前为空,是合理的。 + } } }