using UnityEngine;
using UnityEngine.UI;
using System.Collections;
///
/// 子部件UI渲染与观察器
/// 功能:与 EnhancedModelViewerOrbitCamera 配合,当主脚本进入子物体观察模式时,
/// 将克隆出的小部件渲染到指定UI上,并允许在UI内进行旋转和缩放观察。
/// 配置步骤:
/// 1. 将本脚本挂载到场景中。
/// 2. 在Inspector中,将主观察脚本拖入 `Main Viewer Script` 字段。
/// 3. 配置好UI相机 (UIRender Camera)、渲染纹理 (Render Texture) 和UI显示图片 (Widget Display Image)。
/// 4. 运行场景,点击大模型的子部件即可在UI中查看和操作。
///
public class ChildWidgetUIRenderer : MonoBehaviour
{
[Header("【主观察脚本引用】")]
[Tooltip("关联的 EnhancedModelViewerOrbitCamera 脚本。")]
[SerializeField] private EnhancedModelViewerOrbitCamera mainViewerScript;
[Header("【UI渲染核心组件】")]
[Tooltip("用于显示部件模型的UI面板,通常包含一个RawImage。")]
[SerializeField] public GameObject uiPanel;
[Tooltip("用于显示模型的RawImage组件,其Texture将设置为下方的Render Texture。")]
[SerializeField] public RawImage widgetDisplayImage;
[Tooltip("专用于渲染UI中部件模型的相机。")]
[SerializeField] private Camera uiRenderCamera;
[Tooltip("渲染纹理,UI相机将画面渲染到此,再由RawImage显示。")]
[SerializeField] private RenderTexture renderTexture;
[Header("【UI内模型控制设置】")]
[Tooltip("在UI区域内拖拽时,模型的旋转速度。")]
[SerializeField] private float uiRotationSpeed = 2.0f;
[Tooltip("在UI区域内使用滚轮时,模型的缩放速度。")]
[SerializeField] private float uiZoomSpeed = 0.5f;
[Tooltip("模型在UI中的最小缩放比例。")]
[SerializeField] private float uiMinZoom = 0.5f;
[Tooltip("模型在UI中的最大缩放比例。")]
[SerializeField] private float uiMaxZoom = 3.0f;
[Tooltip("模型在UI中的默认缩放比例。")]
[SerializeField] private float uiDefaultZoom = 1.5f;
[Header("【UI相机设置】")]
[Tooltip("UI相机相对于模型初始位置的偏移量。Z值影响初始观察距离。")]
[SerializeField] private Vector3 uiCameraOffset = new Vector3(0, 0.5f, 2.5f);
[Tooltip("UI相机的视野角度(FOV),影响模型在画面中的大小。")]
[SerializeField] private float uiCameraFieldOfView = 45f;
[Header("【UI操作提示】(可选)")]
[Tooltip("用于显示操作提示的UI文本组件,如“拖拽旋转,滚轮缩放”。")]
[SerializeField] private Text instructionText;
// --- 私有变量 ---
private GameObject currentClonedWidgetForUI = null; // 当前由本脚本管理,用于UI显示的克隆部件
private bool isUIActive = false; // 标识UI面板是否处于激活状态
private bool isDraggingInUI = false; // 标识鼠标是否在UI区域内拖拽
// UI内模型的变换状态
private Vector2 uiWidgetRotation = Vector2.zero; // 累计旋转角度 (x: 绕世界Y轴, y: 绕自身X轴)
private float uiWidgetCurrentZoom = 1.5f; // 当前缩放值
private float uiWidgetTargetZoom = 1.5f; // 目标缩放值(用于平滑)
private Vector2 lastMousePosition; // 上一帧鼠标位置,用于计算拖拽增量
///
/// 初始化:设置UI和相机的初始状态,并订阅主脚本的事件。
///
void Start()
{
InitializeUIAndCamera();
SubscribeToMainViewerEvents();
}
///
/// 初始化UI面板和渲染相机。
///
private void InitializeUIAndCamera()
{
// 初始时隐藏UI面板
if (uiPanel != null)
{
uiPanel.SetActive(false);
}
// 配置UI相机
if (uiRenderCamera != null)
{
uiRenderCamera.enabled = false; // 初始禁用
if (renderTexture != null)
{
uiRenderCamera.targetTexture = renderTexture;
}
uiRenderCamera.fieldOfView = uiCameraFieldOfView;
}
// 将渲染纹理赋给UI的RawImage
if (widgetDisplayImage != null && renderTexture != null)
{
widgetDisplayImage.texture = renderTexture;
}
// 设置初始提示文本
UpdateInstructionText("点击大模型的子部件,将在UI面板中显示。");
}
///
/// 订阅主观察脚本的事件。
///
private void SubscribeToMainViewerEvents()
{
if (mainViewerScript == null)
{
Debug.LogWarning("ChildWidgetUIRenderer: 未指定主观察脚本,功能将不可用。", this);
return;
}
// 订阅开始观察事件
mainViewerScript.OnChildObservationStarted += HandleChildObservationStarted;
// 订阅结束观察事件
mainViewerScript.OnChildObservationEnded += HandleChildObservationEnded;
}
///
/// 取消订阅主观察脚本的事件(防止内存泄漏)。
///
private void OnDestroy()
{
if (mainViewerScript != null)
{
mainViewerScript.OnChildObservationStarted -= HandleChildObservationStarted;
mainViewerScript.OnChildObservationEnded -= HandleChildObservationEnded;
}
CleanupResources(); // 清理可能残留的资源
}
///
/// 主脚本开始观察子物体时的处理函数。
///
/// 主脚本创建的克隆部件。
/// 被点击的原始部件。
private void HandleChildObservationStarted(GameObject clonedWidget, GameObject originalWidget)
{
if (clonedWidget == null)
{
Debug.LogError("收到观察开始事件,但克隆部件为Null。");
return;
}
Debug.Log($"UI观察器:开始处理部件 '{originalWidget.name}' 的UI显示。");
// 1. 显示UI面板
ShowUIPanel();
// 2. 设置UI显示用的克隆部件
SetupWidgetForUIDisplay(clonedWidget);
// 3. 更新UI提示
UpdateInstructionText("在蓝色区域内拖拽旋转,滚轮缩放。\n按ESC或Shift键退出。");
}
///
/// 主脚本结束观察子物体时的处理函数。
///
/// 主脚本创建的克隆部件(即将被销毁)。
/// 被观察的原始部件。
private void HandleChildObservationEnded(GameObject clonedWidget, GameObject originalWidget)
{
Debug.Log($"UI观察器:结束观察部件 '{originalWidget?.name}'。");
// 关闭UI面板,清理资源
CloseUIPanel();
}
///
/// 为UI显示准备克隆部件。
/// 注意:我们会创建这个部件的另一个副本,专供UI相机渲染,以避免干扰主场景的观察。
///
/// 主脚本创建的克隆部件,作为复制源。
private void SetupWidgetForUIDisplay(GameObject sourceWidget)
{
// 清理可能存在的旧UI部件
if (currentClonedWidgetForUI != null)
{
Destroy(currentClonedWidgetForUI);
}
// 创建专用于UI显示的副本
//currentClonedWidgetForUI = Instantiate(sourceWidget, Vector3.zero, Quaternion.identity);
//currentClonedWidgetForUI.name = sourceWidget.name + "_UI_View";
currentClonedWidgetForUI = this.transform.GetChild(0).gameObject;
currentClonedWidgetForUI.layer = LayerMask.NameToLayer("CloneModel");
// 移除可能干扰的组件(如碰撞体、刚体、主脚本可能添加的临时组件等)
CleanupWidgetComponents(currentClonedWidgetForUI);
// 将部件放置在UI相机前
//PositionWidgetInFrontOfUICamera(currentClonedWidgetForUI);
// 重置UI控制参数
ResetUIControlParameters();
// 启用UI相机
if (uiRenderCamera != null)
{
uiRenderCamera.enabled = true;
}
}
///
/// 清理部件上不需要的组件,确保其在UI中正常显示且不干扰物理系统。
///
private void CleanupWidgetComponents(GameObject widget)
{
// 移除所有脚本,避免在UI模式下执行不必要的逻辑
MonoBehaviour[] scripts = widget.GetComponentsInChildren();
foreach (var script in scripts)
{
Destroy(script);
}
// 移除碰撞体,避免意外的射线检测
Collider[] colliders = widget.GetComponentsInChildren();
foreach (var collider in colliders)
{
Destroy(collider);
}
// 移除刚体,避免物理模拟
Rigidbody[] rigidbodies = widget.GetComponentsInChildren();
foreach (var rb in rigidbodies)
{
Destroy(rb);
}
}
///
/// 将部件定位到UI相机前方合适的位置。
///
private void PositionWidgetInFrontOfUICamera(GameObject widget)
{
if (uiRenderCamera == null || widget == null) return;
// 将部件设为UI相机的子物体,便于管理
widget.transform.SetParent(uiRenderCamera.transform);
// 设置局部位置和旋转
widget.transform.localPosition = uiCameraOffset;
widget.transform.localRotation = Quaternion.identity;
// 可选:根据部件大小自动调整初始缩放,确保其在视野内
Bounds bounds = CalculateRenderBounds(widget);
float maxSize = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z);
if (maxSize > 0)
{
// 一个简单的自适应缩放,可以根据uiCameraOffset.z和FOV进一步优化
float adaptiveScale = 2.0f / maxSize;
widget.transform.localScale = Vector3.one * adaptiveScale;
}
}
///
/// 计算包含所有Renderer的包围盒。
///
private Bounds CalculateRenderBounds(GameObject go)
{
Renderer[] renderers = go.GetComponentsInChildren();
if (renderers.Length == 0) return new Bounds(go.transform.position, Vector3.one);
Bounds bounds = renderers[0].bounds;
for (int i = 1; i < renderers.Length; i++)
{
bounds.Encapsulate(renderers[i].bounds);
}
return bounds;
}
///
/// 重置UI内的旋转和缩放控制参数。
///
private void ResetUIControlParameters()
{
uiWidgetRotation = Vector2.zero;
uiWidgetCurrentZoom = uiDefaultZoom;
uiWidgetTargetZoom = uiDefaultZoom;
if (currentClonedWidgetForUI != null)
{
currentClonedWidgetForUI.transform.localRotation = Quaternion.identity;
currentClonedWidgetForUI.transform.localScale = Vector3.one;
}
}
///
/// 显示UI面板。
///
private void ShowUIPanel()
{
if (uiPanel != null && !uiPanel.activeSelf)
{
uiPanel.SetActive(true);
isUIActive = true;
}
}
///
/// 关闭UI面板,并清理相关资源。
///
public void CloseUIPanel()
{
if (!isUIActive) return;
// 隐藏面板
if (uiPanel != null)
{
uiPanel.SetActive(false);
}
isUIActive = false;
isDraggingInUI = false;
// 禁用UI相机
if (uiRenderCamera != null)
{
uiRenderCamera.enabled = false;
}
// 销毁专用于UI显示的克隆部件
if (currentClonedWidgetForUI != null)
{
Destroy(currentClonedWidgetForUI);
currentClonedWidgetForUI = null;
}
// 更新提示文本
UpdateInstructionText("UI观察模式已关闭。");
}
///
/// 每帧更新:处理UI内的输入(旋转和缩放)。
///
void Update()
{
if (!isUIActive || currentClonedWidgetForUI == null) return;
HandleUIInput();
ApplyUITransforms();
}
///
/// 处理UI显示区域内的鼠标输入。
///
private void HandleUIInput()
{
// 检查鼠标是否在显示部件的UI区域内
bool isMouseOverUI = IsMouseOverDisplayImage();
// --- 旋转控制(鼠标拖拽)---
if (isMouseOverUI && Input.GetMouseButtonDown(0))
{
isDraggingInUI = true;
lastMousePosition = Input.mousePosition;
}
if (Input.GetMouseButton(0) && isDraggingInUI)
{
Vector2 currentMousePos = Input.mousePosition;
Vector2 delta = currentMousePos - lastMousePosition;
// 根据鼠标移动增量更新旋转角度
uiWidgetRotation.x += delta.x * uiRotationSpeed * 0.1f; // 绕世界Y轴旋转
uiWidgetRotation.y += delta.y * uiRotationSpeed * 0.1f; // 绕自身X轴旋转
uiWidgetRotation.y = Mathf.Clamp(uiWidgetRotation.y, -80f, 80f); // 限制上下翻转角度
lastMousePosition = currentMousePos;
}
if (Input.GetMouseButtonUp(0))
{
isDraggingInUI = false;
}
// --- 缩放控制(鼠标滚轮)---
if (isMouseOverUI)
{
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) > 0.01f)
{
// 根据滚轮方向调整目标缩放值
uiWidgetTargetZoom += scroll * uiZoomSpeed;
uiWidgetTargetZoom = Mathf.Clamp(uiWidgetTargetZoom, uiMinZoom, uiMaxZoom);
}
}
// --- 退出控制(ESC键)---
if (Input.GetKeyDown(KeyCode.Escape))
{
// 通知主脚本退出子物体观察模式,这将触发我们订阅的End事件,进而关闭UI。
if (mainViewerScript != null && mainViewerScript.IsObservingChild())
{
// 假设主脚本有公共的退出方法。如果没有,您可能需要通过其他方式触发退出。
// 例如,可以调用 mainViewerScript.ExitChildObservationMode(); 如果它是public的。
// 这里作为一个安全调用,实际情况取决于您的主脚本。
Debug.Log("UI观察器:按ESC键,尝试退出观察模式。");
// 注意:更佳实践是让UI的关闭逻辑与主脚本解耦,通过事件驱动。
// 这里我们直接关闭自己的UI,主脚本的退出由用户按Shift键或其他方式触发。
CloseUIPanel();
}
}
}
///
/// 检测鼠标位置是否在显示模型的RawImage矩形区域内。
///
private bool IsMouseOverDisplayImage()
{
if (widgetDisplayImage == null) return false;
RectTransform rect = widgetDisplayImage.rectTransform;
Vector2 localPoint;
// 将屏幕鼠标坐标转换为UI矩形内的本地坐标
return RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, Input.mousePosition, null, out localPoint)
&& rect.rect.Contains(localPoint);
}
///
/// 将UI控制参数(旋转、缩放)应用到模型上。
///
private void ApplyUITransforms()
{
if (currentClonedWidgetForUI == null) return;
// 应用旋转
Quaternion targetRotation = Quaternion.Euler(uiWidgetRotation.y, -uiWidgetRotation.x, 0);
currentClonedWidgetForUI.transform.localRotation = Quaternion.Slerp(
currentClonedWidgetForUI.transform.localRotation,
targetRotation,
10f * Time.deltaTime
);
// 应用缩放
uiWidgetCurrentZoom = Mathf.Lerp(uiWidgetCurrentZoom, uiWidgetTargetZoom, 5f * Time.deltaTime);
currentClonedWidgetForUI.transform.localScale = Vector3.one * uiWidgetCurrentZoom;
}
///
/// 更新UI上的操作提示文本。
///
private void UpdateInstructionText(string text)
{
if (instructionText != null)
{
instructionText.text = text;
}
}
///
/// 清理脚本持有的资源。
///
private void CleanupResources()
{
if (currentClonedWidgetForUI != null)
{
Destroy(currentClonedWidgetForUI);
}
}
///
/// 在编辑器模式下,绘制UI相机的视野范围等调试信息(可选)。
///
void OnDrawGizmosSelected()
{
if (uiRenderCamera != null)
{
Gizmos.color = Color.cyan;
Gizmos.matrix = uiRenderCamera.transform.localToWorldMatrix;
// 简单绘制一个相机视椎体
float distance = uiCameraOffset.z;
Gizmos.DrawFrustum(Vector3.zero, uiCameraFieldOfView, distance, 0.1f, 1.0f);
}
}
}