在unity实现“长按 UI 图标拖拽材质球替换特定 3D 零件材质”的功能

TargetPartID脚本源码(只负责数据存储存ID)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 挂载在每个可被替换的零件上,用于存储其唯一的ID
/// </summary>
public class TargetPartID : MonoBehaviour
{
[Tooltip("该零件的唯一ID,必须与MaterialButtonData中的Target Part ID匹配:")]
public string partID;
}
MaterialButtonData(负载数据和事件的封装)
using UnityEngine;
using UnityEngine.EventSystems;
public class MaterialButtonData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
[Tooltip("该UI按钮所代表的、最终要替换到物体上的材质。")]
public Material targetNewMaterial;
[Header("目标物体的唯一ID")]
[Tooltip("该按钮只能替换具有此ID的物体。")]
public string targetPartID; //用于匹配目标的唯一ID
// 核心交互脚本的引用
private MaterialInteraction interactionManager;
void Start()
{
interactionManager = FindObjectOfType<MaterialInteraction>();
if (interactionManager == null)
{
Debug.LogError("场景中缺少 MaterialInteraction 脚本!请检查 Game Manager。");
}
}
public void OnPointerDown(PointerEventData eventData)
{
if (interactionManager != null)
{
// 传入材质和目标ID
interactionManager.OnMaterialPointerDown(targetNewMaterial, targetPartID);
}
}
public void OnPointerUp(PointerEventData eventData)
{
if (interactionManager != null)
{
interactionManager.OnMaterialPointerUp();
}
}
}
MaterialInteraction 仅负责 逻辑和流程控制 (射线、状态、材质替换)
using UnityEngine;
using UnityEngine.EventSystems;
/// <summary>
/// 控制UI长按生成材质球、跟随鼠标、零件高亮及最终材质替换的核心交互逻辑。
/// 该脚本根据传入的目标ID(currentTargetID)来决定哪些零件可以被高亮和替换。
/// </summary>
public class MaterialInteraction : MonoBehaviour
{
[Header("1. 资源与预制体")]
[Tooltip("用于拖拽的半透明预览球体的预制体。")]
public GameObject previewSpherePrefab;
[Tooltip("悬停时用于高亮显示零件的材质(例如一个发光或半透明材质)。")]
public Material highlightMaterial;
[Header("2. 交互设置")]
[Tooltip("目标电机零件的通用Tag。用于初步筛选射线击中的物体。")]
public string targetTag = "MotorPart";
// --- 内部私有变量 ---
private GameObject currentPreviewSphere; // 当前生成的球体实例
private bool isDraggingMaterial = false; // 是否处于拖拽状态
private Renderer highlightedRenderer; // 当前被高亮的零件的Renderer组件
private Material originalMaterial; // 被高亮的零件的原始材质(用于恢复)
private Material currentDraggingMaterial; // 当前由UI按钮传入的、用于替换的材质
private string currentTargetID; // 当前拖拽按钮对应的目标零件ID
// --- Unity Life Cycle ---
void Update()
{
// 只有处于拖拽状态且预览球体存在时才执行
if (isDraggingMaterial && currentPreviewSphere != null)
{
// 1. 球体跟随鼠标移动
MovePreviewSphere();
// 2. 射线检测并进行高亮处理
HandleRaycastingAndHighlight();
}
}
// --- 核心方法:由 MaterialButtonData 脚本调用 ---
/// <summary>
/// 当UI材质图按钮被按下时调用 (对应 Pointer Down)。
/// 接收 MaterialButtonData 传入的材质参数和目标ID。
/// </summary>
public void OnMaterialPointerDown(Material materialToDrag, string targetID)
{
// 存储当前正在拖拽的材质和目标ID
currentDraggingMaterial = materialToDrag;
currentTargetID = targetID;
if (currentDraggingMaterial == null || previewSpherePrefab == null || highlightMaterial == null)
{
Debug.LogError("请检查材质或球体预制体是否设置正确!");
currentDraggingMaterial = null;
currentTargetID = null;
return;
}
isDraggingMaterial = true;
// 生成半透明球体
currentPreviewSphere = Instantiate(previewSpherePrefab);
}
/// <summary>
/// 当UI材质图按钮松开时调用 (对应 Pointer Up)。
/// </summary>
public void OnMaterialPointerUp()
{
isDraggingMaterial = false;
// 1. 销毁球体
if (currentPreviewSphere != null)
{
Destroy(currentPreviewSphere);
currentPreviewSphere = null;
}
// 2. 如果松开时有零件被高亮,并且当前有拖拽的材质,则执行替换
if (highlightedRenderer != null && currentDraggingMaterial != null)
{
// 调用替换逻辑,使用当前拖拽的材质
ReplaceMaterial(highlightedRenderer.gameObject, currentDraggingMaterial);
// 替换成功,清理 internal 引用。重要:不调用 ClearHighlight() 来防止恢复旧材质。
highlightedRenderer = null;
originalMaterial = null;
}
else
{
// 如果松手时没有目标零件被高亮,就正常清理高亮状态(如果有的话)
ClearHighlight();
}
// 3. 清理当前拖拽的材质和ID引用
currentDraggingMaterial = null;
currentTargetID = null;
}
// --- 辅助逻辑方法 ---
/// <summary>
/// 处理预览球体的世界坐标跟随鼠标。
/// </summary>
private void MovePreviewSphere()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 使用一个与摄像机垂直的平面来确定3D位置
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(ray, out float distance))
{
Vector3 worldPosition = ray.GetPoint(distance);
currentPreviewSphere.transform.position = worldPosition;
}
}
/// <summary>
/// 处理射线检测,检查是否悬停在目标零件上并进行ID匹配。
/// </summary>
private void HandleRaycastingAndHighlight()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
// 1. 检查是否击中了目标零件组(基于通用 Tag)
if (hit.collider.CompareTag(targetTag))
{
Renderer hitRenderer = hit.collider.GetComponent<Renderer>();
// 尝试获取零件上的 TargetPartID 组件
TargetPartID partIDComponent = hit.collider.GetComponent<TargetPartID>();
// 2. 检查 ID 是否匹配 (双重验证:Renderer存在 且 ID组件存在 且 ID匹配)
if (hitRenderer != null && partIDComponent != null && partIDComponent.partID == currentTargetID)
{
// ID 匹配成功,可以高亮
if (highlightedRenderer != hitRenderer)
{
ClearHighlight();
HighlightPart(hitRenderer);
}
}
else
{
// 击中目标组但 ID 不匹配,清除高亮
ClearHighlight();
}
}
else
{
// 击中其他物体或空地,清除高亮
ClearHighlight();
}
}
else
{
// 射线未击中任何物体,清除高亮
ClearHighlight();
}
}
/// <summary>
/// 对一个零件进行高亮显示。
/// </summary>
private void HighlightPart(Renderer partRenderer)
{
highlightedRenderer = partRenderer;
// 存储原始材质
originalMaterial = partRenderer.sharedMaterial;
// 替换为高亮材质 (使用 .material 创建一个材质实例)
partRenderer.material = highlightMaterial;
}
/// <summary>
/// 清理当前的高亮状态,恢复原始材质。
/// </summary>
private void ClearHighlight()
{
if (highlightedRenderer != null && originalMaterial != null)
{
// 恢复原始材质 (使用 sharedMaterial 恢复)
highlightedRenderer.sharedMaterial = originalMaterial;
// 清理引用
highlightedRenderer = null;
originalMaterial = null;
}
}
/// <summary>
/// 将选中的零件替换为新的永久材质。
/// </summary>
private void ReplaceMaterial(GameObject part, Material newMat)
{
Renderer partRenderer = part.GetComponent<Renderer>();
if (partRenderer != null)
{
// 替换为传入的新材质 (使用 sharedMaterial 永久改变)
partRenderer.sharedMaterial = newMat;
}
}
}
更多推荐
所有评论(0)