Files
DuckovMods/CharacterPreview/ModelMove.cs
2025-11-18 18:45:14 +08:00

300 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using UnityEngine;
namespace CharacterPreview
{
public class ModelMove : MonoBehaviour
{
// 摄像机属性
private Camera _camera;
public Camera CurrentCamera
{
get
{
if (!_camera)
{
_camera = Camera.main;
if (!_camera)
{
_camera = FindObjectOfType<Camera>();
}
}
return _camera;
}
}
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();
}
private void Start()
{
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)
{
// 计算模型在世界空间中相对于摄像机的默认位置
var worldPos = CameraLocalToWorld(CurrentCamera, DefaultCameraLocalOffset);
transform.position = worldPos;
// 使模型朝向摄像机
LookAtCamera();
// 记录模型重置后的相对于摄像机的局部偏移量,作为后续移动的基准
_initialCameraLocalOffset = CurrentCamera.transform.InverseTransformPoint(transform.position);
}
else
{
Debug.LogWarning("ModelMove.RefreshPosition: 找不到摄像机。模型将保持其当前世界位置,且相机相对移动功能将无法正常工作。");
_initialCameraLocalOffset = transform.position;
transform.position = new Vector3(8, 8, -16);
}
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();
}
/// <summary>
/// 沿摄像机的Z轴朝向旋转模型实现“滚动”或“倾斜”效果。
/// </summary>
/// <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>
public static Vector3 CameraLocalToWorld(Camera camera, Vector3 localPoint)
{
if (camera == null)
throw new ArgumentNullException(nameof(camera),
"Camera cannot be null for CameraLocalToWorld conversion.");
var camTransform = camera.transform;
return camTransform.position + camTransform.rotation * localPoint;
}
}
}