相机跟随玩家移动时如何让虚拟内摇杆保持在屏幕正确位置
问题原因
你当前的实现把虚拟摇杆放在世界空间下计算,用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
相关产品推荐
相关产品推荐

