Unity Input System实现3D模型手势交互:旋转与缩放功能开发指南
1. 项目概述为什么需要手势交互在移动端和触屏设备上浏览3D模型手势操作几乎是唯一自然且高效的交互方式。想象一下你打开一个产品展示应用想看看新款手机的3D模型如果只能用屏幕上的虚拟按钮来旋转、缩放那体验得多别扭。直接用手指捏合缩放、滑动旋转这才是符合直觉的操作。这个项目要做的就是利用Unity全新的Input System输入系统快速为你的3D模型查看器注入这种“直觉式”的交互能力。我见过不少开发者尤其是刚接触Unity的朋友一提到手势识别就觉得头大要么去网上找复杂的插件要么自己写一堆Touch事件处理逻辑结果代码又乱又容易出Bug。其实自从Unity推出了新的Input System处理手势输入已经变得前所未有的简单和规范。它把输入抽象成“动作”Actions你只需要关心“旋转”或“缩放”这个意图而不用去管这个意图是来自两根手指的触摸、鼠标滚轮还是游戏手柄的摇杆。这种设计让代码更清晰也更容易适配不同平台。所以这篇内容就是带你绕过那些弯路直接上手最现代、最优雅的解决方案。无论你是想做一个AR产品展示、一个教育类应用中的模型观察模块还是一个简单的3D画廊这套方法都能让你在5分钟的核心代码时间内为模型加上流畅的旋转和缩放功能。我们不仅会写代码更会讲清楚Input System背后的设计哲学和配置逻辑让你知其然更知其所以然。2. Input System核心概念与项目配置在动手写代码之前我们必须先和Unity的Input System打好招呼。它和传统的Input.GetMouseButton或Input.touches有本质区别理解这一点能避免很多后续的困惑。2.1 新旧输入系统对比与迁移必要性旧的输入管理器Input Manager是过程式的。你需要每一帧去查询“鼠标左键按下了吗”“当前有几个触摸点它们的坐标是什么”。这种模式下逻辑和输入设备紧密耦合。如果你的应用要同时支持触屏和鼠标就得写两套判断逻辑。而新的Input System是事件驱动和基于动作的。你定义一些“动作”比如“Look”查看和“Zoom”缩放。然后你可以为这些动作绑定多种控制方案Control Schemes例如为“Look”动作绑定“鼠标拖拽”和“单指触摸拖拽”为“Zoom”动作绑定“鼠标滚轮”和“双指捏合”。在代码中你只监听“Look”和“Zoom”动作的事件完全不用关心当前用户用的是鼠标还是手指。这大大提升了代码的复用性和可维护性也是Unity官方主推的方向。注意从2020版开始Unity更倾向于使用新的Input System。对于新项目强烈建议直接使用它。如果你的老项目想迁移也可以两者并存但需要处理一些兼容性问题。2.2 安装与启用Input System Package首先确保你的Unity版本在2019.4或以上并且使用Package Manager来安装Input System。打开Unity点击顶部菜单栏的Window Package Manager。在Package Manager窗口左上角确保查看的是Unity Registry。在列表中找到Input System包点击右侧的Install按钮。安装完成后Unity会弹出一个对话框提示你需要重启编辑器并启用新的输入后端。点击“Yes”重启。重启后你需要验证新系统是否已启用。点击Edit Project Settings打开项目设置。在左侧列表中选择Player。在Player设置中找到Other Settings区域。向下滚动找到Active Input Handling选项。确保其被设置为Input System Package (New)或Both。如果选择“Both”意味着新旧系统可以同时工作但为了项目纯净我们这里选择Input System Package (New)。完成这一步你的项目就正式启用了全新的输入系统。2.3 创建与配置Input Actions AssetInput Actions Asset输入动作资源文件是整个Input System配置的核心。它就像一个输入控制的“清单”里面定义了你所有需要的操作。在Project窗口右键选择Create Input Actions。我通常将其命名为“ModelViewerControls”。双击这个新创建的.inputactions文件会打开一个专门的配置窗口。这个窗口分为三栏Action Maps动作映射集、Actions动作、Properties属性。一个Action Map可以理解为一套控制方案比如“PlayerControls”玩家控制、“UIControls”UI控制。对于我们的模型查看器一个Map就够了。点击Action Maps列表下方的“”号新建一个Map命名为“ModelViewer”。选中新建的ModelViewerMap然后在中间的Actions栏点击“”号来添加我们需要的具体动作。第一个动作Look(类型: Value / 控制类型: Vector2)。这个动作用于旋转模型。Value类型意味着它会持续返回一个值如手指移动的Delta。Vector2对应屏幕上的X和Y方向移动。第二个动作Zoom(类型: Value / 控制类型: Axis)。这个动作用于缩放模型。Axis类型是Value的一种特指一维的、有正负的值比如鼠标滚轮。接下来是关键为这两个动作绑定具体的输入控件。选中Look动作在右侧Properties栏的Bindings下点击“”添加绑定路径。第一个绑定针对鼠标。点击路径选择器No Binding选择Mouse position是不对的因为我们需要的是移动差值Delta。应该选择Mouse delta。这样当鼠标拖动时Look动作就会返回每一帧鼠标移动的Vector2值。第二个绑定针对触摸屏。再次点击“”选择Touchscreen primaryTouch/delta。这绑定了主触摸点第一根手指的移动差值。选中Zoom动作同样添加绑定。第一个绑定针对鼠标滚轮。选择Mouse scroll/y。注意y轴代表垂直滚动。向上滚动通常我们想让模型放大这个对应关系我们可以在代码里处理。第二个绑定针对触摸屏捏合。这是手势识别的核心。Input System内置了“Two Modifiers”来组合多个触摸点。我们需要手动编辑路径。点击路径选择器选择Touchscreen position。然后你会看到绑定下方出现了“And”和“Or”的选项。我们需要的是两根手指所以点击“”添加一个“And”组合。在“And”下再次点击“”添加另一个绑定同样选择Touchscreen position。现在我们需要告诉系统这是两个不同的触摸点。分别点击两个position绑定在Properties栏将Unidentified改为#1和#2。这表示触摸点1和触摸点2。但这只是绑定了两个点的位置还没计算距离差值。我们需要用“Up”和“Down”修饰键来模拟捏合过程吗不更简单的方法是Input System的Touchscreen控件直接提供了一个Touchscreen radius吗不对并没有直接提供双指距离。实际上对于捏合缩放标准做法是监听两个触摸点的位置然后在代码中实时计算它们之间的距离变化。Input System的绑定更多是用于触发复杂手势计算通常放在代码层。因此对于Zoom的触摸绑定一个更实用的方法是不直接绑定缩放量而是绑定一个用于触发缩放计算的“信号”。我们可以创建一个Button类型的动作例如ZoomTouch来检测是否有两根手指按下然后在代码中获取这两根手指的位置进行计算。但为了本教程的简洁和直接使用Zoom这个Axis动作我们可以采取一个折中方案暂时只绑定鼠标滚轮。双指缩放的逻辑我们将在脚本中通过直接访问Touch数据来实现。这是一种混合方案既利用了Input System管理鼠标/手柄输入又直接处理了触摸原生数据以实现复杂手势。这在实际开发中非常常见。经过以上配置你的Input Actions Asset应该包含了ModelViewer这个Action Map以及Look和Zoom两个动作其中Look绑定了鼠标Delta和触摸DeltaZoom绑定了鼠标滚轮。3. 手势旋转功能的核心实现旋转功能的目标是当用户拖动鼠标或单指触摸时让3D模型围绕其自身的中心点进行旋转拖动方向自然映射到模型的旋转方向。3.1 创建模型控制器脚本在Hierarchy中选中你的3D模型或者一个包含模型的空物体在Inspector中点击“Add Component”新建一个C#脚本命名为ModelViewerController。首先我们需要引用Input System的命名空间并声明对Input Actions的引用。using UnityEngine; using UnityEngine.InputSystem; // 关键命名空间 public class ModelViewerController : MonoBehaviour { // 引用我们创建的Input Actions资源 [SerializeField] private InputActionAsset inputActions; // 用于旋转的输入动作 private InputAction lookAction; // 用于缩放的输入动作 private InputAction zoomAction; // 旋转灵敏度 [SerializeField] private float rotationSpeed 0.5f; // 缩放灵敏度 [SerializeField] private float zoomSpeed 0.01f; // 缩放范围限制 [SerializeField] private float minZoom 0.5f; [SerializeField] private float maxZoom 5f; private Vector3 currentRotation Vector3.zero; private float currentZoom 1.0f; void Awake() { // 从Asset中找到我们定义的Action Map和Actions var modelViewerMap inputActions.FindActionMap(ModelViewer); lookAction modelViewerMap.FindAction(Look); zoomAction modelViewerMap.FindAction(Zoom); // 启用这个Action Map modelViewerMap.Enable(); } }将脚本挂载到物体上后把之前创建的ModelViewerControls.inputactions资源拖拽到脚本的Input Actions字段上完成赋值。3.2 解析Look输入与模型旋转逻辑旋转的逻辑是每一帧获取lookAction读取到的输入值一个Vector2这个值代表了本帧鼠标或手指的移动量。我们将这个屏幕上的移动量映射到模型在三维空间中的旋转角度上。通常我们让水平拖动X轴控制模型绕世界坐标Y轴或物体自身Y轴旋转实现左右转让垂直拖动Y轴控制模型绕自身X轴旋转实现上下看。这里有一个常见陷阱直接使用输入值累加会导致旋转方向不符合直觉比如向上拖动物体却向下转。这是因为屏幕坐标Y轴向下为正而旋转角度向上为正。所以我们需要对Y轴输入取反。我们在Update函数中实现这个逻辑void Update() { HandleRotation(); HandleZoom(); // 缩放逻辑下一节实现 } private void HandleRotation() { // 读取Look动作的本帧输入值Delta Vector2 lookDelta lookAction.ReadValueVector2(); // 如果输入量极小忽略防止微小抖动 if (lookDelta.magnitude 0.01f) return; // 将屏幕移动量转换为旋转角度增量 // X轴移动对应Y轴旋转左右Y轴移动对应X轴旋转上下并对Y取反 Vector3 rotationDelta new Vector3(-lookDelta.y, lookDelta.x, 0) * rotationSpeed; // 累加到当前的旋转角度上 currentRotation rotationDelta; // 可选限制上下旋转角度避免模型翻转到奇怪的角度 currentRotation.x Mathf.Clamp(currentRotation.x, -85f, 85f); // 应用旋转。使用欧拉角直接赋值或者使用Quaternion.Euler // 注意这里使用局部旋转Rotate还是世界旋转取决于需求。通常对于查看器我们使用局部旋转。 transform.localEulerAngles currentRotation; }实操心得rotationSpeed这个参数需要根据你的模型大小和摄像机距离进行微调。值太小旋转迟钝值太大又容易失控。我通常会在编辑模式下给它一个较大的范围比如0.1到2然后在游戏运行时通过脚本动态调整或者提供一个设置选项给用户。3.3 平滑处理与阻尼系数上面的代码是即时旋转手指一动模型立刻跟着动停得也干脆。但有时候我们想要一种更平滑、带有一点惯性感的旋转效果这样显得更自然尤其是在触屏设备上。我们可以引入一个“当前旋转速度”的概念用输入去改变这个速度而不是直接改变角度。同时每一帧让这个速度慢慢衰减。这需要用到简单的物理模拟。[SerializeField] private float rotationSmoothTime 0.1f; // 平滑时间 private Vector3 currentRotationVelocity; // 当前旋转角速度欧拉角形式仅用于平滑计算 private void HandleRotationSmooth() { Vector2 lookDelta lookAction.ReadValueVector2(); if (lookDelta.magnitude 0.01f) { // 没有输入时让目标增量为0速度会平滑衰减至0 lookDelta Vector2.zero; } // 计算目标旋转增量 Vector3 targetRotationDelta new Vector3(-lookDelta.y, lookDelta.x, 0) * rotationSpeed; // 计算目标旋转角度当前角度目标增量 Vector3 targetRotation currentRotation targetRotationDelta; targetRotation.x Mathf.Clamp(targetRotation.x, -85f, 85f); // 使用Mathf.SmoothDampAngle对每个轴进行平滑插值 // SmoothDampAngle会处理角度环绕比如从350度平滑到10度 float smoothX Mathf.SmoothDampAngle(currentRotation.x, targetRotation.x, ref currentRotationVelocity.x, rotationSmoothTime); float smoothY Mathf.SmoothDampAngle(currentRotation.y, targetRotation.y, ref currentRotationVelocity.y, rotationSmoothTime); float smoothZ Mathf.SmoothDampAngle(currentRotation.z, targetRotation.z, ref currentRotationVelocity.z, rotationSmoothTime); currentRotation new Vector3(smoothX, smoothY, smoothZ); transform.localEulerAngles currentRotation; }在Update中调用HandleRotationSmooth代替HandleRotation。这样当你快速滑动并松开手指后模型还会依惯性继续旋转一小段距离并慢慢停下手感会好很多。rotationSmoothTime控制着平滑的力度值越大惯性感越强停止得越慢。4. 手势缩放功能的核心实现缩放功能的目标是通过鼠标滚轮或双指捏合改变模型相对于观察者的视觉大小。在3D空间中最直接的实现方式不是改变模型本身的缩放这会影响碰撞体等而是改变摄像机与模型之间的距离或者改变一个包含模型的“容器”物体的局部缩放。4.1 基于鼠标滚轮的缩放实现我们先实现简单的鼠标滚轮缩放。根据之前的配置zoomAction会返回鼠标滚轮的滚动值。在大多数鼠标上向前滚动远离用户返回正值向后滚动返回负值。我们通常将正值定义为“放大”让模型看起来更大负值定义为“缩小”。我们可以通过调整模型父物体的局部缩放localScale来实现private void HandleZoom() { // 读取Zoom动作的本帧输入值对于鼠标滚轮就是scroll/y float zoomDelta zoomAction.ReadValuefloat(); // 如果输入量极小忽略 if (Mathf.Abs(zoomDelta) 0.01f) return; // 计算新的缩放值。滚轮向上正放大所以加上一个正增量。 // zoomSpeed控制缩放灵敏度zoomDelta是输入强度。 float zoomChange zoomDelta * zoomSpeed; currentZoom zoomChange; // 将缩放值限制在预设范围内 currentZoom Mathf.Clamp(currentZoom, minZoom, maxZoom); // 应用缩放。注意这里直接修改本物体的缩放。 // 如果你的模型是另一个子物体可能需要缩放父物体。 transform.localScale Vector3.one * currentZoom; }注意事项直接修改模型本身的缩放在某些情况下可能不是最佳选择。例如如果你的模型带有刚体、碰撞器或复杂的骨骼动画改变其全局缩放可能会引起物理模拟错误或动画异常。更健壮的做法是创建一个空的GameObject作为“查看器容器”将你的模型作为其子物体。然后将ModelViewerController脚本挂在这个容器上缩放只作用于容器模型保持原始缩放localScale Vector3.one。这样隔离了变换操作更安全。4.2 双指捏合缩放的识别与计算这是手势功能的核心难点。Input System的Action Binding对于复杂的多点触控手势如捏合、旋转支持还不够直接通常需要我们在代码中手动处理Touchscreen的输入数据。思路是在每一帧检查当前有效的触摸点数量。如果恰好是两个就计算这两个触摸点当前帧的距离currentDistance和上一帧的距离previousDistance。两者的差值或比值就反映了捏合手势的“力度”和“方向”。我们需要在脚本中增加一些变量来跟踪触摸状态private Vector2 touch1PrevPos; private Vector2 touch2PrevPos; private bool wasZoomingLastFrame false;然后在HandleZoom方法中加入触摸缩放的逻辑private void HandleZoom() { float zoomDelta 0f; // 1. 处理鼠标滚轮输入 zoomDelta zoomAction.ReadValuefloat(); // 鼠标滚轮输入通常较小直接累加 // 2. 处理触摸捏合输入 Touchscreen touchscreen Touchscreen.current; if (touchscreen ! null) { // 获取所有触摸点 var touches touchscreen.touches; int touchCount touches.Count; if (touchCount 2) { // 获取两个触摸点的当前位置 Vector2 touch1Pos touches[0].position.ReadValue(); Vector2 touch2Pos touches[1].position.ReadValue(); if (wasZoomingLastFrame) { // 计算当前帧两指距离 float currentDistance Vector2.Distance(touch1Pos, touch2Pos); // 计算上一帧两指距离需要存储上一帧位置 float previousDistance Vector2.Distance(touch1PrevPos, touch2PrevPos); // 距离差即为缩放量。距离变大正差应放大模型距离变小负差应缩小。 // 为了和鼠标滚轮逻辑统一正放大负缩小这里用 current - previous。 // 再乘以一个系数zoomSpeedTouch来调整灵敏度这个系数通常比鼠标滚轮的zoomSpeed大。 float touchZoomDelta (currentDistance - previousDistance) * zoomSpeedTouch; zoomDelta touchZoomDelta; } // 记录当前帧位置供下一帧使用 touch1PrevPos touch1Pos; touch2PrevPos touch2Pos; wasZoomingLastFrame true; } else { wasZoomingLastFrame false; } } // 综合两种输入的缩放量 if (Mathf.Abs(zoomDelta) 0.01f) { currentZoom zoomDelta; currentZoom Mathf.Clamp(currentZoom, minZoom, maxZoom); transform.localScale Vector3.one * currentZoom; } }你需要额外定义一个zoomSpeedTouch变量如0.005f来控制触摸缩放的灵敏度因为它和鼠标滚轮的数值范围完全不同。核心技巧计算距离差时(currentDistance - previousDistance)这个公式决定了手势方向。如果双指张开距离变大差值为正模型放大符合直觉。你也可以用(previousDistance - currentDistance)那样就需要反转缩放增量的正负号。关键是要和你的视觉反馈统一并在实际设备上测试手感。4.3 缩放中心与视觉优化上面的缩放是以模型自身的轴心点Pivot为中心进行的。在Unity中模型的轴心点是在建模软件中确定的。如果轴心点不在模型的视觉中心缩放时模型就会“漂走”体验很糟糕。解决方案建模时确保轴心点正确这是最根本的解决方法。在Blender、Maya或3ds Max中导出模型前将轴心点设置到模型的几何中心或期望的缩放中心。代码动态调整如果无法修改模型可以创建一个空物体作为父节点将模型放入其中并调整模型子物体的位置使得父物体的轴心点位于模型的视觉中心。然后对父物体进行缩放操作。基于屏幕中心缩放更高级的交互是无论手指捏合的位置在哪里模型都仿佛以屏幕中心为锚点进行缩放。这需要更复杂的计算涉及到将屏幕触摸点转换到世界空间并动态调整模型位置来补偿缩放带来的偏移。对于大多数模型查看器确保轴心点正确已经足够。此外缩放时加入平滑插值Lerp或SmoothDamp也能极大提升手感避免缩放显得生硬。实现方式与旋转平滑类似这里不再赘述。5. 输入控制方案的进阶配置与优化基础功能实现后我们需要考虑更多实际场景让控制方案更健壮、更专业。5.1 多控制方案适配鼠标、触摸与手柄Input System的强大之处在于可以轻松定义多套控制方案。我们之前只绑定了鼠标和触摸。如果你想支持游戏手柄的右摇杆来控制旋转该怎么做回到ModelViewerControls.inputactions文件编辑Look动作。在Look的Bindings下点击“”添加一个新的绑定。选择Gamepad rightStick。你可能会发现手柄摇杆的数值范围-1到1和鼠标Delta的范围差异很大。这会导致手柄控制过于灵敏或迟钝。这时就需要用到Processors处理器。选中你刚添加的rightStick绑定在Properties栏找到Processors点击“Add Processor”。添加一个Scale Vector2处理器。这个处理器可以倍增输入值。将X和Y因子都设置为一个较小的数比如0.1或0.05这样手柄摇杆的输入就会被缩放变得和鼠标Delta量级相近。同理你也可以为Zoom动作绑定手柄的触发器Gamepad rightTrigger和leftTrigger用两个触发器的差值来控制缩放。记得为这些绑定添加合适的Processors如Scale或Invert来调整输入曲线和方向。5.2 使用Interactions提升交互感Interactions交互可以定义输入动作如何被触发。例如我们可以为Look动作添加一个Hold交互要求用户长按0.2秒后才开始旋转这样可以避免误触。或者添加Tap交互来实现点击模型某个部位触发事件。选中Look动作在Properties栏找到Interactions点击“Add Interaction”。选择Hold。你可以设置Duration按住时间和Press Point触发阈值。在代码中你可以通过lookAction.triggered来判断动作是否被触发满足了Interaction条件而lookAction.ReadValueVector2()仍然会返回输入值。对于模型查看器通常不需要为旋转缩放添加Hold交互因为这会降低响应速度。但如果你有点击模型弹出信息的需求为某个“Select”动作添加Tap交互就非常有用。5.3 在UI上禁用模型控制一个常见的需求是当手指在UI按钮上操作时不应该触发模型旋转或缩放。Unity的EventSystem和Input System可以很好地协作解决这个问题。Input System的PlayerInput组件或直接通过InputActionAsset启用的动作默认会收到所有输入。我们需要让UI元素“吃掉”这些事件。确保你的UI元素如按钮、面板挂载了标准的UnityEngine.UI组件如Button,Image等并且Canvas上有GraphicRaycaster组件。这样当点击发生在UI上时EventSystem.current.IsPointerOverGameObject()会返回true对于触摸需要用EventSystem.current.IsPointerOverGameObject(touch.fingerId)。我们可以在处理输入的代码开头加入一个检查private bool IsPointerOverUI() { // 检查鼠标 if (EventSystem.current.IsPointerOverGameObject()) return true; // 检查触摸 if (Touchscreen.current ! null) { foreach (var touch in Touchscreen.current.touches) { if (touch.phase.ReadValue() UnityEngine.InputSystem.TouchPhase.Began) { if (EventSystem.current.IsPointerOverGameObject(touch.touchId.ReadValue())) return true; } } } return false; } private void HandleRotation() { if (IsPointerOverUI()) return; // 如果点在UI上不处理模型旋转 // ... 原有的旋转逻辑 }这样当用户与UI交互时模型会保持静止避免了操作冲突。6. 性能优化与常见问题排查功能实现后在真机特别是移动设备上测试和优化至关重要。6.1 移动设备上的性能考量帧率与输入采样在Update中每帧读取输入是标准的做法对于旋转缩放这种需要即时反馈的操作是合适的。但要确保你的Update函数本身不包含重负载计算。如果游戏逻辑复杂可以考虑将输入处理放在FixedUpdate中并用InputSystem.onEvent事件来获得更精确的输入时序但这对于查看器应用通常杀鸡用牛刀。手势识别的性能双指距离计算Vector2.Distance涉及开平方根运算Mathf.Sqrt这是一个相对耗时的操作。但在每一帧只计算一次对现代手机来说压力微乎其微。如果极端关注性能可以比较距离的平方sqrMagnitude来代替实际距离因为缩放只需要距离的变化趋势变大或变小而不需要精确的欧氏距离。公式变为(currentPos1 - currentPos2).sqrMagnitude - (prevPos1 - prevPos2).sqrMagnitude。但要注意变化量的大小会不同需要重新调整zoomSpeedTouch系数。对象缩放与Draw Call缩放一个包含复杂子物体网格的父对象不会增加Draw Call。但如果你是通过改变摄像机距离来实现缩放则对渲染没有影响。主要性能消耗在于矩阵变换计算。6.2 常见Bug与解决方案实录在实际开发中你几乎一定会遇到下面这些问题问题1模型旋转轴混乱或者旋转方向与手指拖动方向相反。排查检查HandleRotation函数中将lookDelta转换为rotationDelta的那行代码new Vector3(-lookDelta.y, lookDelta.x, 0)。尝试交换x和y或者去掉-号。解决旋转映射没有标准答案取决于你的摄像机朝向和模型初始朝向。准备一个简单的立方体模型通过试错调整这个公式直到水平拖动是左右转垂直拖动是上下转且方向符合直觉。问题2双指缩放时模型抖动或缩放不跟手。排查1检查touch1PrevPos和touch2PrevPos的更新时机。它们必须在计算完previousDistance后立即用当前帧的位置更新为下一帧做准备。顺序错误会导致距离计算错乱。排查2zoomSpeedTouch系数不合适。值太小会导致缩放缓慢值太大会导致缩放跳跃。在真机上调试这个值从0.001开始尝试。排查3触摸点ID可能发生变化。Input System的touches集合顺序不一定是固定的。我们代码假设touches[0]和touches[1]就是上一帧的那两个手指。如果手指抬起再按下顺序可能变。更健壮的做法是记录触摸点的IDtouchId并通过ID来匹配前后帧的触摸点。但对于简单的双指捏合且假设用户不会在缩放过程中抬起所有手指当前的简单实现大多情况下是可行的。问题3在编辑器里用手势模拟如按住Alt鼠标拖动没问题但真机上没反应。排查首先检查Input Actions Asset是否正确赋值给了脚本。其次确保在真机构建时Player Settings中包含了Touchscreen控件。点击Edit Project Settings选择Input System Package在Supported Devices列表中确保有Touchscreen。如果没有点击“”添加它。问题4旋转或缩放时模型会突然跳一下。排查这通常是第一帧输入值异常导致的。Input System在动作激活的第一帧可能会读取到一个累积值或初始值。可以在Awake或Start中在启用动作后立即调用一次lookAction.ReadValueVector2()和zoomAction.ReadValuefloat()来“消耗”掉这个可能的初始值但不做处理。6.3 输入调试技巧Unity Input System提供了强大的调试工具。在运行时打开Window Analysis Input Debugger。在Input Debugger中你可以看到所有激活的设备、它们的输入状态、以及所有已启用的Input Actions的实时数值。点击你的Look和Zoom动作可以看到它们绑定了哪些控件以及当前读出的值。这是排查“为什么我的动作没触发”或“值为什么不对”的最直接方法。最后别忘了在不同分辨率和尺寸的设备上进行测试。手势操作的舒适区如手指可触及范围、最小触摸目标大小会因设备而异。确保你的模型查看区域足够大并且旋转缩放控件不会与屏幕边缘的UI元素冲突。经过这些步骤一个流畅、稳定、专业的3D模型手势查看器就真正完成了。

