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

相机跟随玩家移动时如何让虚拟内摇杆保持在屏幕正确位置

问题原因

你当前的实现把虚拟摇杆放在世界空间下计算,用Camera.main.ScreenToWorldPoint(touch.position)转换触摸坐标、给摇杆Transform赋值世界坐标。当跟随玩家的相机移动时,世界坐标与屏幕坐标的映射关系每帧发生变化,但你记录的初始触摸点startPos是固定的世界坐标,不会随相机偏移同步更新,自然会出现摇杆位置偏移、滞留错位的问题。
虚拟摇杆属于屏幕UI元素,逻辑计算和位置控制应该全程在屏幕/UI空间完成,不需要和游戏世界坐标做绑定,从根源上就不会受主相机移动的影响。

修复方案
  • 将摇杆的底图、内杆从世界场景挪到UI Canvas下,使用RectTransform作为UI元素定位,不要作为普通场景物体用世界坐标控制位置。
  • 删除所有触摸坐标转世界坐标的逻辑,触摸位置直接使用Input.GetTouch(i).position自带的屏幕像素坐标计算,初始触摸点、偏移量、摇杆位置赋值全部在屏幕/UI空间完成。
  • 替换原有的世界碰撞体OverlapPoint触摸区域判定:如果是固定区域摇杆,直接判断触摸屏幕坐标是否落在预设的屏幕矩形范围内即可;如果是动态触发的摇杆,用UI事件系统的射线检测判断触摸点是否落在摇杆响应层即可,完全不需要用到世界空间碰撞检测。
  • 补充TouchPhase.Canceled状态的处理,避免触摸被系统打断时摇杆一直卡在激活状态。

核心逻辑参考代码(可根据自己的项目参数调整):

// 所有摇杆计算全程使用屏幕/UI坐标,不做世界坐标转换
void Update()
{
    touchNum = Input.touchCount;
    if (touchNum > 0)
    {
        for (int i = 0; i < touchNum; i++)
        {
            touch = Input.GetTouch(i);
            Vector2 touchScreenPos = touch.position;
            
            // 替换为自己的UI区域判定逻辑
            if (IsPointInJoyStickActiveArea(touchScreenPos))
            {
                switch (touch.phase)
                {
                    case TouchPhase.Began:
                        // 记录屏幕空间初始触摸点
                        startScreenPos = touchScreenPos;
                        // 用UI锚点坐标设置摇杆位置,不要用世界坐标position
                        joyBack.anchoredPosition = ScreenPosToUIPos(startScreenPos);
                        joyFront.anchoredPosition = ScreenPosToUIPos(startScreenPos);
                        joyBack.gameObject.SetActive(true);
                        joyFront.gameObject.SetActive(true);
                        distance = 0;
                        direction = Vector2.zero;
                        break;
                    case TouchPhase.Moved:
                        Vector2 offset = touchScreenPos - startScreenPos;
                        // 摇杆活动半径根据自己的UI尺寸调整,替换掉原来写死的1.75f
                        Vector2 clampedOffset = Vector2.ClampMagnitude(offset, joyStickPixelRadius);
                        // 方向和距离计算逻辑和原有逻辑一致,距离归一化到0-1区间更方便控制移动速度
                        direction = offset.normalized;
                        direction = Limiters(direction, limit);
                        distance = clampedOffset.magnitude / joyStickPixelRadius;
                        // 更新内杆位置
                        joyFront.anchoredPosition = joyBack.anchoredPosition + ScreenPosToUIPos(clampedOffset);
                        break;
                    case TouchPhase.Ended:
                    case TouchPhase.Canceled:
                        distance = 0;
                        direction = Vector2.zero;
                        joyBack.gameObject.SetActive(false);
                        joyFront.gameObject.SetActive(false);
                        break;
                }
            }
            else
            {
                joyBack.gameObject.SetActive(false);
                joyFront.gameObject.SetActive(false);
                distance = 0;
                direction = Vector2.zero;
            }
        }
    }
}

// 屏幕坐标转当前Canvas下UI坐标的工具方法,根据自己的Canvas渲染模式实现即可
private Vector2 ScreenPosToUIPos(Vector2 screenPos)
{
    RectTransformUtility.ScreenPointToLocalPointInRectangle(
        canvas.GetComponent<RectTransform>(), 
        screenPos, 
        canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera, 
        out Vector2 uiPos
    );
    return uiPos;
}

小提示:原代码在TouchPhase.Began分支里重复给joyBack、joyFront赋值了两次初始位置,这部分冗余逻辑可以直接删掉。

如果Canvas使用Screen Space - Overlay渲染模式,UI坐标和屏幕坐标可以直接映射,不需要额外转换;如果使用Screen Space - Camera模式,只需要在赋值UI位置时做一次上述的坐标转换,计算逻辑全程还是在屏幕空间运行,完全不会受游戏主相机移动的影响。

内容的提问来源于stack exchange,提问作者Spring Onion

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:01:07