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

Unity中如何实现GameObject在透视与正交相机间无缝切换显示?

问题

开发类跳棋棋盘游戏时遇到以下场景与问题:

  • 主相机为透视模式,移除棋子需拖拽至屏幕底部的已移除棋子栈区域,随后动画移动并旋转至栈内正确位置
  • 棋子栈位于独立层,仅正交相机可见(透视模式下显示效果差)
  • 切换棋子所在层时,屏幕位置会跳变(甚至移出屏幕),视觉旋转也会大幅改变
  • 需求:切换层时棋子视觉位置与旋转完全不变,之后无缝动画至栈内目标位置与旋转
解决方案

核心思路是:将棋子在透视相机下的视口坐标转换为正交相机下的世界坐标,同时将棋子相对于透视相机的局部旋转映射到正交相机视角下,确保视觉上无断层。

修改ChangeOver()方法后的完整代码如下:

public class Stack : MonoBehaviour
{
    [SerializeField] private Camera perspectiveCamera, orthogonalCamera;
    [SerializeField] private Vector3 startPosition;
    [SerializeField] private Vector3 startRotation;
    [SerializeField] private float duration;    
    
    public void AddToStack(Transform piece)
    {
        piece.transform.SetParent(transform);
        
        // 切换层并保持视觉位置/旋转不变
        ChangeOver(piece);
        
        StartCoroutine(LerpBetweenPositionsAndRotations(piece));
    }
    
    private void ChangeOver(Transform piece)
    {
        // --- 位置转换:保持屏幕位置不变 ---
        // 将棋子世界坐标转为透视相机的视口坐标(不受屏幕分辨率影响)
        Vector3 viewportPos = perspectiveCamera.WorldToViewportPoint(piece.position);
        
        // 获取棋子栈到正交相机的深度,确保棋子与栈处于同一平面
        float targetDepth = orthogonalCamera.WorldToViewportPoint(transform.position).z;
        
        // 将视口坐标转为正交相机下的世界坐标,保证屏幕位置一致
        Vector3 newWorldPos = orthogonalCamera.ViewportToWorldPoint(
            new Vector3(viewportPos.x, viewportPos.y, targetDepth)
        );
        piece.position = newWorldPos;

        // --- 旋转转换:保持视觉旋转不变 ---
        // 计算棋子相对于透视相机的局部旋转(剥离相机自身旋转影响)
        Quaternion relativeRotToPerspective = Quaternion.Inverse(perspectiveCamera.transform.rotation) * piece.rotation;
        
        // 将该相对旋转映射到正交相机视角,得到新的世界旋转
        piece.rotation = orthogonalCamera.transform.rotation * relativeRotToPerspective;
        
        // 记录动画起始的局部位置与旋转
        startPosition = piece.localPosition;
        startRotation = piece.localRotation.eulerAngles;
        
        // 切换棋子到栈所在层(仅正交相机可见)
        piece.gameObject.layer = gameObject.layer;
    }
    
    private IEnumerator LerpBetweenPositionsAndRotations(Transform piece)
    {
        float elapsedTime = 0f;
        // 提前计算目标值,避免循环内重复计算
        Vector3 targetLocalPos = new Vector3(transform.childCount - 1, 0f, 0f);
        Quaternion targetLocalRot = Quaternion.Euler(0f, 0f, 0f);
        
        while (elapsedTime < duration)
        {
            float t = elapsedTime / duration;
            piece.localPosition = Vector3.Lerp(startPosition, targetLocalPos, t);
            piece.localRotation = Quaternion.Lerp(Quaternion.Euler(startRotation), targetLocalRot, t);
            
            elapsedTime += Time.deltaTime;
            yield return null;
        }
        
        // 确保最终状态完全匹配目标值
        piece.localPosition = targetLocalPos;
        piece.localRotation = targetLocalRot;
    }
}

关键逻辑说明

  1. 位置转换:

    • 使用视口坐标而非屏幕坐标,适配任意分辨率
    • 对齐棋子与栈的深度,避免正交相机下的位置偏移
    • 通过视口坐标的跨相机转换,保证屏幕视觉位置完全一致
  2. 旋转转换:

    • 先计算棋子相对于透视相机的局部旋转,剔除相机自身旋转的干扰
    • 将该相对旋转映射到正交相机视角,确保视觉朝向无变化
  3. 动画优化:

    • 提前计算目标位置与旋转,减少循环内的性能开销
    • 使用while循环更精准地控制动画时长

内容的提问来源于stack exchange,提问作者Eddie Domínguez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:38:35