相关新闻

nlohmann/json 深度解析:让 C++ 解析 JSON 像喝水一样简单

nlohmann/json 深度解析:让 C++ 解析 JSON 像喝水一样简单

一、nlohmann/json 是什么? 一句话:nlohmann/json(官方名 JSON for Modern C)是 C 生态里最"亲民"的开源 JSON 解析库,由德国开发者 Niels Lohmann 维护,GitHub 上 star 数接近 5 万&#xff0c…

2026/8/6 22:25:57 阅读更多 →
MetaGPT | 第十六章:RAG、文档存储、记忆与经验池

MetaGPT | 第十六章:RAG、文档存储、记忆与经验池

预计阅读时间:60 分钟 难度等级:高级 本章导读 前面几章我们已经看过 MetaGPT 的核心执行链路: Team-> Environment-> Role-> Action-> Message也看过 RoleZero 如何在动态任务里选择工具,以及 DataInterpreter 如何围绕 Notebook 推进数据分析任务。 本章…

2026/8/6 21:10:03 阅读更多 →
计算机网络零基础入门:CSGuide带你掌握TCP/IP、HTTP等核心协议

计算机网络零基础入门:CSGuide带你掌握TCP/IP、HTTP等核心协议

计算机网络零基础入门:CSGuide带你掌握TCP/IP、HTTP等核心协议 【免费下载链接】CSGuide 🔥 计算机学习路线,计算机网络、操作系统、C、Java 等面试复习题库 项目地址: https://gitcode.com/gh_mirrors/cs/CSGuide 计算机网络是现代信…

