using System; using System.Collections; using System.Collections.Generic; using System.Linq; using DG.Tweening; using Ichni.RhythmGame.Beatmap; using Ichni.UI; using UniRx; using UnityEngine; using UnityEngine.Rendering.Universal; using UnityEngine.Serialization; namespace Ichni.RhythmGame { public partial class GameCamera : GameElement, IHaveTransformSubmodule, IHaveTimeDurationSubmodule { #region [暴露属性字段] Camera View & Settings public Camera cam; //public Transform rotationPoint; //public Transform positionPoint; public Transform cameraTransform; public CameraViewType cameraViewType; public float perspectiveAngle; public float orthographicSize; public float perspectiveOffset; public float zoomOffset; // 用于效果(如CameraZoomEffect)的临时视野偏移 public void RefreshFOV() { cam.fieldOfView = perspectiveAngle + perspectiveOffset + zoomOffset; } #endregion #region [子模块接口与关联引用] Submodules & References public TransformSubmodule transformSubmodule { get; set; } public TimeDurationSubmodule timeDurationSubmodule { get; set; } private static CameraManager cameraManager => GameManager.Instance.cameraManager; #endregion #region [生命周期] Lifecycle & Factory public static GameCamera GenerateElement(string elementName, Guid id, List tags, bool isFirstGenerated, GameElement parentElement, CameraViewType cameraViewType, float perspectiveAngle, float orthographicSize) { GameCamera gameCamera = Instantiate(GameManager.Instance.basePrefabs.gameCamera).GetComponent(); gameCamera.Initialize(elementName, id, tags, isFirstGenerated, parentElement); cameraManager.gameCamera = gameCamera; gameCamera.parentElement = parentElement; gameCamera.cameraViewType = cameraViewType; gameCamera.cam.orthographic = cameraViewType == CameraViewType.Orthographic; gameCamera.perspectiveAngle = perspectiveAngle; gameCamera.orthographicSize = orthographicSize; gameCamera.cameraTransform = gameCamera.transform; float currentAspect = UIManager.GetScreenRatio(); float targetAspect = UIManager.StandardRatio; if (currentAspect < targetAspect) { // 屏幕比较方 (如 4:3 平板),需要增加垂直 FOV 以保持 16:9 标准下的水平绝对视野 float hFovRad = 2.0f * Mathf.Atan(Mathf.Tan(perspectiveAngle * Mathf.Deg2Rad / 2.0f) * targetAspect); float newVFovRad = 2.0f * Mathf.Atan(Mathf.Tan(hFovRad / 2.0f) / currentAspect); // 设置 perspectiveOffset 以完美补足被裁切的空间 gameCamera.perspectiveOffset = (newVFovRad * Mathf.Rad2Deg) - perspectiveAngle; } else { // 屏幕比较长 (如 20:9 手机),维持原有垂直 FOV,左右延展屏幕用于渲染环境背景 gameCamera.perspectiveOffset = 0f; } gameCamera.RefreshFOV(); return gameCamera; } public override void AfterInitialize() { base.AfterInitialize(); //gameCamera.GetComponent().cameraStack.Add(cameraManager.uiCamera); GameManager.Instance.backgroundController.backgroundCanvas.worldCamera = cam; } public override void SetDefaultSubmodules() { transformSubmodule = new TransformSubmodule(this); } #endregion } #region [枚举类型] Enums public partial class GameCamera { public enum CameraViewType { Perspective = 0, Orthographic = 1 } } #endregion #region [坐标变换逻辑更新] Transform Observe & Update Logics public partial class GameCamera { public void SetTransformObserver() { transformSubmodule.observer = Observable.EveryLateUpdate() .Where(_=>GameManager.Instance.songPlayer.isUpdating) .Subscribe(_ => UpdateTransform()) .AddTo(transformSubmodule.attachedGameElement); } public void UpdateTransform(bool refreshAll = true) { bool willRefresh = false; if (!transformSubmodule.eulerAnglesOffsetLock && transformSubmodule.eulerAnglesDirtyMark) { transformSubmodule.currentEulerAngles = transformSubmodule.originalEulerAngles + transformSubmodule.eulerAnglesOffset; transform.localEulerAngles = transformSubmodule.currentEulerAngles; transformSubmodule.eulerAnglesDirtyMark = false; willRefresh = true; transformSubmodule.eulerAnglesOffset = Vector3.zero; } if (transformSubmodule.positionDirtyMark) { transformSubmodule.currentPosition = transformSubmodule.originalPosition + transformSubmodule.positionOffset; transform.localPosition = transformSubmodule.currentPosition; transformSubmodule.positionDirtyMark = false; willRefresh = true; transformSubmodule.positionOffset = Vector3.zero; } if (refreshAll && willRefresh) { Refresh(); } } } #endregion }