引入 Cinemachine 和 CameraModule,添加摄像机跟随缓动效果
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a15f5e9feb85bd4eab52916ae709ee3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 306ccbf7cd5a0884e92485e113ae4e1a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.CameraModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机效果配置参数。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct CameraEffectSettings
|
||||
{
|
||||
[Header("跟随缓动 (FollowDamping)")]
|
||||
[Tooltip("X 轴阻尼,值越大跟随后的响应越慢")]
|
||||
[Range(0f, 20f)]
|
||||
public float dampingX;
|
||||
|
||||
[Tooltip("Y 轴阻尼")]
|
||||
[Range(0f, 20f)]
|
||||
public float dampingY;
|
||||
|
||||
[Tooltip("Z 轴阻尼")]
|
||||
[Range(0f, 20f)]
|
||||
public float dampingZ;
|
||||
|
||||
[Header("移动方向偏移 (MovementDirectionOffset)")]
|
||||
[Tooltip("偏移强度系数,值越大偏移越明显")]
|
||||
[Range(0f, 10f)]
|
||||
public float offsetStrength;
|
||||
|
||||
[Tooltip("最大偏移距离(世界单位)")]
|
||||
[Range(0f, 20f)]
|
||||
public float maxOffsetDistance;
|
||||
|
||||
[Tooltip("偏移平滑速度")]
|
||||
[Range(0f, 20f)]
|
||||
public float offsetSmoothing;
|
||||
|
||||
[Tooltip("偏移采样速度低于此值时不产生偏移")]
|
||||
[Range(0f, 5f)]
|
||||
public float offsetDeadZone;
|
||||
|
||||
/// <summary>
|
||||
/// 默认配置:适中的阻尼和偏移参数。
|
||||
/// </summary>
|
||||
public static CameraEffectSettings Default => new CameraEffectSettings
|
||||
{
|
||||
dampingX = 1f,
|
||||
dampingY = 1f,
|
||||
dampingZ = 1f,
|
||||
offsetStrength = 2f,
|
||||
maxOffsetDistance = 3f,
|
||||
offsetSmoothing = 5f,
|
||||
offsetDeadZone = 0.1f,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e0d1682fb702543b5fd918d51c72c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace SepCore.CameraModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机效果类型,用于开关控制。
|
||||
/// </summary>
|
||||
public enum CameraEffectType
|
||||
{
|
||||
/// <summary>
|
||||
/// 跟随缓动 —— 相机跟随目标时的阻尼平滑。
|
||||
/// </summary>
|
||||
FollowDamping,
|
||||
|
||||
/// <summary>
|
||||
/// 移动方向偏移 —— 相机根据目标移动方向产生额外偏移。
|
||||
/// </summary>
|
||||
MovementDirectionOffset,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7676c458713132547a745d42db4cf179
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.CameraModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 相机控制器接口。Base 层定义,Runtime 层(Cinemachine)实现。
|
||||
/// </summary>
|
||||
public interface ICameraController
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前跟随目标。
|
||||
/// </summary>
|
||||
Transform Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置跟随目标。传 null 则停止跟随。
|
||||
/// </summary>
|
||||
void SetTarget(Transform target);
|
||||
|
||||
/// <summary>
|
||||
/// 启用指定效果。
|
||||
/// </summary>
|
||||
void EnableEffect(CameraEffectType effect);
|
||||
|
||||
/// <summary>
|
||||
/// 禁用指定效果。
|
||||
/// </summary>
|
||||
void DisableEffect(CameraEffectType effect);
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定效果是否启用。
|
||||
/// </summary>
|
||||
bool IsEffectEnabled(CameraEffectType effect);
|
||||
|
||||
/// <summary>
|
||||
/// 应用效果配置参数。会立即生效。
|
||||
/// </summary>
|
||||
void ApplySettings(CameraEffectSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// 当前生效的配置参数。
|
||||
/// </summary>
|
||||
CameraEffectSettings CurrentSettings { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c239333c308a174685e7f732738603b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "SepCore.CameraModule.Base",
|
||||
"rootNamespace": "SepCore.CameraModule",
|
||||
"references": [
|
||||
"GUID:363c5eb08ff8e6a439b85e37b8c20d96",
|
||||
"GUID:a04b272484018094ab427253f20c416c"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e6a2ce06de727c44965fbc35958920f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 590eb06964a378441aadf2dae1eebc26
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,217 @@
|
||||
using Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.CameraModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于 Cinemachine 的相机控制器。挂载到场景中的主相机上(主相机需有 CinemachineBrain)。
|
||||
/// 会在主相机同级自动创建 CinemachineVirtualCamera,避免虚拟相机成为被 Brain 驱动相机的子物体。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CinemachineBrain))]
|
||||
public class CinemachineCameraController : MonoBehaviour, ICameraController
|
||||
{
|
||||
[SerializeField] private CameraEffectSettings _settings = CameraEffectSettings.Default;
|
||||
[SerializeField] private Vector3 _baseFollowOffset = new Vector3(0f, 10f, -10f);
|
||||
[SerializeField] private bool _lockRotation = true;
|
||||
[SerializeField] private Vector3 _fixedRotationEuler = new Vector3(90f, 0f, 0f);
|
||||
|
||||
private CinemachineVirtualCamera _virtualCamera;
|
||||
private CinemachineTransposer _transposer;
|
||||
private bool _followDampingEnabled = true;
|
||||
private bool _movementDirectionOffsetEnabled = true;
|
||||
|
||||
private Vector3 _currentAppliedOffset;
|
||||
private Vector3 _previousTargetPosition;
|
||||
private bool _hasTarget;
|
||||
|
||||
public Transform Target { get; private set; }
|
||||
public CameraEffectSettings CurrentSettings => _settings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
CreateVirtualCamera();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ApplyRotationMode();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!_hasTarget || Target == null)
|
||||
return;
|
||||
|
||||
if (_movementDirectionOffsetEnabled)
|
||||
UpdateMovementDirectionOffset();
|
||||
else
|
||||
ResetOffsetSmoothly();
|
||||
|
||||
_previousTargetPosition = Target.position;
|
||||
|
||||
if (_lockRotation)
|
||||
ApplyFixedRotation();
|
||||
}
|
||||
|
||||
public void SetTarget(Transform target)
|
||||
{
|
||||
Target = target;
|
||||
_hasTarget = target != null;
|
||||
|
||||
if (_virtualCamera == null)
|
||||
return;
|
||||
|
||||
_virtualCamera.Follow = target;
|
||||
ApplyRotationMode();
|
||||
|
||||
if (_hasTarget)
|
||||
{
|
||||
_previousTargetPosition = target.position;
|
||||
_currentAppliedOffset = _baseFollowOffset;
|
||||
_transposer.m_FollowOffset = _baseFollowOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableEffect(CameraEffectType effect)
|
||||
{
|
||||
switch (effect)
|
||||
{
|
||||
case CameraEffectType.FollowDamping:
|
||||
_followDampingEnabled = true;
|
||||
ApplyDampingValues();
|
||||
break;
|
||||
case CameraEffectType.MovementDirectionOffset:
|
||||
_movementDirectionOffsetEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableEffect(CameraEffectType effect)
|
||||
{
|
||||
switch (effect)
|
||||
{
|
||||
case CameraEffectType.FollowDamping:
|
||||
_followDampingEnabled = false;
|
||||
ApplyDampingValues();
|
||||
break;
|
||||
case CameraEffectType.MovementDirectionOffset:
|
||||
_movementDirectionOffsetEnabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEffectEnabled(CameraEffectType effect)
|
||||
{
|
||||
return effect switch
|
||||
{
|
||||
CameraEffectType.FollowDamping => _followDampingEnabled,
|
||||
CameraEffectType.MovementDirectionOffset => _movementDirectionOffsetEnabled,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public void ApplySettings(CameraEffectSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
ApplyDampingValues();
|
||||
}
|
||||
|
||||
private void CreateVirtualCamera()
|
||||
{
|
||||
var go = new GameObject("CM_CameraController_VCam");
|
||||
go.transform.SetParent(transform.parent, false);
|
||||
go.transform.rotation = Quaternion.Euler(_fixedRotationEuler);
|
||||
_virtualCamera = go.AddComponent<CinemachineVirtualCamera>();
|
||||
|
||||
_virtualCamera.m_Lens.FieldOfView = 60f;
|
||||
|
||||
_transposer = _virtualCamera.AddCinemachineComponent<CinemachineTransposer>();
|
||||
_transposer.m_BindingMode = CinemachineTransposer.BindingMode.WorldSpace;
|
||||
_transposer.m_FollowOffset = _baseFollowOffset;
|
||||
|
||||
ApplyRotationMode();
|
||||
|
||||
ApplyDampingValues();
|
||||
}
|
||||
|
||||
private void ApplyRotationMode()
|
||||
{
|
||||
if (_virtualCamera == null)
|
||||
return;
|
||||
|
||||
if (_lockRotation)
|
||||
{
|
||||
_virtualCamera.LookAt = null;
|
||||
_virtualCamera.DestroyCinemachineComponent<CinemachineHardLookAt>();
|
||||
ApplyFixedRotation();
|
||||
return;
|
||||
}
|
||||
|
||||
_virtualCamera.LookAt = Target;
|
||||
|
||||
if (_virtualCamera.GetCinemachineComponent<CinemachineHardLookAt>() == null)
|
||||
_virtualCamera.AddCinemachineComponent<CinemachineHardLookAt>();
|
||||
}
|
||||
|
||||
private void ApplyFixedRotation()
|
||||
{
|
||||
if (_virtualCamera == null)
|
||||
return;
|
||||
|
||||
_virtualCamera.transform.rotation = Quaternion.Euler(_fixedRotationEuler);
|
||||
}
|
||||
|
||||
private void ApplyDampingValues()
|
||||
{
|
||||
if (_transposer == null)
|
||||
return;
|
||||
|
||||
if (_followDampingEnabled)
|
||||
{
|
||||
_transposer.m_XDamping = _settings.dampingX;
|
||||
_transposer.m_YDamping = _settings.dampingY;
|
||||
_transposer.m_ZDamping = _settings.dampingZ;
|
||||
}
|
||||
else
|
||||
{
|
||||
_transposer.m_XDamping = 0f;
|
||||
_transposer.m_YDamping = 0f;
|
||||
_transposer.m_ZDamping = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMovementDirectionOffset()
|
||||
{
|
||||
float dt = Time.deltaTime;
|
||||
Vector3 velocity = (Target.position - _previousTargetPosition) / Mathf.Max(dt, 0.0001f);
|
||||
float speed = new Vector3(velocity.x, 0f, velocity.z).magnitude;
|
||||
|
||||
Vector3 targetOffset = _baseFollowOffset;
|
||||
|
||||
if (speed > _settings.offsetDeadZone)
|
||||
{
|
||||
Vector3 dir = new Vector3(velocity.x, 0f, velocity.z).normalized;
|
||||
float offsetAmount = Mathf.Min(speed * _settings.offsetStrength, _settings.maxOffsetDistance);
|
||||
targetOffset += dir * offsetAmount;
|
||||
}
|
||||
|
||||
_currentAppliedOffset = Vector3.Lerp(
|
||||
_currentAppliedOffset,
|
||||
targetOffset,
|
||||
_settings.offsetSmoothing * dt
|
||||
);
|
||||
|
||||
_transposer.m_FollowOffset = _currentAppliedOffset;
|
||||
}
|
||||
|
||||
private void ResetOffsetSmoothly()
|
||||
{
|
||||
_currentAppliedOffset = Vector3.Lerp(
|
||||
_currentAppliedOffset,
|
||||
_baseFollowOffset,
|
||||
_settings.offsetSmoothing * Time.deltaTime
|
||||
);
|
||||
_transposer.m_FollowOffset = _currentAppliedOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72dbf93744f52c94ca50331c526685e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "SepCore.CameraModule",
|
||||
"rootNamespace": "SepCore.CameraModule",
|
||||
"references": [
|
||||
"GUID:363c5eb08ff8e6a439b85e37b8c20d96",
|
||||
"GUID:0e6a2ce06de727c44965fbc35958920f",
|
||||
"GUID:4307f53044263cf4b835bd812fc161a4"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ddd9fc6811c1d14bbde99bd57869070
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
# CameraModule 接入说明
|
||||
|
||||
基于 Cinemachine 2.x 的可复用相机插件,提供跟随缓动、移动方向偏移等基础效果,支持开关控制。
|
||||
|
||||
## 导入步骤
|
||||
|
||||
1. 将 `Assets/Plugins/CameraModule/` 整个目录导入目标项目
|
||||
2. 确保目标项目已安装 `com.unity.cinemachine` 2.x(通过 Package Manager 或 manifest.json 添加)
|
||||
3. 导入后基座项目仍可正常编译和运行——CameraModule 的 asmdef 独立于基座程序集
|
||||
|
||||
## 程序集结构
|
||||
|
||||
```
|
||||
SepCore.CameraModule.Base ← 接口与数据类型(无 Cinemachine 依赖)
|
||||
SepCore.CameraModule ← Cinemachine 实现(依赖 Cinemachine 2.x)
|
||||
```
|
||||
|
||||
- `SepCore.CameraModule.Base` 引用 `UnityGameFramework.Runtime` 和 `SepCore.Base`
|
||||
- `SepCore.CameraModule` 额外引用 `Cinemachine` 和 `SepCore.CameraModule.Base`
|
||||
- 移除 CameraModule 后,基座程序集不受任何影响
|
||||
|
||||
## 场景挂载要求
|
||||
|
||||
1. 在场景主相机上添加 `CinemachineBrain` 组件(如果还没有)
|
||||
2. 在主相机上添加 `CinemachineCameraController` 组件
|
||||
3. 控制器会自动创建 `CinemachineVirtualCamera` 子对象
|
||||
|
||||
## API 初始化方式
|
||||
|
||||
```csharp
|
||||
using SepCore.CameraModule;
|
||||
|
||||
// 获取控制器引用(通常通过 Inspector 拖拽或 GetComponent)
|
||||
var controller = GetComponent<CinemachineCameraController>();
|
||||
|
||||
// 设置跟随目标
|
||||
controller.SetTarget(targetTransform);
|
||||
|
||||
// 调整效果参数
|
||||
var settings = CameraEffectSettings.Default;
|
||||
settings.dampingX = 2f;
|
||||
settings.dampingY = 2f;
|
||||
settings.dampingZ = 2f;
|
||||
settings.offsetStrength = 3f;
|
||||
settings.maxOffsetDistance = 5f;
|
||||
controller.ApplySettings(settings);
|
||||
|
||||
// 开关控制
|
||||
controller.DisableEffect(CameraEffectType.FollowDamping); // 关闭缓动
|
||||
controller.EnableEffect(CameraEffectType.FollowDamping); // 开启缓动
|
||||
controller.DisableEffect(CameraEffectType.MovementDirectionOffset); // 关闭方向偏移
|
||||
```
|
||||
|
||||
## CameraEffectSettings 参数说明
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `dampingX/Y/Z` | float | 1.0 | 跟随阻尼,值越大响应越慢。设为 0 则无缓动 |
|
||||
| `offsetStrength` | float | 2.0 | 移动方向偏移强度系数 |
|
||||
| `maxOffsetDistance` | float | 3.0 | 最大偏移距离(世界单位) |
|
||||
| `offsetSmoothing` | float | 5.0 | 偏移平滑速度 |
|
||||
| `offsetDeadZone` | float | 0.1 | 速度低于此值时不产生偏移 |
|
||||
|
||||
## 效果开关
|
||||
|
||||
通过 `EnableEffect` / `DisableEffect` 控制:
|
||||
|
||||
- `CameraEffectType.FollowDamping` — 跟随缓动。关闭后 damping 设为 0,相机即时跟随
|
||||
- `CameraEffectType.MovementDirectionOffset` — 移动方向偏移。关闭后 FollowOffset 平滑回归基础值
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e84e48de48841ab4b82efdd941a87f2e
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user