2026/8/5 20:45:11 阅读更多 →

最新新闻

【路径规划】基于信息增益 RRT(Information Gain RRT) 算法求解不确定性二维路径规划问题附Matlab代码

【路径规划】基于信息增益 RRT(Information Gain RRT) 算法求解不确定性二维路径规划问题附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室🍊个人信条:格物致知,完整Matlab代码获取及仿真…

2026/8/6 23:03:52 阅读更多 →
AI二创攻略_华强买瓜篇⑧:剪辑与合规篇——后期合成与法律边界

AI二创攻略_华强买瓜篇⑧:剪辑与合规篇——后期合成与法律边界

系列目录:第一篇:现象篇 | 第二篇:工具篇 | 第三篇:剧本篇 | 第四篇:全流程教程 | 第五篇:角色一致性 | 第六篇:图生视频 | 第七篇:声音篇 | 第八篇:剪辑与合规篇 【AI二…

2026/8/6 23:03:52 阅读更多 →
电力系统多能源协同调度与N-k安全优化模型

电力系统多能源协同调度与N-k安全优化模型

1. 项目概述:电力系统多能源协同调度挑战在新型电力系统建设背景下,风电、光伏等波动性电源渗透率持续提升。我们团队开发的这个N-k安全优化调度模型,正是为了解决高比例可再生能源接入带来的运行控制难题。光热电站(CSP&#xff…

2026/8/6 23:03:52 阅读更多 →
解决php-version常见问题:版本找不到、路径配置错误怎么办

解决php-version常见问题:版本找不到、路径配置错误怎么办

解决php-version常见问题:版本找不到、路径配置错误怎么办 【免费下载链接】php-version stupid simple PHP version management 项目地址: https://gitcode.com/gh_mirrors/ph/php-version php-version是一款简单实用的PHP版本管理工具,能够帮助…

2026/8/6 23:03:52 阅读更多 →
Stanchion实战案例:如何用列式表优化传感器数据存储与分析

Stanchion实战案例:如何用列式表优化传感器数据存储与分析

Stanchion实战案例:如何用列式表优化传感器数据存储与分析 【免费下载链接】stanchion A SQLite extension that brings column-oriented tables to SQLite 项目地址: https://gitcode.com/gh_mirrors/sta/stanchion 在物联网和工业监控系统中,传…

2026/8/6 23:03:52 阅读更多 →
Linux日志系统:Syslog与Journald配置与实战

Linux日志系统:Syslog与Journald配置与实战

1. 日志系统概述与核心组件解析在企业级Linux系统管理中,日志分析是运维工作的"眼睛"。RH134课程中重点介绍的Syslog和systemd-journald构成了现代Linux系统的日志管理骨架。这两个组件各司其职又相互协作:Syslog作为传统的日志服务&#xff0…

2026/8/6 23:02:52 阅读更多 →

日新闻

深入解析LimboAI C++内核:架构设计与性能优化实战

深入解析LimboAI C++内核:架构设计与性能优化实战

1. 项目概述:为什么我们需要深入LimboAI的C内核?如果你是一名使用Godot引擎的游戏开发者,尤其是对AI行为逻辑有较高要求的项目,那么LimboAI这个名字你大概率不会陌生。它作为Godot 4生态中一个备受瞩目的行为树与状态机插件&#…

2026/8/6 0:00:06 阅读更多 →
Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

1. 项目概述与核心思路大家好,我是老张,一个在游戏开发一线摸爬滚打了十多年的老码农。今天咱们接着聊《空洞骑士》风格2D动作游戏的Demo制作。上一期我们搭好了基础框架,处理了角色移动和碰撞,这一期,我们要让游戏世界…

2026/8/6 0:00:06 阅读更多 →
被动防火门市场前景发展趋势

被动防火门市场前景发展趋势

被动防火门依靠材质结构、密闭构造阻隔烟火蔓延,无需电控启动,是建筑被动消防系统核心构件,行业依托新规管控、城市更新、工业安全升级迎来稳定扩容,整体朝着合规化、专项化、低碳化、智能化方向发展。现阶段 GB12955‑2024 新版国…

2026/8/6 0:00:06 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/6 22:02:27 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/6 22:02:27 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/6 22:02:27 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/5 23:28:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/6 22:02:28 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/5 23:46:51 阅读更多 →