1
This commit is contained in:
@@ -1673,7 +1673,7 @@ GameObject:
|
|||||||
- component: {fileID: 609157176}
|
- component: {fileID: 609157176}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_HasEditorInfo: 1
|
m_HasEditorInfo: 1
|
||||||
m_Name: GameObject
|
m_Name: "\u8FFD\u51FB"
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
@@ -2249,6 +2249,8 @@ MonoBehaviour:
|
|||||||
lockYAxis: 1
|
lockYAxis: 1
|
||||||
smoothRotation: 1
|
smoothRotation: 1
|
||||||
rotationSpeed: 1
|
rotationSpeed: 1
|
||||||
|
snapAngle: 0.5
|
||||||
|
deadZone: 0.2
|
||||||
--- !u!114 &743379413
|
--- !u!114 &743379413
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -3949,6 +3951,38 @@ SpriteRenderer:
|
|||||||
m_WasSpriteAssigned: 1
|
m_WasSpriteAssigned: 1
|
||||||
m_MaskInteraction: 0
|
m_MaskInteraction: 0
|
||||||
m_SpriteSortPoint: 0
|
m_SpriteSortPoint: 0
|
||||||
|
--- !u!1 &2024604079
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 7
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 2024604080}
|
||||||
|
m_Layer: 0
|
||||||
|
m_HasEditorInfo: 1
|
||||||
|
m_Name: GameObject (1)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &2024604080
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2024604079}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &2116328956
|
--- !u!1 &2116328956
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -4488,3 +4522,4 @@ SceneRoots:
|
|||||||
- {fileID: 8914096}
|
- {fileID: 8914096}
|
||||||
- {fileID: 444470370}
|
- {fileID: 444470370}
|
||||||
- {fileID: 609157177}
|
- {fileID: 609157177}
|
||||||
|
- {fileID: 2024604080}
|
||||||
|
|||||||
122
unity/Assets/camera/CameraBounds.cs
Normal file
122
unity/Assets/camera/CameraBounds.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace IndianOceanAssets.Engine2_5D
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 摄像机边界(空气墙)—— 框定摄像机可移动的矩形范围。
|
||||||
|
/// 摄像机碰到边界后停下,玩家仍可继续走。
|
||||||
|
///
|
||||||
|
/// 原理:在 LateUpdate(晚于 CameraFollow 的 Update)把摄像机位置钳制到
|
||||||
|
/// 以本物体为中心、size 为尺寸的矩形内(仅 XZ 平面,Y 不限制)。
|
||||||
|
/// [DefaultExecutionOrder(10000)] 保证本脚本在其它 LateUpdate 之后执行,
|
||||||
|
/// 即使以后把 CameraFollow 改成 LateUpdate 也能正确钳制。
|
||||||
|
///
|
||||||
|
/// 为什么不会抖:
|
||||||
|
/// CameraFollow 每帧用 Lerp 把摄像机往目标推(可能推出边界),
|
||||||
|
/// 本脚本随后把它钳回边界。边界是固定直线,钳制结果每帧一致 → 稳定不抖。
|
||||||
|
///
|
||||||
|
/// 用法:
|
||||||
|
/// 1. 场景中创建空物体,挂上此脚本
|
||||||
|
/// 2. 把空物体放到摄像机活动范围的中心点
|
||||||
|
/// 3. 设置 size(X=宽度,Y=深度,以本物体位置为中心)
|
||||||
|
/// 4. targetCamera 留空则自动用 Camera.main
|
||||||
|
/// 5. Scene 视图会显示青色线框,方便对齐
|
||||||
|
/// </summary>
|
||||||
|
[DefaultExecutionOrder(10000)]
|
||||||
|
public class CameraBounds : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("要约束的摄像机(留空则使用 Camera.main)")]
|
||||||
|
[SerializeField] private Camera targetCamera;
|
||||||
|
|
||||||
|
[Tooltip("边界尺寸(X=宽度,Y=深度,以本物体位置为中心)")]
|
||||||
|
[SerializeField] private Vector2 size = new Vector2(30f, 30f);
|
||||||
|
|
||||||
|
[Tooltip("Scene 视图是否显示边界线框")]
|
||||||
|
[SerializeField] private bool drawGizmo = true;
|
||||||
|
|
||||||
|
[Tooltip("线框颜色")]
|
||||||
|
[SerializeField] private Color gizmoColor = Color.cyan;
|
||||||
|
|
||||||
|
private Transform _camTransform;
|
||||||
|
private bool _resolved;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
ResolveCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveCamera()
|
||||||
|
{
|
||||||
|
var cam = targetCamera != null ? targetCamera : Camera.main;
|
||||||
|
if (cam != null)
|
||||||
|
{
|
||||||
|
_camTransform = cam.transform;
|
||||||
|
_resolved = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_resolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
if (!_resolved || _camTransform == null)
|
||||||
|
{
|
||||||
|
ResolveCamera();
|
||||||
|
if (_camTransform == null) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 pos = _camTransform.position;
|
||||||
|
Vector3 center = transform.position;
|
||||||
|
float minX = center.x - size.x * 0.5f;
|
||||||
|
float maxX = center.x + size.x * 0.5f;
|
||||||
|
float minZ = center.z - size.y * 0.5f;
|
||||||
|
float maxZ = center.z + size.y * 0.5f;
|
||||||
|
|
||||||
|
_camTransform.position = new Vector3(
|
||||||
|
Mathf.Clamp(pos.x, minX, maxX),
|
||||||
|
pos.y,
|
||||||
|
Mathf.Clamp(pos.z, minZ, maxZ)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 把世界坐标钳制到边界内(仅 XZ,Y 不变)。供外部调用。
|
||||||
|
/// </summary>
|
||||||
|
public Vector3 ClampPosition(Vector3 pos)
|
||||||
|
{
|
||||||
|
Vector3 center = transform.position;
|
||||||
|
return new Vector3(
|
||||||
|
Mathf.Clamp(pos.x, center.x - size.x * 0.5f, center.x + size.x * 0.5f),
|
||||||
|
pos.y,
|
||||||
|
Mathf.Clamp(pos.z, center.z - size.y * 0.5f, center.z + size.y * 0.5f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDrawGizmos()
|
||||||
|
{
|
||||||
|
if (!drawGizmo) return;
|
||||||
|
|
||||||
|
Vector3 c = transform.position;
|
||||||
|
float hx = size.x * 0.5f;
|
||||||
|
float hz = size.y * 0.5f;
|
||||||
|
Vector3 p1 = new Vector3(c.x - hx, c.y, c.z - hz);
|
||||||
|
Vector3 p2 = new Vector3(c.x + hx, c.y, c.z - hz);
|
||||||
|
Vector3 p3 = new Vector3(c.x + hx, c.y, c.z + hz);
|
||||||
|
Vector3 p4 = new Vector3(c.x - hx, c.y, c.z + hz);
|
||||||
|
|
||||||
|
Gizmos.color = gizmoColor;
|
||||||
|
Gizmos.DrawLine(p1, p2);
|
||||||
|
Gizmos.DrawLine(p2, p3);
|
||||||
|
Gizmos.DrawLine(p3, p4);
|
||||||
|
Gizmos.DrawLine(p4, p1);
|
||||||
|
|
||||||
|
// 中心十字
|
||||||
|
Gizmos.color = new Color(gizmoColor.r, gizmoColor.g, gizmoColor.b, 0.6f);
|
||||||
|
const float k = 0.5f;
|
||||||
|
Gizmos.DrawLine(c - new Vector3(k, 0, 0), c + new Vector3(k, 0, 0));
|
||||||
|
Gizmos.DrawLine(c - new Vector3(0, 0, k), c + new Vector3(0, 0, k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
unity/Assets/camera/CameraBounds.cs.meta
Normal file
11
unity/Assets/camera/CameraBounds.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: DylKsyylBn7lcA2y8oKcMWusHhL4i0Lo5tk6EFfHLt0CelquVgviXtA=
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -3,25 +3,44 @@ using UnityEngine;
|
|||||||
namespace IndianOceanAssets.Engine2_5D
|
namespace IndianOceanAssets.Engine2_5D
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 场景物件面向摄像机脚本(饥荒风格)。
|
/// 场景物件面向摄像机脚本(饥荒风格)v2。
|
||||||
/// 让物体始终朝摄像机方向旋转,默认只绕 Y 轴(保持竖直不倒)。
|
/// 让物体始终朝摄像机方向旋转,默认只绕 Y 轴(保持竖直不倒)。
|
||||||
///
|
///
|
||||||
|
/// 抖动修复(v2):
|
||||||
|
/// 旧版平滑旋转用 Quaternion.Slerp(rot, target, speed * deltaTime),
|
||||||
|
/// 它永远无法真正到达目标(每帧只走剩余距离的几分之一),叠加 deltaTime
|
||||||
|
/// 逐帧波动,会持续产生微小旋转 → 看起来在抖动。若摄像机本身也用 Lerp
|
||||||
|
/// 跟随(CameraFollow 永不收敛),两者叠加更明显。
|
||||||
|
///
|
||||||
|
/// 修复要点:
|
||||||
|
/// 1. 指数平滑 t = 1 - exp(-speed * dt),帧率无关,比 speed * dt 更稳定。
|
||||||
|
/// 2. 剩余角度 ≤ snapAngle 时直接吸附到目标,杜绝永不收敛的微旋转。
|
||||||
|
/// 3. 已吸附后,目标方向在 deadZone 内的微小漂移(摄像机/物件微抖)一律忽略,
|
||||||
|
/// 只有方向变化超过 deadZone 才重新吸附 → 摄像机静止时物件彻底不动。
|
||||||
|
///
|
||||||
/// 用法:挂到需要面向摄像机的精灵/物件上即可。
|
/// 用法:挂到需要面向摄像机的精灵/物件上即可。
|
||||||
/// 适合:树木、岩石、角色、道具等 2D 精灵在 3D 场景中的朝向修正。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BillboardSprite : MonoBehaviour
|
public class BillboardSprite : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Tooltip("只绕Y轴旋转(饥荒风格,保持竖直不倒)。关闭则完全面向摄像机。")]
|
[Tooltip("只绕Y轴旋转(饥荒风格,保持竖直不倒)。关闭则完全面向摄像机。")]
|
||||||
[SerializeField] private bool lockYAxis = true;
|
[SerializeField] private bool lockYAxis = true;
|
||||||
|
|
||||||
[Tooltip("平滑旋转(lerp过渡),关闭则瞬间转向")]
|
[Tooltip("平滑旋转(过渡),关闭则瞬间转向")]
|
||||||
[SerializeField] private bool smoothRotation = false;
|
[SerializeField] private bool smoothRotation = false;
|
||||||
|
|
||||||
[Tooltip("平滑旋转速度")]
|
[Tooltip("平滑旋转速度(越大转得越快)")]
|
||||||
[SerializeField] private float rotationSpeed = 8f;
|
[SerializeField] private float rotationSpeed = 8f;
|
||||||
|
|
||||||
|
[Tooltip("剩余角度小于此值时直接吸附到目标,避免永不收敛的微旋转(主要抖动来源)。")]
|
||||||
|
[SerializeField] private float snapAngle = 0.5f;
|
||||||
|
|
||||||
|
[Tooltip("已吸附后,目标方向在此角度内的微小漂移视为噪声忽略(过滤摄像机微抖)。")]
|
||||||
|
[SerializeField] private float deadZone = 0.2f;
|
||||||
|
|
||||||
private Camera _mainCamera;
|
private Camera _mainCamera;
|
||||||
private Transform _camTransform;
|
private Transform _camTransform;
|
||||||
|
private Quaternion _convergedRot;
|
||||||
|
private bool _hasConverged;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
@@ -50,11 +69,35 @@ namespace IndianOceanAssets.Engine2_5D
|
|||||||
|
|
||||||
Quaternion targetRot = Quaternion.LookRotation(dir);
|
Quaternion targetRot = Quaternion.LookRotation(dir);
|
||||||
|
|
||||||
if (smoothRotation)
|
if (!smoothRotation)
|
||||||
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot,
|
{
|
||||||
rotationSpeed * Time.deltaTime);
|
|
||||||
else
|
|
||||||
transform.rotation = targetRot;
|
transform.rotation = targetRot;
|
||||||
|
_convergedRot = targetRot;
|
||||||
|
_hasConverged = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 平滑旋转 ----
|
||||||
|
float angleToTarget = Quaternion.Angle(transform.rotation, targetRot);
|
||||||
|
|
||||||
|
if (angleToTarget <= snapAngle)
|
||||||
|
{
|
||||||
|
// 已接近目标。仅当目标相对上次吸附转了超过 deadZone 才重新吸附,
|
||||||
|
// 否则视为噪声保持不动(摄像机静止时物件不抖)
|
||||||
|
if (!_hasConverged || Quaternion.Angle(targetRot, _convergedRot) > deadZone)
|
||||||
|
{
|
||||||
|
transform.rotation = targetRot;
|
||||||
|
_convergedRot = targetRot;
|
||||||
|
_hasConverged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 还差较远,指数平滑趋近(帧率无关,比 speed*deltaTime 更稳)
|
||||||
|
float t = 1f - Mathf.Exp(-rotationSpeed * Time.deltaTime);
|
||||||
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, t);
|
||||||
|
_hasConverged = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user