Unity相机交互开发:从Scene视图到Game视图的工业级迁移方案

📅 2026/8/4 15:34:17
Unity相机交互开发:从Scene视图到Game视图的工业级迁移方案
1. 项目概述为什么我们需要关注相机交互的迁移在Unity3D的开发流程中Scene视图和Game视图是我们最熟悉的两个工作区。Scene视图是开发者的“上帝视角”我们在这里摆放物体、调整光照、构建世界而Game视图则是玩家的“第一人称视角”是最终产品运行时的样子。很多开发者尤其是新手常常会遇到一个尴尬的局面在Scene视图里精心调整好的相机角度、平滑的镜头移动逻辑一到Game视图里就“变了味”——要么交互失灵要么视角错乱要么性能骤降。这背后的核心问题就是相机操控逻辑在编辑态Scene与运行态Game之间的“迁移”没有做好。Scene视图的交互如鼠标拖拽旋转、滚轮缩放是编辑器内置的、基于屏幕坐标的便捷操作而Game视图的交互则需要我们通过代码将玩家的输入鼠标、键盘、触屏转化为相机在三维世界中的精确变换。这不仅仅是写几行transform.Translate那么简单它涉及到输入系统的抽象、坐标系的转换、运动曲线的平滑处理以及至关重要的——性能与体验的平衡。最近无论是“虚拟相机”还是“深度相机”等热词的火爆都指向了一个趋势交互的沉浸感和真实感要求越来越高。一个操控别扭的相机足以毁掉一个 otherwise 很出色的游戏或应用。因此掌握从Scene视图那种“所见即所得”的便捷感无缝迁移到Game视图下稳定、流畅、可预测的相机交互是每个Unity开发者必须跨过的一道坎。本文将从一个实战者的角度拆解这其中的核心逻辑、常见陷阱并提供一个可直接复用的、工业级的解决方案。2. 核心设计思路拆解“操控”与“视图”的耦合要实现优雅的迁移首先得理解两者本质的不同。Scene视图的相机操控是编辑器功能它高度集成但对我们透明Game视图的相机操控是我们自己写的游戏逻辑的一部分。我们的目标不是复现编辑器而是汲取其交互设计的精髓并适配我们自己的游戏需求。2.1 Scene视图交互的逆向工程虽然我们不能直接调用UnityEditor.SceneView的API它们在构建后不存在但我们可以观察并抽象其行为模式右键拖拽围绕一个焦点通常是选择物体的中心或视图中心旋转相机。中键拖拽平移整个视图Pan。滚轮以鼠标位置为中心进行缩放Zoom。F键聚焦将选中的物体快速置于视图中央。这些操作的共同点是以视图内某个点或物体为交互锚点操作感非常符合人类在三维空间中观察物体的直觉。我们的Game视图相机交互也应该追求这种直觉性。2.2 Game视图交互的核心挑战在Game视图下我们面临几个关键挑战输入抽象需要处理PC鼠标/键盘、移动端触屏、甚至手柄等多种输入设备。坐标系转换鼠标在屏幕上的二维移动Input.mousePosition如何转化为相机在三维空间中的旋转、平移和缩放焦点与插值如何让相机的运动平滑如使用Mathf.SmoothDamp或Vector3.SmoothDamp而不是生硬地跳跃如何实现像Scene视图那样围绕一个目标点旋转性能与边界相机移动是否需要物理碰撞检测移动范围是否要限制缩放是否有最小/最大值限制基于这些分析一个健壮的相机操控系统应该采用分层设计输入层抽象并统一处理来自不同设备的原始输入输出标准的“平移”、“旋转”、“缩放”指令和强度。逻辑层根据指令计算相机目标位置TargetPosition和目标旋转TargetRotation。这是核心算法所在例如计算绕点旋转的新坐标。表现层使用插值方法让相机的Transform平滑地趋向于目标位置和旋转。同时处理碰撞检测、边界限制等“后处理”效果。注意切勿在Update中直接将输入变化加到相机transform上这会导致运动生硬且帧率依赖。正确的做法是每帧计算一个“目标状态”然后让相机平滑地跟随这个目标状态。3. 核心模块实现构建可复用的相机控制器下面我将构建一个名为AdvancedCameraController的组件它融合了Scene视图的核心交互逻辑并针对Game视图进行了增强。3.1 输入抽象模块首先我们需要一个模块来屏蔽输入设备的差异。这里以PC端为主但结构易于扩展。using UnityEngine; public class CameraInputModule : MonoBehaviour { // 平移 public Vector3 MoveInput { get; private set; } // 旋转欧拉角变化量 public Vector2 RotateInput { get; private set; } // 缩放 public float ZoomInput { get; private set; } // 当前交互焦点世界坐标 public Vector3? InteractionFocusPoint { get; private set; } [Header(鼠标设置)] public float mouseRotateSensitivity 1.0f; public float mousePanSensitivity 0.5f; public float mouseZoomSensitivity 5.0f; public int mouseRotateButton 1; // 右键 public int mousePanButton 2; // 中键 [Header(键盘设置)] public KeyCode forwardKey KeyCode.W; public KeyCode backKey KeyCode.S; public KeyCode leftKey KeyCode.A; public KeyCode rightKey KeyCode.D; public KeyCode riseKey KeyCode.E; public KeyCode fallKey KeyCode.Q; void Update() { HandleMouseInput(); HandleKeyboardInput(); } void HandleMouseInput() { // 1. 鼠标旋转右键拖拽 if (Input.GetMouseButton(mouseRotateButton)) { RotateInput new Vector2( Input.GetAxis(Mouse X) * mouseRotateSensitivity, Input.GetAxis(Mouse Y) * mouseRotateSensitivity ); // 尝试通过射线投射获取焦点 UpdateFocusPoint(); } else { RotateInput Vector2.zero; } // 2. 鼠标平移中键拖拽 if (Input.GetMouseButton(mousePanButton)) { MoveInput new Vector3( -Input.GetAxis(Mouse X) * mousePanSensitivity, -Input.GetAxis(Mouse Y) * mousePanSensitivity, 0 // 屏幕平面移动Z轴在逻辑层处理 ); } else if (MoveInput.magnitude 0.01f) // 避免键盘输入被覆盖 { MoveInput Vector3.zero; } // 3. 鼠标滚轮缩放 ZoomInput Input.mouseScrollDelta.y * mouseZoomSensitivity; } void HandleKeyboardInput() { Vector3 keyboardMove Vector3.zero; if (Input.GetKey(forwardKey)) keyboardMove Vector3.forward; if (Input.GetKey(backKey)) keyboardMove Vector3.back; if (Input.GetKey(leftKey)) keyboardMove Vector3.left; if (Input.GetKey(rightKey)) keyboardMove Vector3.right; if (Input.GetKey(riseKey)) keyboardMove Vector3.up; if (Input.GetKey(fallKey)) keyboardMove Vector3.down; if (keyboardMove ! Vector3.zero) { // 将世界方向转换为相机本地方向 MoveInput Camera.main.transform.TransformDirection(keyboardMove.normalized); } else if (!Input.GetMouseButton(mousePanButton)) { // 只有既没有鼠标平移也没有键盘输入时才清零 MoveInput Vector3.zero; } } void UpdateFocusPoint() { Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { InteractionFocusPoint hit.point; } else { // 如果没有击中任何物体可以设置为相机前方一定距离的点或者null InteractionFocusPoint Camera.main.transform.position Camera.main.transform.forward * 10f; } } }这个模块的关键在于它将原始的InputAPI封装成了逻辑指令MoveInputRotateInputZoomInput和一个可选的InteractionFocusPoint。这样上层的相机逻辑就与具体的按键设置解耦了。3.2 相机逻辑与运动模块这是核心算法模块负责将输入指令转化为相机的目标变换。using UnityEngine; public class AdvancedCameraController : MonoBehaviour { public CameraInputModule inputModule; [Header(目标与跟随)] public Transform target; // 可选相机可以围绕或跟随一个目标 public Vector3 targetOffset new Vector3(0, 2, -5); // 相对于目标的偏移 public bool followTarget false; [Header(旋转控制)] public float rotationSmoothTime 0.1f; public Vector2 rotationLimitY new Vector2(-80, 80); // 垂直旋转限制 private float currentRotationX; private float currentRotationY; private Vector3 rotationSmoothVelocity; [Header(平移控制)] public float moveSpeed 10f; public float panSpeedMultiplier 1f; // 鼠标平移的额外速度系数 [Header(缩放控制)] public float zoomSpeed 20f; public float minZoomDistance 2f; public float maxZoomDistance 50f; private float currentZoomDistance 10f; [Header(边界与碰撞)] public bool enableCollision true; public LayerMask collisionMask -1; public float collisionRadius 0.3f; private Vector3 currentVelocity; void Start() { if (inputModule null) inputModule GetComponentCameraInputModule(); if (inputModule null) Debug.LogError(CameraInputModule is required!); // 初始化旋转角度为当前相机的欧拉角 Vector3 startRotation transform.eulerAngles; currentRotationX startRotation.y; currentRotationY startRotation.x; // 初始化缩放距离为相机到目标的距离如果有目标 if (target ! null) { currentZoomDistance Vector3.Distance(transform.position, target.position); } else { currentZoomDistance Vector3.Distance(transform.position, transform.position transform.forward * 10f); } } void LateUpdate() // 在Update之后执行确保目标移动已完成 { if (inputModule null) return; HandleRotation(); HandleZoom(); HandlePosition(); ApplyCollision(); } void HandleRotation() { if (inputModule.RotateInput ! Vector2.zero) { // 累积旋转输入 currentRotationX inputModule.RotateInput.x; currentRotationY - inputModule.RotateInput.y; // 注意Y轴取反符合鼠标操作直觉 currentRotationY Mathf.Clamp(currentRotationY, rotationLimitY.x, rotationLimitY.y); } // 如果有跟随目标且没有鼠标旋转输入时可以平滑看向目标可选 // 这里我们专注于处理输入驱动的旋转 // 计算目标旋转 Quaternion targetRotation Quaternion.Euler(currentRotationY, currentRotationX, 0); // 平滑旋转 transform.rotation Quaternion.Slerp(transform.rotation, targetRotation, rotationSmoothTime * Time.deltaTime * 10); } void HandleZoom() { if (Mathf.Abs(inputModule.ZoomInput) 0.01f) { // 基于当前距离进行缩放 currentZoomDistance - inputModule.ZoomInput * zoomSpeed * Time.deltaTime; currentZoomDistance Mathf.Clamp(currentZoomDistance, minZoomDistance, maxZoomDistance); } } void HandlePosition() { Vector3 targetPosition; if (followTarget target ! null) { // ***模式一围绕/跟随目标*** // 计算基于旋转和缩放距离的理想位置 Vector3 desiredPosition target.position - (transform.rotation * Vector3.forward) * currentZoomDistance; // 加上偏移量例如让相机稍微高于目标 desiredPosition transform.rotation * targetOffset; targetPosition desiredPosition; } else { // ***模式二自由漫游类似Scene视图*** // 这是迁移的关键将鼠标平移输入屏幕空间转换为世界空间移动 if (inputModule.MoveInput ! Vector3.zero) { // 将输入向量从屏幕/相机空间转换到世界空间 // 注意inputModule.MoveInput的x,y对应屏幕的左右和上下 Vector3 moveDelta transform.right * inputModule.MoveInput.x * panSpeedMultiplier transform.up * inputModule.MoveInput.y * panSpeedMultiplier; // 平滑移动到新位置 targetPosition Vector3.SmoothDamp(transform.position, transform.position moveDelta, ref currentVelocity, 0.1f); } else { // 如果没有平移输入则保持原位或根据其他逻辑更新 // 对于自由漫游模式缩放是通过改变FOV或向前后移动实现的这里我们结合缩放逻辑 // 缩放时沿视线方向移动 if (Mathf.Abs(inputModule.ZoomInput) 0.01f) { Vector3 zoomMove transform.forward * inputModule.ZoomInput * zoomSpeed * Time.deltaTime; targetPosition transform.position zoomMove; } else { targetPosition transform.position; } } } // 应用位置碰撞检测前 transform.position targetPosition; } void ApplyCollision() { if (!enableCollision || target null) return; Vector3 desiredPosition target.position - (transform.rotation * Vector3.forward) * currentZoomDistance; desiredPosition transform.rotation * targetOffset; RaycastHit hit; Vector3 dir desiredPosition - target.position; if (Physics.SphereCast(target.position, collisionRadius, dir.normalized, out hit, dir.magnitude, collisionMask)) { // 如果发生碰撞将相机拉到碰撞点前方 transform.position hit.point - dir.normalized * collisionRadius; // 同时更新当前缩放距离避免下次计算时又穿回去 currentZoomDistance Vector3.Distance(transform.position, target.position); } } // 提供一个外部调用方法模拟Scene视图的“F”聚焦功能 public void FocusOnTarget(Transform newTarget, float duration 0.5f) { // 这里可以扩展一个协程平滑地将相机移动到观察新Target的位置 target newTarget; followTarget true; // 可以立即重置距离和角度或启动平滑过渡动画 currentZoomDistance Mathf.Clamp(Vector3.Distance(transform.position, target.position), minZoomDistance, maxZoomDistance); Vector3 relativePos transform.position - target.position; currentRotationX Mathf.Atan2(relativePos.x, relativePos.z) * Mathf.Rad2Deg; currentRotationY -Mathf.Asin(relativePos.y / relativePos.magnitude) * Mathf.Rad2Deg; } }这个控制器实现了两种核心模式目标跟随模式相机始终尝试保持与一个target的相对位置和角度并可以围绕其旋转、缩放。这是实现“围绕物体观察”的经典模式类似于在Scene视图中选中一个物体后操作。自由漫游模式当没有指定target或关闭followTarget时相机进入自由模式。此时鼠标中键拖拽Pan会沿着相机自身的右方和上方移动完美模拟了Scene视图的平移感觉。滚轮缩放则沿着视线方向前进后退。关键迁移点解析鼠标平移的实现HandlePosition方法中自由漫游模式下的moveDelta计算。它将inputModule.MoveInput来自鼠标中键的屏幕XY变化乘以相机的transform.right和transform.up方向从而将屏幕上的拖拽完美映射为在世界平面上的移动。这是还原Scene视图操作感的核心代码。旋转与缩放的分离处理旋转直接修改相机的欧拉角而缩放通过改变currentZoomDistance这个逻辑变量来实现。在目标跟随模式下这个距离用于计算相机相对于目标的位置在自由模式下它直接转化为沿视线方向的位移。这种分离设计使得逻辑更清晰。平滑处理使用了Quaternion.Slerp和Vector3.SmoothDamp进行平滑插值避免了运动的生硬感。rotationSmoothTime和SmoothDamp的平滑时间参数是调节手感的关键。3.3 高级功能射线聚焦与区域限制Scene视图的“F”键聚焦是一个非常实用的功能。我们可以扩展我们的控制器来实现它。// 在AdvancedCameraController中添加 [Header(高级交互)] public float focusSmoothTime 0.3f; private Coroutine focusCoroutine; public void SmartFocus(Vector3 focusPointWorldPos, float targetDistance -1) { if (focusCoroutine ! null) StopCoroutine(focusCoroutine); focusCoroutine StartCoroutine(DoSmartFocus(focusPointWorldPos, targetDistance)); } IEnumerator DoSmartFocus(Vector3 focusPointWorldPos, float targetDistance) { Vector3 startPos transform.position; Quaternion startRot transform.rotation; // 计算理想的观察位置 if (targetDistance 0) targetDistance currentZoomDistance; // 保持当前距离 // 让相机看向焦点 Vector3 directionFromFocus (startPos - focusPointWorldPos).normalized; Quaternion endRot Quaternion.LookRotation(-directionFromFocus); // LookRotation需要看向焦点的方向所以取反 // 计算终点位置从焦点沿视线反方向退后targetDistance Vector3 endPos focusPointWorldPos - (endRot * Vector3.forward) * targetDistance; float elapsed 0; while (elapsed focusSmoothTime) { elapsed Time.deltaTime; float t Mathf.SmoothStep(0, 1, elapsed / focusSmoothTime); transform.position Vector3.Lerp(startPos, endPos, t); transform.rotation Quaternion.Slerp(startRot, endRot, t); yield return null; } transform.position endPos; transform.rotation endRot; // 更新内部状态 Vector3 euler endRot.eulerAngles; currentRotationX euler.y; currentRotationY euler.x; focusCoroutine null; }这个协程提供了平滑的聚焦动画比瞬间跳转体验好得多。你可以通过点击UI按钮或按特定按键如F键来触发它并传入通过射线检测得到的世界坐标。区域限制也是实战中常需要的比如防止相机飞出地图边界。// 在AdvancedCameraController中添加 [Header(区域限制)] public bool enableBounds false; public Bounds movementBounds; void ClampToBounds() { if (!enableBounds) return; Vector3 clampedPosition transform.position; clampedPosition.x Mathf.Clamp(clampedPosition.x, movementBounds.min.x, movementBounds.max.x); clampedPosition.y Mathf.Clamp(clampedPosition.y, movementBounds.min.y, movementBounds.max.y); clampedPosition.z Mathf.Clamp(clampedPosition.z, movementBounds.min.z, movementBounds.max.z); transform.position clampedPosition; } // 在LateUpdate末尾调用ClampToBounds();4. 实战调试与性能优化将脚本挂载到主相机上并配置好CameraInputModule后你就可以在Game视图获得类似Scene视图的操控体验了。但在实际项目中还需要注意以下几点。4.1 输入冲突与优先级管理在一个复杂的游戏中相机控制可能不是唯一的输入消费者。UI点击、角色控制等都会争夺输入事件。Unity的EventSystem会阻止点击UI时的场景操作。但对于键盘和鼠标拖拽我们需要更精细的管理。解决方案在CameraInputModule的Update方法开始可以检查EventSystem.current.IsPointerOverGameObject()。如果鼠标在UI上则忽略某些相机操作如旋转、平移但可能保留缩放如果游戏逻辑允许。using UnityEngine.EventSystems; // 需要引入命名空间 void HandleMouseInput() { // 检查鼠标是否在UI上 if (EventSystem.current ! null EventSystem.current.IsPointerOverGameObject()) { // 如果希望UI阻断所有相机鼠标操作则直接返回 // return; // 如果只希望阻断拖拽保留滚轮 RotateInput Vector2.zero; if (Input.GetMouseButton(mousePanButton)) { MoveInput Vector3.zero; return; // 不处理中键拖拽 } // 滚轮缩放可以保留 } // ... 原有的输入处理逻辑 }4.2 不同场景下的参数调优相机操控的“手感”极度依赖参数mouseRotateSensitivity/mousePanSensitivity根据项目类型调整。对于精细编辑类应用灵敏度要低对于快速观察的游戏灵敏度可以高一些。rotationSmoothTime平滑时间越长相机运动越“粘滞”感觉越沉稳越短则响应越迅速但也可能更“飘”。对于跟随角色的第三人称相机平滑时间通常需要设置如0.1s对于RTS或编辑器的自由相机可以设为0以获得即时反馈。zoomSpeed和min/maxZoomDistance必须根据场景尺度来设定。一个室内场景的缩放范围可能是[0.5, 10]而一个开放世界则可能是[5, 5000]。实操心得不要迷信默认值。最好的调参方法是创建一个简单的测试场景放置几个不同尺度的参考物体如一个胶囊体代表角色一个立方体代表建筑然后边操作边调整参数直到找到最符合项目需求和用户直觉的那组值。可以将调好的参数保存为ScriptableObject资产方便在不同相机配置间复用。4.3 性能考量LateUpdatevsUpdate相机控制务必放在LateUpdate中。这样可以确保相机在所有其他对象尤其是它要跟随的目标移动完毕后再更新避免出现一帧的抖动。碰撞检测开销Physics.SphereCast或Raycast在每帧调用是有开销的。如果相机移动不频繁可以改为在相机即将移动时检测。或者对于固定场景可以预先使用导航网格NavMesh或触发器体积来定义可移动区域而不是每帧进行物理检测。不必要的计算在自由漫游模式下如果target为null应跳过所有与目标相关的计算如碰撞检测中的target.position。4.4 常见问题与排查技巧问题1相机旋转时发生剧烈抖动或翻转。排查检查currentRotationY上下旋转的累加逻辑。确保使用了-而不是来处理鼠标Y轴输入因为屏幕Y轴向上为正而欧拉角X轴对应上下看向上旋转为负。同时务必用Mathf.Clamp将currentRotationY限制在[-80, 80]度之间避免万向节死锁导致的翻转。技巧在计算旋转时可以考虑使用Quaternion进行运算而非直接操作欧拉角但欧拉角对初学者更直观。关键是确保旋转顺序一致通常为Y-X-Z。问题2鼠标中键平移时相机移动方向不符合预期例如上下拖拽却让相机前后走。排查检查HandlePosition中自由漫游模式下的moveDelta计算。transform.right对应相机本地X轴左右transform.up对应本地Y轴上下。确保inputModule.MoveInput.x和.y与之正确相乘。可以打印出这些向量进行调试。问题3相机穿过墙壁或其他物体。排查首先确认enableCollision已勾选。检查collisionMask是否正确设置了需要碰撞的层。墙壁等障碍物应该位于这些层中。调整collisionRadius。这个值太小相机边缘可能会穿模太大则相机可能在离墙还很远时就停下。通常设置为略小于相机近裁剪面到模型中心的距离。SphereCast的起点是target.position如果target是null或者在墙另一侧检测会失效。对于自由相机可以考虑从相机位置向目标方向或视线反方向做检测。问题4在UI上操作时相机仍然响应了鼠标拖拽。排查按照4.1节的方法在输入模块中添加对EventSystem.current.IsPointerOverGameObject()的判断。确保UI元素的Raycast Target属性为true。问题5缩放时感觉卡顿或不线性。排查缩放输入inputModule.ZoomInput通常来自鼠标滚轮其值是一个整数如±1。在HandleZoom中我们将其乘以zoomSpeed和Time.deltaTime。如果zoomSpeed过大单次滚轮滚动会导致距离变化过大感觉像“跳变”。可以尝试降低zoomSpeed或者对滚轮输入进行平滑处理如使用Mathf.SmoothDamp来平滑currentZoomDistance本身。5. 扩展从PC到移动端的交互适配要将这套操控逻辑迁移到移动端触屏核心是重写CameraInputModule。我们需要处理触摸手势。public class TouchCameraInputModule : CameraInputModule { [Header(触摸设置)] public float touchRotateSensitivity 0.2f; public float touchPanSensitivity 0.01f; public float touchZoomSensitivity 0.005f; private Vector2? lastPanPosition; private float lastPinchDistance; void Update() { // 先调用基类处理可能的键盘输入如果外接键盘 base.HandleKeyboardInput(); if (Input.touchCount 1) { Touch touch Input.GetTouch(0); // 单指拖拽平移或旋转这里通常定义为平移 if (touch.phase TouchPhase.Began) { lastPanPosition touch.position; } else if (touch.phase TouchPhase.Moved lastPanPosition.HasValue) { Vector2 delta touch.position - lastPanPosition.Value; MoveInput new Vector3(-delta.x * touchPanSensitivity, -delta.y * touchPanSensitivity, 0); lastPanPosition touch.position; RotateInput Vector2.zero; // 单指通常不作为旋转 ZoomInput 0; } else if (touch.phase TouchPhase.Ended || touch.phase TouchPhase.Canceled) { lastPanPosition null; MoveInput Vector3.zero; } } else if (Input.touchCount 2) { // 双指手势缩放和旋转 Touch touch1 Input.GetTouch(0); Touch touch2 Input.GetTouch(1); if (touch1.phase TouchPhase.Began || touch2.phase TouchPhase.Began) { lastPinchDistance Vector2.Distance(touch1.position, touch2.position); lastPanPosition (touch1.position touch2.position) / 2; } else if (touch1.phase TouchPhase.Moved || touch2.phase TouchPhase.Moved) { float currentPinchDistance Vector2.Distance(touch1.position, touch2.position); // 缩放 ZoomInput (currentPinchDistance - lastPinchDistance) * touchZoomSensitivity; lastPinchDistance currentPinchDistance; // 双指中点移动作为平移可选或作为旋转 Vector2 currentMidPos (touch1.position touch2.position) / 2; if (lastPanPosition.HasValue) { Vector2 delta currentMidPos - lastPanPosition.Value; // 这里可以将delta作为旋转输入实现双指旋转 RotateInput new Vector2(delta.x * touchRotateSensitivity, delta.y * touchRotateSensitivity); // 或者作为平移输入 // MoveInput new Vector3(-delta.x * touchPanSensitivity, -delta.y * touchPanSensitivity, 0); } lastPanPosition currentMidPos; } else if (touch1.phase TouchPhase.Ended || touch2.phase TouchPhase.Ended) { lastPanPosition null; MoveInput Vector3.zero; RotateInput Vector2.zero; ZoomInput 0; } } else { // 没有触摸时确保输入归零除非有键盘输入 if (MoveInput.magnitude 0.01f) MoveInput Vector3.zero; RotateInput Vector2.zero; ZoomInput 0; } // 注意触摸输入不需要调用base.HandleMouseInput() } }这个触摸模块将单指拖拽映射为平移双指捏合映射为缩放双指滑动映射为旋转或平移取决于你的设计。你需要替换原有的CameraInputModule为这个TouchCameraInputModule并根据实际手感调整灵敏度参数。这样同一套AdvancedCameraController逻辑就能同时服务于PC和移动端真正实现了交互逻辑与平台输入的解耦。整个迁移过程的核心思想是将Scene视图那种直观、流畅的操控感通过清晰的输入抽象、数学计算和状态管理在Game视图中用代码复现出来。它考验的不是高深的图形学知识而是对三维空间变换、用户交互设计和代码架构的扎实理解。