You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义ScreenHandler无法正确处理点击拖拽及Slider交互问题求助

自定义ScreenHandler无法正确处理点击拖拽及Slider交互问题求助

我正在开发Unity中可点击的3D交互屏幕功能,已经实现了鼠标位置到UI纹理坐标的转换,点击按钮这类控件能正常响应,但Slider完全无法被拖动交互;同时还不知道该怎么实现悬停(Hover)效果。我尝试过修改射线检测逻辑让它识别Slider手柄,但结果是Slider直接跳到最大值,拖拽操作完全失效。目前脚本没有报错,拖拽检测逻辑正常,看起来核心问题出在UI事件的传递和处理上。

以下是我的完整代码:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class ScreenHandler : MonoBehaviour { 
    public Camera mainCamera; 
    public Camera uiCamera; 
    public Canvas uiCanvas; 
    public float dragThreshold = 5f; 
    public RenderTexture renderTexture; 

    private Vector2 mousePosition; 
    private GraphicRaycaster raycaster; 
    private bool dragging = false; 
    private GameObject target; 
    private PointerEventData dragData; 
    private Vector2 uiStartPos; 
    private Vector2 currentUIPos; 

    void Awake() { 
        if (uiCanvas != null) { 
            raycaster = uiCanvas.GetComponent<GraphicRaycaster>(); 
            if (raycaster == null) { 
                raycaster = uiCanvas.gameObject.AddComponent<GraphicRaycaster>(); 
            } 
        } 
    } 

    public bool CheckForInteractable(GameObject obj) { 
        if (obj == null) { 
            return false; 
        } 
        bool b = false; 
        // Check for Button component 
        if (obj.GetComponent<Button>() != null) { 
            b = true; 
        } 
        if (obj.GetComponent<Toggle>() != null) { 
            b = true; 
        } 
        if (obj.GetComponent<Slider>() != null) { 
            b = true; 
        } 
        return b; 
    } 

    public void Click(InputAction.CallbackContext context) { 
        if (context.performed) { 
            Ray ray = mainCamera.ScreenPointToRay(mousePosition); 
            if (Physics.Raycast(ray, out RaycastHit hit)) { 
                if (hit.collider.gameObject == gameObject) { 
                    Vector2 uv = hit.textureCoord; 
                    Vector2 pix = new Vector2( 
                        uv.x * renderTexture.width, 
                        uv.y * renderTexture.height 
                    ); 
                    // Store UI coordinates for consistent tracking 
                    uiStartPos = pix; 
                    currentUIPos = pix; 

                    PointerEventData pointerData = new PointerEventData(EventSystem.current) { position = pix }; 
                    var results = new System.Collections.Generic.List<RaycastResult>(); 
                    raycaster.Raycast(pointerData, results); 

                    if (results.Count > 0) { 
                        for (int i = 0; i < results.Count; i++) { 
                            if (CheckForInteractable(results[i].gameObject)) { 
                                target = results[i].gameObject; 
                                break; 
                            } 
                        } 
                        if (target != null) { 
                            dragData = new PointerEventData(EventSystem.current) { 
                                position = pix, 
                                pressPosition = pix, 
                                // Important for sliders 
                                delta = Vector2.zero 
                            }; 
                            // Start with pointer down event 
                            ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerEnterHandler); 
                            ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerDownHandler); 
                        } 
                    } 
                } 
            } 
        } else if (context.canceled) { 
            if (target != null) { 
                if (dragging) { 
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.endDragHandler); 
                } else { 
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerClickHandler); 
                } 
                ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerUpHandler); 
            } 
            // Reset state 
            dragData = null; 
            dragging = false; 
            target = null; 
        } 
    } 

    public void MouseMove(InputAction.CallbackContext context) { 
        if (context.performed) { 
            mousePosition = context.ReadValue<Vector2>(); 
            // Update current UI position 
            Ray ray = mainCamera.ScreenPointToRay(mousePosition); 
            if (Physics.Raycast(ray, out RaycastHit hit)) { 
                if (hit.collider.gameObject == gameObject) { 
                    Vector2 uv = hit.textureCoord; 
                    currentUIPos = new Vector2(uv.x * renderTexture.width, uv.y * renderTexture.height); 
                } 
            } 
            if (target != null && !dragging) { 
                // Use UI coordinates for drag threshold 
                float distance = Vector2.Distance(currentUIPos, uiStartPos); 
                if (distance > dragThreshold) { 
                    dragging = true; 
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.beginDragHandler); 
                } 
            } 
            if (target != null && dragData != null) { 
                // Always update position and delta for proper slider behavior 
                Vector2 previousPos = dragData.position; 
                dragData.position = currentUIPos; 
                dragData.delta = currentUIPos - previousPos; 
                if (dragging) { 
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.dragHandler); 
                } 
            } 
        } 
    } 
}

