ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Unity移动开发:Easy Touch 5.0.8手势控制插件深度解析与实战

Unity移动开发:Easy Touch 5.0.8手势控制插件深度解析与实战 1. 项目概述为什么Easy Touch 5依然是移动开发的“瑞士军刀”在Unity移动端开发里输入处理是个绕不开的坎。尤其是多点触控从简单的点击、拖拽到复杂的双指缩放、旋转如果自己从零开始写不仅要处理不同平台iOS/Android的输入差异还得考虑性能、手势识别精度和代码的维护性一套下来少说也得折腾个把星期。所以一个成熟、稳定的第三方触摸控制插件就成了项目里的“标配”。Easy Touch 5特别是我们这次要深挖的5.0.8版本就是这样一个在社区里经久不衰的经典选择。它不是什么新鲜出炉的酷炫框架但胜在稳定、全面、上手快就像一个工具箱里的“瑞士军刀”你可能不会天天炫耀它但真遇到触摸交互的需求拿起来就能用而且大概率能解决问题。我最初接触Easy Touch也是在几年前的一个AR项目里需要在手机屏幕上实现物体的旋转和缩放。当时试过几种方案要么识别不灵敏要么代码写得臃肿。后来用了Easy Touch 5基本上就是“拖拖组件写写回调”的事项目进度一下子就提上来了。虽然现在Unity自己的Input System也在不断完善但对于需要快速原型开发、或者项目历史包袱较重比如还在用旧的Input Manager的团队来说Easy Touch 5凭借其极低的接入成本和丰富的预设功能依然有很强的实用价值。5.0.8这个版本是一个经过多次迭代后的稳定版修复了不少早期版本的bug文档和社区资源也相对丰富是投入学习性价比很高的一个节点。简单来说Easy Touch 5.0.8的核心价值在于它把复杂的、底层的手势识别和事件分发机制封装成了直观的组件和事件监听器。开发者不需要关心手指按下时TouchPhase是怎么变化的也不需要自己去计算两指间的向量和角度差只需要关注“当用户做了一个捏合手势时我的游戏对象应该缩放多少”这样的业务逻辑。这对于独立开发者、小团队或者开发周期紧张的商业项目来说能节省大量时间和精力。2. 核心架构与组件深度解析Easy Touch 5的架构设计遵循了“事件驱动”和“组件化”的思想整个系统可以看作是由几个核心的“管理器”组件和一系列“监听器”组件构成的。理解这个结构是灵活使用它的关键。2.1 EasyTouch核心管理器引擎的心脏EasyTouch组件是整个插件的中枢神经系统它是一个单例Singleton通常你只需要在场景中放置一个GameObject并挂上这个组件即可。它的配置面板看起来选项很多但主要分为几大块1. 通用设置General这里定义了插件的全局行为。比如Enable Remote选项它允许你通过Unity Remote应用在编辑器里模拟触摸这对于调试移动端手势至关重要我强烈建议在开发阶段一直开启。Enable twist和Enable pinch是控制是否启用旋转和捏合手势识别的总开关如果你的游戏只需要点击和拖拽可以关闭它们以节省一点点性能。Enable 2 fingers gesture这个选项需要注意它决定了双指手势如缩放、旋转是否需要在两根手指都触摸屏幕后才触发。如果关闭即使用户先放下一根手指只要另一根还在屏幕上手势也会持续计算这能带来更流畅的连续操作体验但逻辑上会稍微复杂一点。2. 手势识别设置Gestures这是配置手势识别灵敏度的核心区域。Long tap time定义了多长时间按住算“长按”Min swipe distance决定了手指滑动多长的距离才会被判定为“滑动手势”而非误触。这里的参数需要根据你的项目类型来调整。比如一个快节奏的射击游戏Min swipe distance可以设得小一些让玩家能更快地做出闪避反应而一个策略游戏为了防止误操作这个值可以设得大一些。Pinch precision和Twist precision分别控制缩放和旋转手势的识别精度值越小越敏感。我的经验是不要使用默认值一定要在真机上反复测试找到最适合你应用场景的“手感”。2.2 三大监听器将输入转化为游戏逻辑Easy Touch 5提供了三种主要的监听器组件它们负责将EasyTouch管理器识别出的原始手势转化为具体的游戏事件。1. Quick Touch Quick Swipe这是最简单直接的监听器用于处理基础的点击、按下、抬起和滑动事件。你把它挂到任何一个UI或3D物体上它就能响应对该物体的触摸。它的工作原理是通过射线检测Raycasting来判断触摸点是否落在目标物体的碰撞体或RectTransform上。这里有个常见的坑对于UI元素确保它的Canvas渲染模式和射线投射Raycast Target设置正确对于3D物体确保它有碰撞体Collider。Quick Swipe是Quick Touch的扩展专门用于处理滑动手势你可以定义向上、下、左、右四个方向的滑动阈值。2. Quick Drag拖拽监听器用于实现物体的跟随手指移动。它内部处理了拖拽开始、进行中和结束的全生命周期事件。一个关键属性是Is restrict to area如果勾选你可以限制物体只能在一个特定的2D矩形区域内或被一个3D碰撞体范围内拖拽这对于制作拼图、滑块UI等功能非常有用。在拖拽3D物体时记得在On_Drag事件中将获取到的屏幕位置gesture.position通过Camera.ScreenToWorldPoint或射线检测转换为世界坐标。3. Quick Pinch Quick Twist这两个是高级手势监听器分别处理双指捏合缩放和双指旋转。它们是全局监听器通常不挂载在特定物体上而是放在一个全局管理器物体上。Quick Pinch会在每帧计算两指间距的变化并输出一个float类型的缩放增量pinch.deltaPinch。Quick Twist则计算两指连线的角度变化输出一个旋转增量twist.angle。使用它们的关键在于在你的回调函数中将这些增量值应用到目标物体的Transform上。例如void On_Pinch(Gesture gesture) { float scaleFactor gesture.deltaPinch * pinchSensitivity; // pinchSensitivity是一个调节系数 targetTransform.localScale Vector3.one * scaleFactor; }2.3 事件系统通信的桥梁所有监听器都通过C#事件C# Events或UnityEvent来触发回调。我强烈推荐使用C#事件的方式因为它在性能和解耦方面更有优势。在代码中你需要订阅Subscribe特定的事件void OnEnable() { EasyTouch.On_TouchStart MyTouchStartMethod; QuickPinch.On_Pinch MyPinchMethod; } void OnDisable() { EasyTouch.On_TouchStart - MyTouchStartMethod; // 务必取消订阅防止内存泄漏 QuickPinch.On_Pinch - MyPinchMethod; }传递给回调方法的Gesture对象是一个信息宝库它包含了当前所有触摸点的位置gesture.positiongesture.touchCount、手势开始的屏幕位置gesture.startPosition、滑动向量gesture.swipe、甚至是被触摸的物体gesture.pickedObject。熟练掌握从Gesture对象中提取信息是编写复杂交互逻辑的基础。3. 实战应用从零构建一个可操控的3D模型查看器理论讲得再多不如动手做一遍。我们用一个常见的需求——在手机屏幕上通过触摸来旋转、缩放、平移查看一个3D模型——来串联Easy Touch 5的大部分核心功能。这个例子涵盖了单指拖拽旋转、双指缩放和双指平移在Easy Touch中可通过拖拽实现非常具有代表性。3.1 场景与基础设置首先在Unity中创建一个新场景导入你的3D模型比如一个角色或一辆车把它放在场景中央。然后我们需要设置输入从Asset Store导入Easy Touch 5.0.8并将其添加到项目中。在场景中创建一个空GameObject命名为“TouchManager”。给“TouchManager”添加EasyTouch组件。在Inspector中确保勾选了Enable Remote、Enable Pinch和Enable Twist。根据你的模型大小可以适当调整Pinch precision和Twist precision比如都设为0.1。继续在“TouchManager”上添加Quick Pinch和Quick Twist组件。这两个组件我们用来接收全局的手势事件。再创建一个空GameObject作为模型的容器比如叫“ModelContainer”将你的3D模型作为它的子对象。这样我们可以通过控制容器的Transform来整体操作模型而不影响模型自身的层级关系。3.2 核心控制脚本编写接下来我们创建一个名为ModelViewerController的C#脚本挂载到“ModelContainer”上。这个脚本将处理所有交互逻辑。using UnityEngine; using HedgehogTeam.EasyTouch; // 引入Easy Touch命名空间 public class ModelViewerController : MonoBehaviour { [Header(旋转控制)] public float rotationSpeed 0.5f; // 旋转灵敏度 private bool isRotating false; private Vector2 lastTouchPos; [Header(缩放控制)] public float zoomSpeed 0.01f; public float minScale 0.5f; public float maxScale 3.0f; [Header(平移控制)] public float dragSpeed 0.005f; private bool isDragging false; private Vector3 dragStartPos; void OnEnable() { // 订阅单指触摸开始事件用于旋转 EasyTouch.On_TouchStart OnTouchStartForRotate; EasyTouch.On_TouchUp OnTouchEnd; // 订阅拖拽事件用于平移 EasyTouch.On_DragStart OnDragStart; EasyTouch.On_Drag OnDrag; EasyTouch.On_DragEnd OnDragEnd; // 订阅捏合事件用于缩放 QuickPinch.On_Pinch OnPinch; // 订阅旋转事件本例中暂不使用但保留接口 // QuickTwist.On_Twist OnTwist; } void OnDisable() { // 务必取消订阅 EasyTouch.On_TouchStart - OnTouchStartForRotate; EasyTouch.On_TouchUp - OnTouchEnd; EasyTouch.On_DragStart - OnDragStart; EasyTouch.On_Drag - OnDrag; EasyTouch.On_DragEnd - OnDragEnd; QuickPinch.On_Pinch - OnPinch; // QuickTwist.On_Twist - OnTwist; } // 方法1单指触摸旋转 void OnTouchStartForRotate(Gesture gesture) { // 如果当前是双指手势或正在拖拽则不触发旋转 if (gesture.touchCount 1 || isDragging) return; isRotating true; lastTouchPos gesture.position; } void OnTouchEnd(Gesture gesture) { isRotating false; } void Update() { if (isRotating Input.touchCount 1) { // 计算当前帧与上一帧的触摸位移 Touch currentTouch Input.GetTouch(0); Vector2 deltaPos currentTouch.deltaPosition; // 将屏幕位移转换为模型的旋转角度 float rotateX deltaPos.y * rotationSpeed; float rotateY -deltaPos.x * rotationSpeed; // 注意方向通常水平移动对应Y轴旋转 // 应用旋转绕世界坐标的Y轴和物体自身的X轴 transform.Rotate(Vector3.up, rotateY, Space.World); transform.Rotate(Vector3.right, rotateX, Space.Self); lastTouchPos currentTouch.position; } } // 方法2双指捏合缩放 void OnPinch(Gesture gesture) { float pinchDelta gesture.deltaPinch * zoomSpeed; Vector3 newScale transform.localScale Vector3.one * pinchDelta; // 限制缩放范围 newScale Vector3.Max(Vector3.one * minScale, Vector3.Min(Vector3.one * maxScale, newScale)); transform.localScale newScale; } // 方法3双指拖拽平移这里用EasyTouch的Drag事件模拟 void OnDragStart(Gesture gesture) { // 只有双指触摸时才视为平移 if (gesture.touchCount 2) { isDragging true; dragStartPos transform.position; } } void OnDrag(Gesture gesture) { if (isDragging gesture.touchCount 2) { // 使用两指中心点的位移来计算平移 // EasyTouch的gesture.position在两指时是中心点 Vector3 screenDelta new Vector3(gesture.deltaPosition.x, gesture.deltaPosition.y, 0); // 将屏幕位移转换为世界位移。这里需要一个参考距离比如模型到摄像机的Z轴距离。 float distanceToCam Vector3.Distance(transform.position, Camera.main.transform.position); Vector3 worldDelta Camera.main.ScreenToWorldPoint(screenDelta new Vector3(Screen.width/2, Screen.height/2, distanceToCam)) - Camera.main.ScreenToWorldPoint(new Vector3(Screen.width/2, Screen.height/2, distanceToCam)); transform.position dragStartPos worldDelta * dragSpeed; } } void OnDragEnd(Gesture gesture) { isDragging false; } }这个脚本实现了三个核心交互单指拖拽旋转通过监听On_TouchStart和On_TouchUp结合Update中读取Input.touchCount和Touch.deltaPosition来实现平滑旋转。这里没有直接用EasyTouch的On_Swipe因为我们需要的是持续拖拽而非快速滑动。双指捏合缩放直接使用QuickPinch组件的On_Pinch事件获取deltaPinch增量并应用到模型的localScale上同时加了最大最小值的限制。双指拖拽平移我们巧妙地利用了EasyTouch的On_Drag事件。通过判断gesture.touchCount 2将双指在屏幕上的整体移动通过屏幕坐标到世界坐标的转换变为模型在XZ平面或根据摄像机角度调整的平移。注意平移部分的坐标转换是难点。上述代码是一种简化方法适用于摄像机正对模型的正交或透视视角。在更复杂的3D场景中你可能需要根据摄像机的朝向使用Camera.ScreenPointToRay结合射线与某个假想平面的交点来计算精确的世界位移。3.3 调试与手感调优脚本写完把“TouchManager”和“ModelContainer”都设置好运行项目。用Unity Remote连接手机或者在编辑器里用鼠标模拟触摸按住Alt键鼠标左键模拟单指AltCtrl鼠标左键模拟双指。你会发现第一次运行手感可能很奇怪旋转太快或太慢缩放不跟手平移方向错乱。这时候就需要回到EasyTouch组件和你的控制脚本里调整参数旋转不跟手调整脚本中的rotationSpeed值。通常设置在0.1到1.0之间微调。缩放太灵敏调整脚本中的zoomSpeed以及EasyTouch组件里的Pinch precision。Pinch precision调大如0.5会让缩放更“钝”需要更大的手指开合变化才触发。平移方向错误检查OnDrag方法中的坐标转换逻辑。一个更稳健的方法是在OnDragStart时记录下模型初始位置和从摄像机到初始触摸点的射线与某个世界平面如Y0的地面的交点。在OnDrag中计算当前射线与同一平面的新交点用这两个交点的差值作为移动向量。这能保证平移方向与摄像机视角解耦。调试的过程就是不断在真机上测试、调整参数、再测试的过程。记住在编辑器里感觉“差不多”的手感到真机上往往差很多真机测试必不可少。4. 高级技巧与性能优化实战当你的项目从原型走向成熟或者触摸交互变得复杂时就需要考虑更深层次的问题了。下面分享几个我从实际项目中总结出来的高级技巧和优化点。4.1 输入冲突的仲裁与管理在一个复杂的UI界面中你可能同时有需要拖拽的3D物体、可滑动的页面、可点击的按钮。如何避免“我想旋转模型结果却滑动了下方的列表”这种输入冲突1. 使用手势阶段Gesture Phase和触摸点信息EasyTouch的Gesture对象提供了phase属性表示手势所处的阶段StartUpdateEnd。在复杂的交互逻辑中你可以在Start阶段就进行仲裁。例如在你的模型旋转脚本的OnTouchStartForRotate方法开头可以加入判断void OnTouchStartForRotate(Gesture gesture) { // 如果触摸起始点落在了UI元素上则本次触摸不用于旋转模型 if (gesture.isOverGui gesture.pickedObject ! null gesture.pickedObject.layer LayerMask.NameToLayer(UI)) { return; } // ... 原有的旋转开始逻辑 }这里gesture.isOverGui和gesture.pickedObject是关键它们能帮你判断触摸的初始目标。2. 优先级与状态机为不同的交互模块设置优先级。例如定义一个全局的输入状态枚举public enum InputState { Free, RotatingModel, DraggingUI, InMenu } public static InputState currentInputState InputState.Free;在每个交互事件开始时检查currentInputState。如果状态不是Free且不等于自身应处理的状态则直接return。在事件结束时将状态重置为Free。这种方法对于回合制游戏或带有明确模式切换的应用非常有效。3. 利用Unity的EventSystem对于UI元素最规范的做法是让它们通过Unity自带的EventSystem如EventTrigger组件来处理输入。EasyTouch可以与EventSystem共存。确保你的UI元素勾选了Raycast Target并且Canvas的Graphic Raycaster正常工作。EasyTouch的QuickTouch组件在判断是否触摸到UI时内部也是通过射线检测实现的与EventSystem的机制类似。保持UI输入处理在EventSystem体系内能减少很多不必要的冲突。4.2 性能敏感场景下的优化策略EasyTouch本身开销不大但在低端移动设备上或者每帧需要处理大量基于触摸的游戏逻辑时以下几点优化能带来可观的性能提升1. 减少不必要的射线检测QuickTouch、QuickSwipe等组件每帧都会对触摸点进行射线检测以判断是否触摸到了挂载该组件的物体。如果场景中有大量可交互但当前并不需要交互的物体这会产生不必要的开销。优化方案动态启用/禁用监听器组件。例如只有当玩家进入某个区域时才通过代码GetComponentQuickTouch().enabled true来激活该区域内物体的触摸监听。替代方案对于大量同类型的物体比如一堆可收集的硬币不要每个都挂QuickTouch。可以只在一个全局管理器上挂一个QuickTouch然后在它的On_TouchStart事件中通过物理OverlapSphere或网格查找算法来批量判断触摸点附近有哪些物体被“点击”了。2. 降低手势识别的事件频率EasyTouch组件在Advanced设置里有一个Gesture rate手势更新率参数。它定义了每秒计算手势如滑动向量、捏合距离的次数。默认是60次/秒即每帧都计算。对于一些对实时性要求不高的手势如长按确认你可以将这个值降低到30甚至15能减少一些计算量。3. 避免在事件回调中进行昂贵操作在On_Pinch、On_Drag等每帧触发的事件回调中尽量避免进行以下操作复杂的物理查询如Physics.RaycastAll。查找游戏对象GameObject.FindGetComponent尽量在Start或Awake中缓存引用。动态实例化/销毁对象InstantiateDestroy。 如果必须进行可以考虑使用协程Coroutine分帧处理或者将计算结果缓存起来复用。4. 针对WebGL平台的特别注意事项如果你要发布到WebGL比如微信小游戏需要特别注意。WebGL的输入系统是基于浏览器的与原生移动端有差异。EasyTouch 5.0.8对WebGL的支持基本是OK的但务必测试以下几点多点触控在Chrome和Safari浏览器上的表现可能不同。鼠标模拟在PC浏览器上用鼠标操作时右键和滚轮事件是否被正确映射为触摸手势。事件冒泡防止触摸事件意外触发浏览器默认行为如页面滚动。可以在初始化时尝试调用EasyTouch.SetEnableAutoSelect(true)并确保你的Canvas设置正确。一个常见的WebGL性能问题是如果页面失去焦点Update循环可能还在运行导致不必要的计算。可以在OnApplicationPause事件中禁用EasyTouch组件void OnApplicationPause(bool pauseStatus) { EasyTouch.instance.enabled !pauseStatus; }4.3 扩展实现自定义复杂手势虽然EasyTouch提供了几种基础手势但有时你需要更特别的比如三指上滑、画圈、双击等。EasyTouch的架构允许你在其基础上进行扩展。实现双击Double TapEasyTouch没有原生的双击事件但我们可以利用On_TouchStart和计时器轻松实现。private float lastTapTime; private float doubleTapThreshold 0.3f; // 双击时间间隔阈值 void OnEnable() { EasyTouch.On_TouchStart OnTouchStartForDoubleTap; } void OnTouchStartForDoubleTap(Gesture gesture) { if (gesture.touchCount 1) { float currentTime Time.time; if (currentTime - lastTapTime doubleTapThreshold) { // 双击成功 Debug.Log(Double Tap Detected!); // 重置时间防止连续三次触摸被误判为两次双击 lastTapTime 0; // 触发你的双击逻辑... } else { lastTapTime currentTime; } } }实现自定义笔势例如画“√”这需要更复杂的轨迹识别。一个简单的思路是在On_Drag或On_Swipe事件中持续记录触摸点的位置序列ListVector2。当手指抬起On_TouchUp时对这个点序列进行分析。分析算法可以包括计算整体移动方向、拐点检测、与预设模板的匹配度如使用动态时间规整DTW算法等。 虽然实现完整的笔势识别引擎工作量较大但对于识别固定方向的简单勾画通过判断点序列的大致角度变化就能做到。5. 常见问题排查与避坑指南即使按照文档操作在实际开发中你还是会遇到各种各样的问题。下面我整理了一份“踩坑实录”希望能帮你快速排雷。5.1 手势无响应或响应错乱这是最常见的一类问题通常由以下原因导致问题现象可能原因排查步骤与解决方案点击/触摸完全没反应1.EasyTouch组件未启用。2. 监听器组件如QuickTouch未挂载或未启用。3. 触摸对象缺少碰撞体Collider或UI元素未开启Raycast Target。4. 有其他UI面板覆盖且挡住了射线。1. 检查Hierarchy中EasyTouch实例的激活状态。2. 检查目标物体上的监听器组件是否勾选启用。3. 对于3D对象添加MeshCollider或BoxCollider对于UI检查Image或Text组件的Raycast Target。4. 检查Canvas层级确保可交互对象在最上层。可以使用EasyTouch的Show touches调试选项在Game视图显示触摸点看是否落在预期位置。双指缩放/旋转不生效1.EasyTouch组件中Enable Pinch或Enable Twist未勾选。2.QuickPinch或QuickTwist组件未添加或未启用。3. 脚本中未正确订阅事件On_Pinch/On_Twist。4. 在编辑器测试时未用正确方式模拟双指需AltCtrl鼠标左键。1. 确认EasyTouch管理器上的总开关已打开。2. 确认场景中存在已启用的QuickPinch/QuickTwist组件。3. 检查脚本的OnEnable中是否订阅了事件方法名是否正确并且OnDisable中取消订阅。4. 使用Unity Remote连接真机进行测试这是最可靠的方式。拖拽物体时“粘手”或跳动1. 坐标转换错误。将屏幕坐标直接赋值给了世界坐标。2. 拖拽逻辑每帧应用的是绝对位置而非增量。3. 物体的碰撞体形状或位置异常。1. 确保使用Camera.ScreenToWorldPoint并传入正确的Z值通常是物体到摄像机的距离。对于UI拖拽应使用RectTransform.anchoredPosition。2. 在On_Drag事件中使用gesture.deltaPosition本帧与上一帧的位移差值而非gesture.position当前绝对位置。3. 检查碰撞体是否完全包裹住物体拖拽时射线检测点是否稳定。5.2 与其他系统或插件的兼容性问题1. 与新版Unity Input System的冲突如果你的项目同时使用了新的Input System Package和EasyTouch可能会发生输入事件被处理两次的情况。因为两者都可能监听底层输入。解决方案是“二选一”方案A推荐在Player Settings的Active Input Handling中选择Input Manager (Old)。EasyTouch完全基于旧的Input Manager。方案B如果必须使用新的Input System则需要通过设置让EasyTouch失效或者自己编写一个适配层将Input System的输入事件转发成EasyTouch能识别的格式但这比较麻烦。2. 与NGUI、FairyGUI等第三方UI框架的兼容这些UI框架有自己的事件系统。通常只要确保EasyTouch的触摸射线能正确穿透到UI层即可。需要检查第三方UI的摄像机深度是否高于EasyTouch用于射线检测的摄像机通常是Main Camera。第三方UI的元素是否提供了正确的碰撞信息。有时需要在EasyTouch的EasyTouch组件中调整Raycast Layer Mask确保包含了UI所在的层。3. 在AR/VR项目中的应用在ARFoundation或VR项目中屏幕空间的概念可能发生变化。EasyTouch的输入是基于屏幕Screen Space的。在VR里你可能需要将手柄的射线交互转换为屏幕坐标。在AR里如果触摸是针对3D空间中的AR物体你需要确保用于射线检测的摄像机是正确的AR摄像机并且物体的碰撞体与真实的AR模型对齐。这可能需要对EasyTouch的工作流程进行较大改造有时直接使用AR/VR SDK提供的交互工具包会更高效。5.3 版本升级与项目迁移从EasyTouch 4升级到5或者在不同小版本间迁移可能会遇到API变更。5.0.8相对于更早的5.x版本通常更加稳定。备份升级前务必备份整个项目。查看日志导入新版本后首先查看Unity Console是否有编译错误或过时API警告Obsolete。逐步替换最常见的变更是事件回调方法的签名。例如某些版本中Gesture参数的类型或命名空间可能变了。根据错误提示逐个修改订阅事件的方法签名。测试核心功能升级后重点测试所有触摸交互功能特别是复杂手势和边界情况。最后关于“紫屏”或材质丢失的问题有时在热更新或AssetBundle打包后出现这通常不是EasyTouch直接导致的而是Unity资源管理或着色器变体收集的问题。但如果你发现只有在涉及EasyTouch的UI或物体上出现可以检查一下EasyTouch的预制体或示例场景中是否使用了特殊的Shader确保这些Shader被打包到了最终构建中。
返回列表