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;
        }
    }
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