问题分析与修复方案

1. 核心问题点

  • 可交互控件检测不严谨:原代码通过逐个判断组件类型来检测可交互对象,容易遗漏,且没有利用Unity UI的统一基类Selectable。
  • Slider事件序列不完整:Slider需要完整的pointerDown→beginDrag→drag→endDrag→pointerUp事件链,原代码在事件参数传递和触发时机上有缺失。
  • 悬停事件未处理:没有在鼠标移动时实时检测悬停的UI元素,无法触发pointerEnter/pointerExit事件。
  • 拖拽事件参数更新不及时:dragData的pointerPress和pointerDrag属性未赋值,导致Slider无法正确识别拖拽发起者。

2. 具体修复步骤

(1)统一用Selectable检测可交互控件

替换原CheckForInteractable方法,所有UI交互控件都继承自Selectable,这样代码更简洁且无遗漏:

public bool CheckForInteractable(GameObject obj)
{
    return obj?.GetComponent<Selectable>() != null;
}

(2)完善悬停事件处理

添加变量跟踪上一帧悬停对象,在鼠标移动时对比当前对象,触发对应的进入/离开事件:

private GameObject lastHoveredObj; // 新增:跟踪上一帧悬停的对象

// 在MouseMove方法中添加悬停检测逻辑
GameObject currentHoveredObj = null;
// ...(原有坐标转换逻辑)
// 检测当前悬停的UI元素
var pointerData = new PointerEventData(EventSystem.current) { position = currentUIPos };
var results = new System.Collections.Generic.List<RaycastResult>();
raycaster.Raycast(pointerData, results);

foreach (var result in results)
{
    if (CheckForInteractable(result.gameObject))
    {
        currentHoveredObj = result.gameObject;
        break;
    }
}

// 处理悬停事件切换
if (currentHoveredObj != lastHoveredObj)
{
    var hoverData = new PointerEventData(EventSystem.current) { position = currentUIPos };
    if (lastHoveredObj != null)
    {
        ExecuteEvents.Execute(lastHoveredObj, hoverData, ExecuteEvents.pointerExitHandler);
    }
    if (currentHoveredObj != null)
    {
        ExecuteEvents.Execute(currentHoveredObj, hoverData, ExecuteEvents.pointerEnterHandler);
    }
    lastHoveredObj = currentHoveredObj;
}

(3)修复Slider拖拽的事件参数

初始化dragData时,补充pointerPress和pointerDrag属性,这是Slider识别拖拽的关键:

dragData = new PointerEventData(EventSystem.current)
{
    position = pix,
    pressPosition = pix,
    delta = Vector2.zero,
    pointerPress = target, // 新增:指定按下的对象
    pointerDrag = target   // 新增:指定拖拽的对象
};

(4)完善事件触发逻辑

在点击取消时,补充触发pointerExit事件,确保状态重置完整:

else if (context.canceled)
{
    if (target != null && dragData != null)
    {
        if (dragging)
        {
            ExecuteEvents.Execute(target, dragData, ExecuteEvents.endDragHandler);
        }
        else
        {
            ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerClickHandler);
        }
        ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerUpHandler);
        ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerExitHandler); // 新增:触发离开事件
    }
    // ...(原有状态重置逻辑)
}

