摄像机区域的架构改动
This commit is contained in:
17
Assets/_Game/Scripts/Camera/BaseGames.Camera.asmdef
Normal file
17
Assets/_Game/Scripts/Camera/BaseGames.Camera.asmdef
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.Camera",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Camera",
|
||||
"references": [
|
||||
"BaseGames.Core.Events",
|
||||
"Unity.Cinemachine"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
7
Assets/_Game/Scripts/Camera/BaseGames.Camera.asmdef.meta
Normal file
7
Assets/_Game/Scripts/Camera/BaseGames.Camera.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b9cbc0f2e569d64a862f3b7f417c7b6
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
90
Assets/_Game/Scripts/Camera/CameraArea.cs
Normal file
90
Assets/_Game/Scripts/Camera/CameraArea.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机区域数据组件。一个房间场景内可放置任意数量的 CameraArea,
|
||||
/// 每个区域独立定义限位范围、可视边界与混合配置。
|
||||
///
|
||||
/// 运行时由 <see cref="CameraStateController"/> 管理:
|
||||
/// - <c>_dedicatedCamera</c> 为空 → 使用 Persistent 场景中的两台全局 VCam 交替承接,
|
||||
/// 减少场景内 VCam 数量,相机参数统一由全局 VCam 配置。
|
||||
/// - <c>_dedicatedCamera</c> 不为空 → 激活该专有 VCam(优先级高于全局 VCam),
|
||||
/// 适用于需要独特相机参数(FOV / Offset / 阻尼)的特殊区域。
|
||||
/// </summary>
|
||||
public class CameraArea : MonoBehaviour
|
||||
{
|
||||
[Header("限位区域")]
|
||||
[Tooltip("定义相机移动边界的 PolygonCollider2D(通常挂载在子节点 AreaBoundary 上)。\n" +
|
||||
"会被赋给全局 VCam 的 CinemachineConfiner2D.BoundingShape2D。")]
|
||||
[SerializeField] private PolygonCollider2D _confinerCollider;
|
||||
|
||||
[Header("可视区域(透视相机)")]
|
||||
[Tooltip("摄像机应显示的最大可视矩形(世界坐标)。\n" +
|
||||
"Scene 视图中可直接拖拽四条边编辑,然后点击 Inspector 中的\n" +
|
||||
"「从可视区域更新限位区域(透视)」按钮将其换算为限位多边形。")]
|
||||
[SerializeField] private Rect _visibleBounds = new Rect(-12f, -6f, 24f, 12f);
|
||||
|
||||
[Tooltip("摄像机到场景平面(Z = 0)的垂直距离,用于透视视口尺寸计算。\n" +
|
||||
"留 0 时自动取 transform.position.z 的绝对值(推荐)。")]
|
||||
[SerializeField] private float _cameraDepth = 0f;
|
||||
|
||||
[Header("混合配置")]
|
||||
[SerializeField] private CameraBlendProfileSO _blendProfile;
|
||||
|
||||
[Header("专有虚拟相机(可选)")]
|
||||
[Tooltip("为空时由全局双 VCam 交替过渡(推荐,节省 VCam 数量)。\n" +
|
||||
"不为空时激活此专有 CinemachineCamera,优先级高于全局 VCam。\n" +
|
||||
"适用于需要独特 FOV / Noise / LookAt 等参数的特殊区域。")]
|
||||
[SerializeField] private CinemachineCamera _dedicatedCamera;
|
||||
|
||||
[Tooltip("专有 VCam 激活时使用的优先级,须高于全局 VCam 的 _globalActivePriority(默认 10)。")]
|
||||
[SerializeField] private int _dedicatedPriority = 20;
|
||||
|
||||
// ── 公开属性 ──────────────────────────────────────────────────────────
|
||||
|
||||
public PolygonCollider2D ConfinerCollider => _confinerCollider;
|
||||
public CameraBlendProfileSO BlendProfile => _blendProfile;
|
||||
public Rect VisibleBounds => _visibleBounds;
|
||||
public bool HasDedicated => _dedicatedCamera != null;
|
||||
public CinemachineCamera DedicatedCamera => _dedicatedCamera;
|
||||
public int DedicatedPriority => _dedicatedPriority;
|
||||
|
||||
/// <summary>
|
||||
/// 摄像机到场景平面的有效深度(用于透视视口换算)。
|
||||
/// _cameraDepth > 0 时使用配置值,否则自动读取 |transform.position.z|,再兜底 10。
|
||||
/// </summary>
|
||||
public float CameraDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cameraDepth > 0f) return _cameraDepth;
|
||||
float z = Mathf.Abs(transform.position.z);
|
||||
return z > 0.01f ? z : 10f;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gizmo ────────────────────────────────────────────────────────────
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
// 黄色:可视区域
|
||||
Vector3 center = new Vector3(_visibleBounds.center.x, _visibleBounds.center.y, 0f);
|
||||
Vector3 size = new Vector3(_visibleBounds.width, _visibleBounds.height, 0.01f);
|
||||
|
||||
Gizmos.color = new Color(1f, 0.85f, 0.15f, 0.10f);
|
||||
Gizmos.DrawCube(center, size);
|
||||
Gizmos.color = new Color(1f, 0.85f, 0.15f, 0.90f);
|
||||
Gizmos.DrawWireCube(center, size);
|
||||
|
||||
// 青色:专有 VCam 指示线
|
||||
if (_dedicatedCamera != null)
|
||||
{
|
||||
Gizmos.color = new Color(0.2f, 1f, 0.8f, 0.8f);
|
||||
Gizmos.DrawLine(transform.position,
|
||||
_dedicatedCamera.transform.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/CameraArea.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/CameraArea.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ebf3921efccfad429f451251738375e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
Assets/_Game/Scripts/Camera/CameraBlendProfileSO.cs
Normal file
25
Assets/_Game/Scripts/Camera/CameraBlendProfileSO.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Camera/BlendProfile")]
|
||||
public class CameraBlendProfileSO : ScriptableObject
|
||||
{
|
||||
public CinemachineBlendDefinition.Styles Style = CinemachineBlendDefinition.Styles.EaseInOut;
|
||||
public float BlendTime = 0.5f;
|
||||
[Tooltip("Style = Custom 时使用")]
|
||||
public AnimationCurve CustomCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
/// <summary>转换为 Cinemachine 混合定义。</summary>
|
||||
public CinemachineBlendDefinition ToBlendDefinition()
|
||||
{
|
||||
return new CinemachineBlendDefinition
|
||||
{
|
||||
Style = this.Style,
|
||||
Time = this.BlendTime,
|
||||
CustomCurve = this.Style == CinemachineBlendDefinition.Styles.Custom ? CustomCurve : null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/CameraBlendProfileSO.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/CameraBlendProfileSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04f7b183b6d364d4ea85283d30339db7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/_Game/Scripts/Camera/CameraConfigSO.cs
Normal file
22
Assets/_Game/Scripts/Camera/CameraConfigSO.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Camera/CameraConfig")]
|
||||
public class CameraConfigSO : ScriptableObject
|
||||
{
|
||||
[Header("跟随")]
|
||||
public float FollowDamping = 0.15f;
|
||||
public float LookAheadTime = 0.3f;
|
||||
public float LookAheadSmoothing = 0.1f;
|
||||
public Vector2 DeadZoneSize = new Vector2(1f, 0.5f);
|
||||
public Vector2 SoftZoneSize = new Vector2(2.5f, 2f);
|
||||
|
||||
[Header("偏移")]
|
||||
public float LookDownOffset = -1.5f;
|
||||
public float LookUpOffset = 1.5f;
|
||||
|
||||
[Header("画面抖动默认强度")]
|
||||
public float DefaultImpulseStrength = 0.3f;
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/CameraConfigSO.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/CameraConfigSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b358a30ac16c6a34fb673ede0a288e48
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
180
Assets/_Game/Scripts/Camera/CameraStateController.cs
Normal file
180
Assets/_Game/Scripts/Camera/CameraStateController.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
using BaseGames.Core;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机状态单例控制器。须放置在 Persistent 场景中。
|
||||
///
|
||||
/// 支持两种相机切换模式:
|
||||
/// 1. 全局双 VCam 模式(推荐):<see cref="SwitchArea"/>
|
||||
/// 两台全局 CinemachineCamera(<c>_vcamA</c> / <c>_vcamB</c>)交替承接各区域,
|
||||
/// 通过优先级 ping-pong 触发 Cinemachine 混合过渡。场景内无需每个区域都放置 VCam。
|
||||
///
|
||||
/// 2. 专有 VCam 模式:<see cref="SwitchArea"/>(区域含 dedicatedCamera 时自动使用)
|
||||
/// 激活该区域专属的 CinemachineCamera(优先级 > 全局 VCam),
|
||||
/// 适用于需要独特相机参数的特殊区域。
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-100)]
|
||||
public class CameraStateController : MonoBehaviour, ICameraService
|
||||
{
|
||||
[Header("引用")]
|
||||
[SerializeField] private CinemachineBrain _brain;
|
||||
[SerializeField] private CinemachineImpulseSource _impulseSource;
|
||||
|
||||
[Header("全局双 VCam(Persistent 场景中放置两台通用虚拟相机)")]
|
||||
[Tooltip("两台 VCam 交替承接各相机区域,通过优先级 ping-pong 触发混合过渡。\n" +
|
||||
"须各自挂载 CinemachineCamera + CinemachineConfiner2D;\n" +
|
||||
"Follow 指向 Player/CameraFollowTarget(或运行时调用 SetFollowTarget 赋值)。")]
|
||||
[SerializeField] private CinemachineCamera _vcamA;
|
||||
[SerializeField] private CinemachineCamera _vcamB;
|
||||
|
||||
[Tooltip("全局 VCam 激活时的优先级(非活跃时为 0)。专有 VCam 的 _dedicatedPriority 须高于此值。")]
|
||||
[SerializeField] private int _globalActivePriority = 10;
|
||||
|
||||
[Header("默认混合配置")]
|
||||
[SerializeField] private CameraBlendProfileSO _defaultBlendProfile;
|
||||
|
||||
// ── 状态 ──────────────────────────────────────────────────────────────
|
||||
private int _activeSlot = -1; // -1 = 未初始化;0 = A;1 = B
|
||||
private CinemachineCamera _activeDedicatedCam;
|
||||
private CinemachineConfiner2D _confinerA;
|
||||
private CinemachineConfiner2D _confinerB;
|
||||
|
||||
// ── Lifecycle ────────────────────────────────────────────────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (ServiceLocator.GetOrDefault<ICameraService>() != null) { Destroy(gameObject); return; }
|
||||
ServiceLocator.Register<ICameraService>(this);
|
||||
|
||||
// 缓存 Confiner 引用
|
||||
if (_vcamA != null) _confinerA = _vcamA.GetComponent<CinemachineConfiner2D>();
|
||||
if (_vcamB != null) _confinerB = _vcamB.GetComponent<CinemachineConfiner2D>();
|
||||
|
||||
// 初始两台 VCam 均处于非活跃优先级
|
||||
if (_vcamA != null) _vcamA.Priority = 0;
|
||||
if (_vcamB != null) _vcamB.Priority = 0;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ServiceLocator.Unregister<ICameraService>(this);
|
||||
}
|
||||
|
||||
// ── 公开 API ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 切换到目标相机区域。
|
||||
/// <list type="bullet">
|
||||
/// <item>区域有专有 VCam → 激活它(高优先级),全局 VCam 保持当前状态。</item>
|
||||
/// <item>区域无专有 VCam → 配置非活跃全局 VCam,ping-pong 切换优先级触发混合。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public void SwitchArea(CameraArea targetArea)
|
||||
{
|
||||
if (targetArea == null) return;
|
||||
|
||||
ApplyBlendProfile(targetArea.BlendProfile ?? _defaultBlendProfile);
|
||||
|
||||
if (targetArea.HasDedicated)
|
||||
ActivateDedicated(targetArea);
|
||||
else
|
||||
ActivateGlobalSlot(targetArea);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行时为两台全局 VCam 统一设置跟随目标(如 Player/CameraFollowTarget)。
|
||||
/// 可在 Player 生成后由任意系统调用。
|
||||
/// </summary>
|
||||
public void SetFollowTarget(Transform followTarget)
|
||||
{
|
||||
if (_vcamA != null) _vcamA.Follow = followTarget;
|
||||
if (_vcamB != null) _vcamB.Follow = followTarget;
|
||||
}
|
||||
|
||||
/// <summary>触发屏幕抖动。</summary>
|
||||
public void TriggerImpulse(Vector3 velocity)
|
||||
{
|
||||
if (_impulseSource != null) _impulseSource.GenerateImpulse(velocity);
|
||||
}
|
||||
|
||||
/// <summary>以默认强度触发屏幕抖动。</summary>
|
||||
public void TriggerImpulse(float strength = 0.3f)
|
||||
=> TriggerImpulse(Vector3.down * strength);
|
||||
|
||||
// ── 内部方法 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>激活区域的专有 VCam(高优先级)。</summary>
|
||||
private void ActivateDedicated(CameraArea area)
|
||||
{
|
||||
// 降低前一个专有 VCam(若与新的不同)
|
||||
if (_activeDedicatedCam != null && _activeDedicatedCam != area.DedicatedCamera)
|
||||
_activeDedicatedCam.Priority = 0;
|
||||
|
||||
_activeDedicatedCam = area.DedicatedCamera;
|
||||
_activeDedicatedCam.Priority = area.DedicatedPriority;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用全局 VCam ping-pong 切换到新区域。
|
||||
/// 配置非活跃 VCam 的 Confiner → 提升其优先级 → 降低旧 VCam 优先级。
|
||||
/// Cinemachine Brain 检测到优先级变化后自动触发混合。
|
||||
/// </summary>
|
||||
private void ActivateGlobalSlot(CameraArea area)
|
||||
{
|
||||
// 收回专有 VCam
|
||||
if (_activeDedicatedCam != null)
|
||||
{
|
||||
_activeDedicatedCam.Priority = 0;
|
||||
_activeDedicatedCam = null;
|
||||
}
|
||||
|
||||
bool noVCams = _vcamA == null && _vcamB == null;
|
||||
if (noVCams)
|
||||
{
|
||||
Debug.LogWarning("[CameraStateController] 全局 VCam A / B 均未绑定,无法切换相机区域。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 首次调用:直接激活 VCamA(场景淡入阶段,无需混合动画)
|
||||
if (_activeSlot < 0)
|
||||
{
|
||||
var cam = _vcamA ?? _vcamB;
|
||||
var confiner = _vcamA != null ? _confinerA : _confinerB;
|
||||
ConfigureSlot(cam, confiner, area);
|
||||
cam.Priority = _globalActivePriority;
|
||||
_activeSlot = _vcamA != null ? 0 : 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ping-pong:配置非活跃槽 → 升级其优先级 → 降低活跃槽优先级
|
||||
bool nextIsA = _activeSlot != 0;
|
||||
var inactiveCam = nextIsA ? _vcamA : _vcamB;
|
||||
var activeCam = nextIsA ? _vcamB : _vcamA;
|
||||
var inactiveConfiner = nextIsA ? _confinerA : _confinerB;
|
||||
|
||||
// 只有一台 VCam 时降级处理(仍能工作,但无混合动画)
|
||||
if (inactiveCam == null) inactiveCam = activeCam;
|
||||
|
||||
ConfigureSlot(inactiveCam, inactiveConfiner, area);
|
||||
inactiveCam.Priority = _globalActivePriority;
|
||||
activeCam.Priority = 0;
|
||||
_activeSlot = nextIsA ? 0 : 1;
|
||||
}
|
||||
|
||||
private static void ConfigureSlot(
|
||||
CinemachineCamera vcam, CinemachineConfiner2D confiner, CameraArea area)
|
||||
{
|
||||
if (confiner != null && area.ConfinerCollider != null)
|
||||
confiner.BoundingShape2D = area.ConfinerCollider;
|
||||
}
|
||||
|
||||
private void ApplyBlendProfile(CameraBlendProfileSO profile)
|
||||
{
|
||||
if (_brain != null && profile != null)
|
||||
_brain.DefaultBlend = profile.ToBlendDefinition();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/CameraStateController.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/CameraStateController.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49f718c655d71394ea13e312a2dd9eed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
46
Assets/_Game/Scripts/Camera/CameraTriggerZone.cs
Normal file
46
Assets/_Game/Scripts/Camera/CameraTriggerZone.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机区域切换触发器。玩家进入时通知 <see cref="CameraStateController"/> 切换到目标 <see cref="CameraArea"/>。
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
public class CameraTriggerZone : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CameraArea _targetArea;
|
||||
[SerializeField] private string _playerTag = "Player";
|
||||
|
||||
private BoxCollider2D _collider;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_collider = GetComponent<BoxCollider2D>();
|
||||
_collider.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (!other.CompareTag(_playerTag)) return;
|
||||
|
||||
var service = ServiceLocator.GetOrDefault<ICameraService>();
|
||||
if (service == null) return;
|
||||
|
||||
if (_targetArea != null)
|
||||
service.SwitchArea(_targetArea);
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (_collider == null) _collider = GetComponent<BoxCollider2D>();
|
||||
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.25f);
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.DrawCube(_collider.offset, _collider.size);
|
||||
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.8f);
|
||||
Gizmos.DrawWireCube(_collider.offset, _collider.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/CameraTriggerZone.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/CameraTriggerZone.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 108d2b73047255f44a823dbcdea4a7fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
15
Assets/_Game/Scripts/Camera/ICameraService.cs
Normal file
15
Assets/_Game/Scripts/Camera/ICameraService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机服务接口。供 CameraTriggerZone 等调用,
|
||||
/// 通过 ServiceLocator.Get<ICameraService>() 访问,无需直接依赖 CameraStateController。
|
||||
/// </summary>
|
||||
public interface ICameraService
|
||||
{
|
||||
/// <summary>
|
||||
/// 切换到目标相机区域。
|
||||
/// 区域有专有 VCam 时激活它(高优先级);无专有 VCam 时由全局双 VCam 交替承接。
|
||||
/// </summary>
|
||||
void SwitchArea(CameraArea targetArea);
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/ICameraService.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/ICameraService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94f7141340a22a54aa504d7c6a09eeb3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
71
Assets/_Game/Scripts/Camera/RoomCamera.cs
Normal file
71
Assets/_Game/Scripts/Camera/RoomCamera.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 单房间虚拟相机。激活时提升优先级,停用时降为 0。
|
||||
/// 挂载在每个房间的 CinemachineCamera GameObject 上。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CinemachineCamera))]
|
||||
public class RoomCamera : MonoBehaviour
|
||||
{
|
||||
[Header("房间设置")]
|
||||
[SerializeField] private RoomVisibleArea _visibleArea;
|
||||
[SerializeField] private Vector2 _cameraOffset = Vector2.zero;
|
||||
[SerializeField] private CameraBlendProfileSO _blendProfile;
|
||||
[SerializeField] private int _activePriority = 15;
|
||||
|
||||
[Header("可视区域(透视相机)")]
|
||||
[Tooltip("摄像机应显示的最大可视矩形(世界坐标)。\n" +
|
||||
"在 Scene 视图中可直接拖拽四条边编辑,然后点击 Inspector 中的\n" +
|
||||
"「从可视区域更新限位区域」按钮将其换算为 CinemachineConfiner2D 所需的限位多边形。")]
|
||||
[SerializeField] private Rect _visibleBounds = new Rect(-12f, -6f, 24f, 12f);
|
||||
|
||||
[Tooltip("摄像机到场景平面(Z = 0)的垂直距离,用于透视视口尺寸计算。\n" +
|
||||
"留 0 时自动取 transform.position.z 的绝对值(推荐)。")]
|
||||
[SerializeField] private float _cameraDepth = 0f;
|
||||
|
||||
private CinemachineCamera _vcam;
|
||||
|
||||
private void Awake() => _vcam = GetComponent<CinemachineCamera>();
|
||||
private void OnEnable() => _vcam.Priority = _activePriority;
|
||||
private void OnDisable() => _vcam.Priority = 0;
|
||||
|
||||
public PolygonCollider2D ConfinerCollider => _visibleArea?.Collider;
|
||||
public Vector2 CameraOffset => _cameraOffset;
|
||||
public CameraBlendProfileSO BlendProfile => _blendProfile;
|
||||
public Rect VisibleBounds => _visibleBounds;
|
||||
|
||||
/// <summary>
|
||||
/// 摄像机到场景平面的有效深度。
|
||||
/// _cameraDepth > 0 时使用配置值,否则自动读取 |transform.position.z|,再兜底 10。
|
||||
/// </summary>
|
||||
public float CameraDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cameraDepth > 0f) return _cameraDepth;
|
||||
float z = Mathf.Abs(transform.position.z);
|
||||
return z > 0.01f ? z : 10f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>在 CameraStateController 管理的激活流程中调用。</summary>
|
||||
public void Activate() => gameObject.SetActive(true);
|
||||
public void Deactivate() => gameObject.SetActive(false);
|
||||
|
||||
// ── Gizmo ──────────────────────────────────────────────────────────────
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
// 黄色:可视区域(设计意图——玩家在此房间内的最大可见范围)
|
||||
Vector3 center = new Vector3(_visibleBounds.center.x, _visibleBounds.center.y, 0f);
|
||||
Vector3 size = new Vector3(_visibleBounds.width, _visibleBounds.height, 0.01f);
|
||||
|
||||
Gizmos.color = new Color(1f, 0.85f, 0.15f, 0.10f);
|
||||
Gizmos.DrawCube(center, size);
|
||||
Gizmos.color = new Color(1f, 0.85f, 0.15f, 0.90f);
|
||||
Gizmos.DrawWireCube(center, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/RoomCamera.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/RoomCamera.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af7e12583264b8c4da8dcd69df274793
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
Assets/_Game/Scripts/Camera/RoomVisibleArea.cs
Normal file
30
Assets/_Game/Scripts/Camera/RoomVisibleArea.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记房间的可见区域(多边形)。供 CinemachineConfiner2D 使用。
|
||||
/// [ExecuteAlways] 确保编辑器中碰撞体立即更新。
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(PolygonCollider2D))]
|
||||
public class RoomVisibleArea : MonoBehaviour
|
||||
{
|
||||
private PolygonCollider2D _collider;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_collider = GetComponent<PolygonCollider2D>();
|
||||
_collider.isTrigger = true;
|
||||
}
|
||||
|
||||
public PolygonCollider2D Collider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_collider == null) _collider = GetComponent<PolygonCollider2D>();
|
||||
return _collider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Camera/RoomVisibleArea.cs.meta
Normal file
11
Assets/_Game/Scripts/Camera/RoomVisibleArea.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38af2eabab7039c4a919181e4c507d12
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user