2025-11-13 16:24:49 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
namespace CharacterPreview
|
|
|
|
|
|
{
|
2025-11-18 18:45:14 +08:00
|
|
|
|
public class ModelMove : MonoBehaviour
|
2025-11-13 16:24:49 +08:00
|
|
|
|
{
|
2025-11-18 18:45:14 +08:00
|
|
|
|
// 摄像机属性
|
2025-11-13 16:24:49 +08:00
|
|
|
|
private Camera _camera;
|
2025-11-18 18:45:14 +08:00
|
|
|
|
|
|
|
|
|
|
public Camera CurrentCamera
|
2025-11-13 16:24:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_camera)
|
|
|
|
|
|
{
|
|
|
|
|
|
_camera = Camera.main;
|
|
|
|
|
|
if (!_camera)
|
|
|
|
|
|
{
|
|
|
|
|
|
_camera = FindObjectOfType<Camera>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-18 18:45:14 +08:00
|
|
|
|
|
2025-11-13 16:24:49 +08:00
|
|
|
|
return _camera;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-18 18:45:14 +08:00
|
|
|
|
|
|
|
|
|
|
public Vector2 horizontalMoveRange = new Vector2(-1f, 2f); // X轴范围
|
|
|
|
|
|
public Vector2 verticalMoveRange = new Vector2(-2f, 2f); // Y轴范围
|
|
|
|
|
|
public Vector2 depthMoveRange = new Vector2(-1, 10f); // Z轴范围 (深度)
|
|
|
|
|
|
|
|
|
|
|
|
public Vector2 scaleRange = new Vector2(0.5f, 3f); // 缩放范围 (x=min, y=max)
|
|
|
|
|
|
|
|
|
|
|
|
// 初始状态变量
|
|
|
|
|
|
private Vector3 _initialScale = Vector3.one;
|
|
|
|
|
|
|
|
|
|
|
|
private Quaternion _initialRotation = Quaternion.identity;
|
|
|
|
|
|
|
|
|
|
|
|
// 模型相对于摄像机的初始局部位置偏移量
|
|
|
|
|
|
private Vector3 _initialCameraLocalOffset;
|
|
|
|
|
|
|
|
|
|
|
|
// 用于Input输入的控制器对象
|
|
|
|
|
|
private GameObject _controlModelMoveGameObject;
|
|
|
|
|
|
|
|
|
|
|
|
public bool CanFix => ModBehaviour.config.data.canEdit;
|
|
|
|
|
|
|
|
|
|
|
|
// 模型在摄像机局部空间中的默认位置偏移量 (用于Reset)
|
|
|
|
|
|
public static readonly Vector3 DefaultCameraLocalOffset = new Vector3(0.38f, -0.92f, 2.00f);
|
|
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
EnsureControlModelMoveGameObject();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 16:24:49 +08:00
|
|
|
|
private void Start()
|
|
|
|
|
|
{
|
2025-11-18 18:45:14 +08:00
|
|
|
|
if (ModBehaviour.config.data.use)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyConfig();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
RefreshPosition();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
// // 调试功能,按下 "-" 打印模型相对于摄像机的局部位置
|
|
|
|
|
|
// if (Input.GetKeyDown(KeyCode.Minus))
|
|
|
|
|
|
// {
|
|
|
|
|
|
// if (CurrentCamera)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// var localPositionRelativeToCamera =
|
|
|
|
|
|
// CurrentCamera.transform.InverseTransformPoint(transform.position);
|
|
|
|
|
|
// Debug.Log($"模型相对于摄像机的局部位置: {localPositionRelativeToCamera}");
|
|
|
|
|
|
// }
|
|
|
|
|
|
// else
|
|
|
|
|
|
// {
|
|
|
|
|
|
// Debug.LogWarning("找不到摄像机,无法计算局部位置。模型的全局位置为: " + transform.position);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 销毁时清理生成的控制器GameObject
|
|
|
|
|
|
if (_controlModelMoveGameObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
Destroy(_controlModelMoveGameObject);
|
|
|
|
|
|
_controlModelMoveGameObject = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 确保ControlModelMove GameObject存在并绑定脚本。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void EnsureControlModelMoveGameObject()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_controlModelMoveGameObject == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尝试查找场景中已有的ControlModelMove
|
|
|
|
|
|
_controlModelMoveGameObject = GameObject.Find("ControlModelMove");
|
|
|
|
|
|
if (_controlModelMoveGameObject == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果没有,则创建新的
|
|
|
|
|
|
_controlModelMoveGameObject = new GameObject("ControlModelMove");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保ControlModelMove组件已添加
|
|
|
|
|
|
if (_controlModelMoveGameObject.GetComponent<ControlModelMove>() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_controlModelMoveGameObject.AddComponent<ControlModelMove>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将模型朝向当前摄像机。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LookAtCamera()
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CanFix)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (CurrentCamera)
|
|
|
|
|
|
{
|
|
|
|
|
|
transform.LookAt(CurrentCamera.transform);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("ModelMove.LookAtCamera: 找不到摄像机,无法使模型朝向摄像机。");
|
|
|
|
|
|
}
|
|
|
|
|
|
SaveDataToConfig();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 重置模型的位置、旋转和缩放
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void RefreshPosition()
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CanFix)
|
|
|
|
|
|
return;
|
|
|
|
|
|
// 重置缩放和旋转到初始状态
|
|
|
|
|
|
transform.localScale = _initialScale;
|
|
|
|
|
|
transform.rotation = _initialRotation;
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentCamera)
|
2025-11-13 16:24:49 +08:00
|
|
|
|
{
|
2025-11-18 18:45:14 +08:00
|
|
|
|
// 计算模型在世界空间中相对于摄像机的默认位置
|
|
|
|
|
|
var worldPos = CameraLocalToWorld(CurrentCamera, DefaultCameraLocalOffset);
|
2025-11-13 16:24:49 +08:00
|
|
|
|
transform.position = worldPos;
|
2025-11-18 18:45:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 使模型朝向摄像机
|
|
|
|
|
|
LookAtCamera();
|
|
|
|
|
|
|
|
|
|
|
|
// 记录模型重置后的相对于摄像机的局部偏移量,作为后续移动的基准
|
|
|
|
|
|
_initialCameraLocalOffset = CurrentCamera.transform.InverseTransformPoint(transform.position);
|
2025-11-13 16:24:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-11-18 18:45:14 +08:00
|
|
|
|
Debug.LogWarning("ModelMove.RefreshPosition: 找不到摄像机。模型将保持其当前世界位置,且相机相对移动功能将无法正常工作。");
|
|
|
|
|
|
_initialCameraLocalOffset = transform.position;
|
2025-11-13 16:24:49 +08:00
|
|
|
|
transform.position = new Vector3(8, 8, -16);
|
|
|
|
|
|
}
|
2025-11-18 18:45:14 +08:00
|
|
|
|
SaveDataToConfig();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 移动模型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="shift">相对于当前总偏移量的位移增量(在摄像机局部坐标系下)</param>
|
|
|
|
|
|
public void Move(Vector3 shift)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CanFix)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (!CurrentCamera)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("ModelMove.Move: 找不到摄像机,无法执行相机相对移动。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var currentLocalPos = CurrentCamera.transform.InverseTransformPoint(transform.position);
|
|
|
|
|
|
var currentOffsetFromInitial = currentLocalPos - _initialCameraLocalOffset;
|
|
|
|
|
|
var targetOffsetFromInitial = currentOffsetFromInitial + shift;
|
|
|
|
|
|
targetOffsetFromInitial.x = Mathf.Clamp(targetOffsetFromInitial.x, horizontalMoveRange.x, horizontalMoveRange.y);
|
|
|
|
|
|
targetOffsetFromInitial.y = Mathf.Clamp(targetOffsetFromInitial.y, verticalMoveRange.x, verticalMoveRange.y);
|
|
|
|
|
|
targetOffsetFromInitial.z = Mathf.Clamp(targetOffsetFromInitial.z, depthMoveRange.x, depthMoveRange.y);
|
|
|
|
|
|
|
|
|
|
|
|
var newLocalPos = _initialCameraLocalOffset + targetOffsetFromInitial;
|
|
|
|
|
|
|
|
|
|
|
|
transform.position = CurrentCamera.transform.TransformPoint(newLocalPos);
|
|
|
|
|
|
|
|
|
|
|
|
SaveDataToConfig();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 等比例缩放模型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="increment">缩放增量,通常来自鼠标滚轮或UI滑块</param>
|
|
|
|
|
|
public void Scale(float increment)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CanFix)
|
|
|
|
|
|
return;
|
|
|
|
|
|
// 假设是等比例缩放,所以只取x轴的值来计算
|
|
|
|
|
|
var currentScale = transform.localScale.x;
|
|
|
|
|
|
var newScale = currentScale + increment;
|
|
|
|
|
|
|
|
|
|
|
|
// 将新的缩放值限制在预设范围内
|
|
|
|
|
|
newScale = Mathf.Clamp(newScale, scaleRange.x, scaleRange.y);
|
|
|
|
|
|
|
|
|
|
|
|
// 应用新的等比例缩放值
|
|
|
|
|
|
SaveDataToConfig();
|
|
|
|
|
|
transform.localScale = new Vector3(newScale, newScale, newScale);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 旋转模型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="rotationAmount">旋转量,通常来自鼠标拖拽的偏移量(delta)</param>
|
|
|
|
|
|
public void Rotate(Vector2 rotationAmount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CanFix)
|
|
|
|
|
|
return;
|
|
|
|
|
|
// 左右拖拽(rotationAmount.x)时,围绕世界坐标的Y轴旋转,使模型保持直立
|
|
|
|
|
|
transform.Rotate(Vector3.up, -rotationAmount.x * Time.deltaTime, Space.World);
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentCamera)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 上下拖拽(rotationAmount.y)时,围绕摄像机的右方向轴旋转,感觉更自然
|
|
|
|
|
|
transform.Rotate(CurrentCamera.transform.right, rotationAmount.y * Time.deltaTime, Space.World);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果找不到相机,则退而求其次,围绕世界坐标的X轴旋转
|
|
|
|
|
|
Debug.LogWarning("ModelMove.Rotate: 找不到摄像机,上下旋转将使用世界X轴。");
|
|
|
|
|
|
transform.Rotate(Vector3.right, rotationAmount.y * Time.deltaTime, Space.World);
|
|
|
|
|
|
}
|
|
|
|
|
|
SaveDataToConfig();
|
|
|
|
|
|
|
2025-11-13 16:24:49 +08:00
|
|
|
|
}
|
2025-11-18 18:45:14 +08:00
|
|
|
|
|
2025-11-13 16:24:49 +08:00
|
|
|
|
/// <summary>
|
2025-11-18 18:45:14 +08:00
|
|
|
|
/// 沿摄像机的Z轴(朝向)旋转模型,实现“滚动”或“倾斜”效果。
|
2025-11-13 16:24:49 +08:00
|
|
|
|
/// </summary>
|
2025-11-18 18:45:14 +08:00
|
|
|
|
/// <param name="angle">要旋转的角度。正值通常为顺时针,负值为逆时针。</param>
|
|
|
|
|
|
public void RotateAroundCameraZ(float angle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!CanFix)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (CurrentCamera == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("ModelMove.RotateAroundCameraZ: 无法执行围绕相机Z轴的旋转,因为找不到相机。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var rotationAxis = CurrentCamera.transform.forward;
|
|
|
|
|
|
transform.Rotate(rotationAxis, angle, Space.World);
|
|
|
|
|
|
SaveDataToConfig();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void SaveDataToConfig()
|
|
|
|
|
|
{
|
|
|
|
|
|
var configData = ModBehaviour.config.data;
|
|
|
|
|
|
configData.modelPosition = CurrentCamera.transform.InverseTransformPoint(transform.position);
|
|
|
|
|
|
configData.modelRotation = transform.eulerAngles;
|
|
|
|
|
|
configData.modelScale = CurrentCamera.transform.localScale.x;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ApplyConfig()
|
|
|
|
|
|
{
|
|
|
|
|
|
var configData = ModBehaviour.config.data;
|
|
|
|
|
|
transform.position = CameraLocalToWorld(CurrentCamera, configData.modelPosition);
|
|
|
|
|
|
transform.eulerAngles = configData.modelRotation;
|
|
|
|
|
|
transform.localScale = new Vector3(configData.modelScale, configData.modelScale, configData.modelScale);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将摄像机局部空间中的点转换为世界空间中的点。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="camera">参考摄像机。</param>
|
|
|
|
|
|
/// <param name="localPoint">摄像机局部空间中的点。</param>
|
|
|
|
|
|
/// <returns>世界空间中的点。</returns>
|
2025-11-13 16:24:49 +08:00
|
|
|
|
public static Vector3 CameraLocalToWorld(Camera camera, Vector3 localPoint)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (camera == null)
|
2025-11-18 18:45:14 +08:00
|
|
|
|
throw new ArgumentNullException(nameof(camera),
|
|
|
|
|
|
"Camera cannot be null for CameraLocalToWorld conversion.");
|
2025-11-13 16:24:49 +08:00
|
|
|
|
|
2025-11-18 18:45:14 +08:00
|
|
|
|
var camTransform = camera.transform;
|
2025-11-13 16:24:49 +08:00
|
|
|
|
return camTransform.position + camTransform.rotation * localPoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-18 18:45:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|