3. 完整修复后代码

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class ScreenHandler : MonoBehaviour
{
    public Camera mainCamera;
    public Camera uiCamera;
    public Canvas uiCanvas;
    public float dragThreshold = 5f;
    public RenderTexture renderTexture;

    private Vector2 mousePosition;
    private GraphicRaycaster raycaster;
    private bool dragging = false;
    private GameObject target;
    private PointerEventData dragData;
    private Vector2 uiStartPos;
    private Vector2 currentUIPos;
    private GameObject lastHoveredObj; // 跟踪上一帧悬停的UI对象

    void Awake()
    {
        if (uiCanvas != null)
        {
            raycaster = uiCanvas.GetComponent<GraphicRaycaster>();
            if (raycaster == null)
            {
                raycaster = uiCanvas.gameObject.AddComponent<GraphicRaycaster>();
            }
        }
    }

    // 统一用Selectable检测可交互控件
    public bool CheckForInteractable(GameObject obj)
    {
        return obj?.GetComponent<Selectable>() != null;
    }

    public void Click(InputAction.CallbackContext context)
    {
        if (context.performed)
        {
            Ray ray = mainCamera.ScreenPointToRay(mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider.gameObject == gameObject)
            {
                Vector2 uv = hit.textureCoord;
                Vector2 pix = new Vector2(
                    uv.x * renderTexture.width,
                    uv.y * renderTexture.height
                );

                uiStartPos = pix;
                currentUIPos = pix;

                // 检测点击的UI元素
                var pointerData = new PointerEventData(EventSystem.current) { position = pix };
                var results = new System.Collections.Generic.List<RaycastResult>();
                raycaster.Raycast(pointerData, results);

                foreach (var result in results)
                {
                    if (CheckForInteractable(result.gameObject))
                    {
                        target = result.gameObject;
                        // 初始化拖拽事件数据,补充关键属性
                        dragData = new PointerEventData(EventSystem.current)
                        {
                            position = pix,
                            pressPosition = pix,
                            delta = Vector2.zero,
                            pointerPress = target,
                            pointerDrag = target
                        };

                        // 触发进入和按下事件
                        ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerEnterHandler);
                        ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerDownHandler);
                        break;
                    }
                }
            }
        }
        else if (context.canceled)
        {
            if (target != null && dragData != null)
            {
                if (dragging)
                {
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.endDragHandler);
                }
                else
                {
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerClickHandler);
                }
                ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerUpHandler);
                ExecuteEvents.Execute(target, dragData, ExecuteEvents.pointerExitHandler);
            }

            // 重置所有状态
            dragData = null;
            dragging = false;
            target = null;
        }
    }

    public void MouseMove(InputAction.CallbackContext context)
    {
        if (context.performed)
        {
            mousePosition = context.ReadValue<Vector2>();
            GameObject currentHoveredObj = null;

            Ray ray = mainCamera.ScreenPointToRay(mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider.gameObject == gameObject)
            {
                Vector2 uv = hit.textureCoord;
                currentUIPos = new Vector2(uv.x * renderTexture.width, uv.y * renderTexture.height);

                // 检测当前悬停的UI元素
                var pointerData = new PointerEventData(EventSystem.current) { position = currentUIPos };
                var results = new System.Collections.Generic.List<RaycastResult>();
                raycaster.Raycast(pointerData, results);

                foreach (var result in results)
                {
                    if (CheckForInteractable(result.gameObject))
                    {
                        currentHoveredObj = result.gameObject;
                        break;
                    }
                }
            }

            // 处理悬停事件切换
            if (currentHoveredObj != lastHoveredObj)
            {
                var hoverData = new PointerEventData(EventSystem.current) { position = currentUIPos };
                if (lastHoveredObj != null)
                {
                    ExecuteEvents.Execute(lastHoveredObj, hoverData, ExecuteEvents.pointerExitHandler);
                }
                if (currentHoveredObj != null)
                {
                    ExecuteEvents.Execute(currentHoveredObj, hoverData, ExecuteEvents.pointerEnterHandler);
                }
                lastHoveredObj = currentHoveredObj;
            }

            // 处理拖拽逻辑
            if (target != null && dragData != null)
            {
                Vector2 previousPos = dragData.position;
                dragData.position = currentUIPos;
                dragData.delta = currentUIPos - previousPos;

                if (!dragging)
                {
                    float distance = Vector2.Distance(currentUIPos, uiStartPos);
                    if (distance > dragThreshold)
                    {
                        dragging = true;
                        ExecuteEvents.Execute(target, dragData, ExecuteEvents.beginDragHandler);
                    }
                }
                else
                {
                    ExecuteEvents.Execute(target, dragData, ExecuteEvents.dragHandler);
                }
            }
        }
    }
}

额外注意事项

  • 确保场景中存在EventSystem对象(右键UI → Event System),否则UI事件无法正常触发。
  • 检查uiCanvas的Render Mode设置为Screen Space - Camera并关联uiCamera,与renderTexture的配置匹配。
  • 测试Slider时,确认其Interactable属性为勾选状态,且Handle Rect已正确赋值。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:00:30