Unity开发VR塔防游戏:从零到Pico 4真机部署 最近两个月 Unity 和 Pico 4 的热度一直没下来很多做塔防玩法、VR 展馆和游戏毕设的朋友都在问同一个问题Unity 做的 VR 项目到底怎么搬到 Pico 4 上跑这次我们直接选一个具体目标用 Unity 制作 VR Pico 4 项目《王国保卫战》第一季。这一季先把“VR 塔防 Demo”跑通不做概念验证不搞过度设计从创建工程、搭场景、写敌人波次、做防御塔攻击到最后打包 APK 装进 Pico 4 里实际看一眼整条链路完整走一遍。《王国保卫战》这类塔防玩法的 VR 化最大的价值不是复刻 PC 端的操作手感而是把“防御、部署、观察战场”变成一种空间交互体验。玩家可以在 VR 场景中移动视角、用手柄指向防御塔位置、看着敌人沿着道路进攻城堡。第一季的重点就是三个基础场景可运行、手柄交互可用、塔防循环能转起来。如果你以前只做过普通 2D/3D 游戏没有碰过 XR 项目这篇文章可以直接照步骤抄。先明确一点这不是零基础 Unity 教程。你需要先知道 Unity 编辑器怎么打开、C# 脚本怎么挂到物体上。如果这两个还不熟建议先花一天时间把 Unity 基础操作过一遍再回来做 VR 项目。下面所有内容基于 Unity 的 XR Interaction Toolkit 和 PICO 设备的标准 Android 构建流程不依赖某款付费插件核心逻辑全部自己写。1. 项目核心能力速览能力项说明项目类型Unity VR 塔防游戏开发目标设备Pico 4 及同类 PICO 一体机开发工具Unity XR Interaction Toolkit PICO 设备连接调试默认输入手柄摇杆移动、射线指向、扳机键交互玩法循环敌人沿路径推进 → 防御塔自动索敌攻击 → 玩家建造或升级防御塔可运行平台Android APKPICO 设备启动方式Unity Editor 预览 PICO 设备实机运行是否支持批量任务不涉及本文专注单机 VR Demo是否支持接口 API不涉及适合场景VR 开发入门、塔防玩法原型、PICO 设备应用开发、毕业设计显存要求取决于场景复杂度建议独立显卡至少 GTX 1060 同级或更高第一季不做成品游戏目标是一套可扩展的 VR 塔防框架。敌人、防御塔、玩家移动、手柄交互分离开第二季可以直接在这套框架上加英雄、技能、粒子特效、音效和手势识别。2. 教程规划与第一季目标《王国保卫战》本身有成熟的塔防规则敌人从固定生成点出发沿着路径走向城堡玩家在路径两侧建造防御塔防御塔自动攻击进入射程的敌人消灭敌人获得金币再用金币升级塔或者建造新塔。VR 版本不能照搬鼠标点击需要把“点击建造”改成“射线瞄准 扳机确认”。场景也不是固定屏幕而是玩家站在城堡旁通过手柄瞬移或平滑移动观察战场。这季具体完成下面几个模块一个包含地面、路径、城堡、敌人出生点的 VR 场景手柄控制玩家移动平滑移动 瞬移两种敌人沿路径点移动并被防御塔攻击一个简单的防御塔射线交互面板构建出可以在 Pico 4 上运行和安装的 APK。从开发顺序上看建议先完成场景搭建再做玩家移动然后写敌人和塔的逻辑最后完善交互和打包。这个顺序不是随便定的因为 VR 项目最怕一开始把交互做得很复杂结果连“场景稳定运行”都没做到。3. 环境准备与前置条件3.1 Unity 编辑器版本选择开发 VR 项目建议使用 Unity 2021.3 LTS 或更高版本。如果你想用 Unity 6 也可以但需要先确认你的 PICO 相关 SDK 和 XR Interaction Toolkit 版本是否兼容。更稳妥的做法是到 PICO 开发者平台下载对应版本的 SDK 说明再决定用哪个 Unity 版本。在 Unity Hub 里安装时Android Build Support 模块必须勾选否则后面无法导出 APK。这个模块包含 Android SDK、NDK 和 OpenJDKUnity Hub 会自动装一套默认配置。3.2 Pico 4 设备与开发者模式Pico 4 默认不允许安装安卓调试 APK需要先开启开发者模式。操作路径通常是设备设置 → 通用 → 关于本机 → 连续点击软件版本号直到出现开发者选项。然后在开发者选项里打开 USB 调试。之后用 USB 线连接电脑和 Pico 4在设备上选择“允许 USB 调试”。第一次连接时电脑上可能需要安装 ADB 驱动。如果你之前做过 Android 开发直接用 Android Studio 自带的 ADB 工具就行如果没做过也可以用 PICO 官方提供的 adb 工具包。连接成功后在命令行输入adb devices能看到设备序列号。这是最直接的验证方式能识别出设备和开发者模式是否正常。3.3 创建 Unity 项目并导入 XR 工具包打开 Unity Hub新建一个 3D 项目项目名称建议直接叫KingdomDefenseVR。创建之后打开 Package Manager安装下面两个包XR Interaction ToolkitUnity 官方的 XR 交互工具包XR Plugin ManagementXR 插件管理安装完成后在 Project Settings 里打开 XR Plug-in Management选择 Android 平台启用PICO或OpenXR后端。不同 PICO SDK 版本显示的选项不同但核心流程是一样的必须确保 Android 平台启用了一个 XR 插件并且当前构建目标为 Android。如果你使用的是 PICO 官方 SDK直接导入 PICO Unity Integration SDK 的 unitypackage。包里面自带预制体和示例场景可以作为最低成本的运行验证。不过本文为了保证通用性只使用 Unity 官方 XR Interaction Toolkit 作为交互层PICO SDK 只负责设备底层接入。4. 搭建 VR 基础场景4.1 创建 XR OriginXR Interaction Toolkit 的现代版本使用XR Origin作为玩家的根物体。它由 Camera Offset、Main Camera 和 Left/Right Controller 组成。在 Hierarchy 窗口点击右键选择 XR → XR Origin 创建即可。创建之后确认 Main Camera 的 Tag 为MainCamera并且在 Player Settings 的 XR 设置中启用了对应设备后端。如果没有这个根物体手柄模型和头显跟踪都无法正常工作。XR Origin创建后场景里会带两个手柄默认分别代表左右手控制器。手柄上已经挂好了射线交互器和传送提供器可以先用官方示例验证设备连接。4.2 配置手柄射线和交互给左右手柄的XR Ray Interactor加上Line Renderer可以直观看到射线。射线默认从手柄发射指向前方。碰到 UI 按钮、可抓取物体时射线末端会显示一个小的交互提示点。在XR Interaction Manager已经自动生成的情况下手柄射线就能和场景中的XR Simple Interactable、XR Grab Interactable、XR Ray Interactable交互。这部分不用写代码主要是做配置。如果你之前做过普通 Unity UI别直接把Button丢进去。VR 里的 UI 需要放在Canvas上并且把 Canvas 的渲染模式设置为World Space再把 Event Camera 指向 Main Camera。这样射线才能准确点中按钮。4.3 手柄移动平滑移动脚本VR 移动是第一个需要写代码的地方。这里先给一个基于手柄摇杆的平滑移动脚本。它通过读取左手柄摇杆的输入把摇杆方向转化为玩家相对头显方向的移动。using UnityEngine; using UnityEngine.XR; using UnityEngine.XR.Interaction.Toolkit; public class VRLocomotion : MonoBehaviour { public XRNode inputSource XRNode.LeftHand; public float moveSpeed 1.5f; public float gravity -9.81f; public CharacterController characterController; public Transform cameraRoot; private Vector2 inputAxis; private float verticalVelocity; void Update() { InputDevice device InputDevices.GetDeviceAtXRNode(inputSource); if (device.TryGetFeatureValue(CommonUsages.primary2DAxis, out inputAxis)) { Vector3 direction new Vector3(inputAxis.x, 0, inputAxis.y); Vector3 move cameraRoot.TransformDirection(direction) * moveSpeed * Time.deltaTime; move.y 0; characterController.Move(move); } if (characterController.isGrounded) { verticalVelocity 0f; } else { verticalVelocity gravity * Time.deltaTime; characterController.Move(new Vector3(0, verticalVelocity, 0) * Time.deltaTime); } } }把这个脚本挂到XR Origin上同时手动添加一个CharacterController组件。记得把cameraRoot指定为 Main CameracharacterController指定为同一个根物体上的 CharacterController。再说一句平滑移动在部分 VR 用户身上会产生眩晕。所以第一季建议同时保留瞬移功能。瞬移在 XR Interaction Toolkit 里可以直接通过Teleportation Provider和Teleportation Area实现不需要自己写。在场景地面创建一个Teleportation Area给它添加一个半透明的传送目标区域材质手柄射线瞄准后按扳机即可传送。5. 塔防核心玩法实现5.1 敌人路径与移动塔防玩法的核心是敌人从出生点走到城堡。先在地图上创建一条路径用一组空物体标记路径点。比如在 Hierarchy 里创建PathPoints下面放Point_0、Point_1、Point_2等空物体。敌人生成后依次经过这些点到达终点后扣城堡生命值。敌人移动脚本using UnityEngine; public class EnemyMovement : MonoBehaviour { public Transform[] waypoints; public float moveSpeed 2f; public int currentIndex 0; void Update() { if (waypoints null || waypoints.Length 0) return; Transform target waypoints[currentIndex]; Vector3 direction target.position - transform.position; direction.y 0; transform.Translate(direction.normalized * moveSpeed * Time.deltaTime, Space.World); if (Vector3.Distance(transform.position, target.position) 0.1f) { currentIndex; if (currentIndex waypoints.Length) { Destroy(gameObject); // 在这里调用 GameManager 扣生命值 } } } }这个脚本被敌人预制体挂载后敌人会自动沿路径点移动。真实塔防中的敌人移动会用 NavMesh 或路点插值第一季先用路点简单且容易验证。5.2 敌人波次生成器敌人生成需要有一个Spawner来控制波次。敌人不是一次性全部出现而是每隔一段时间生成一个。这里用协程实现using System.Collections; using UnityEngine; public class EnemySpawner : MonoBehaviour { public GameObject enemyPrefab; public Transform spawnPoint; public int totalWaves 3; public int enemiesPerWave 5; public float spawnInterval 1f; public float waveDelay 3f; void Start() { StartCoroutine(SpawnWaves()); } IEnumerator SpawnWaves() { for (int wave 1; wave totalWaves; wave) { for (int i 0; i enemiesPerWave; i) { GameObject enemy Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity); enemy.GetComponentEnemyMovement().waypoints spawnPoint.GetComponentPathHolder().pathPoints; yield return new WaitForSeconds(spawnInterval); } yield return new WaitForSeconds(waveDelay); } } }为了让waypoints配置更方便可以给出生点挂一个PathHolder脚本把路径点数组直接引用进去。这样生成器只需要读取出生点身上的路径点不用手写一大串 Find 逻辑。using UnityEngine; public class PathHolder : MonoBehaviour { public Transform[] pathPoints; }5.3 防御塔自动索敌防御塔放在路径两侧它自动检测射程内最近的敌人然后旋转炮口并开火。第一季不写子弹飞行直接给敌人造成伤害即可。using UnityEngine; public class TowerAttack : MonoBehaviour { public float range 5f; public float damage 10f; public float fireInterval 1f; public Transform turretHead; public LayerMask enemyLayerMask; public GameObject hitEffectPrefab; private float lastFireTime; private Collider[] results new Collider[16]; void Update() { int count Physics.OverlapSphereNonAlloc(transform.position, range, results, enemyLayerMask); Transform target null; float closestDist Mathf.Infinity; for (int i 0; i count; i) { if (results[i].TryGetComponentEnemyHealth(out var enemy)) { float dist Vector3.Distance(transform.position, results[i].transform.position); if (dist closestDist) { closestDist dist; target results[i].transform; } } } if (target ! null) { if (turretHead ! null) { Vector3 lookPos target.position; lookPos.y turretHead.position.y; turretHead.LookAt(lookPos); } if (Time.time - lastFireTime fireInterval) { Fire(); lastFireTime Time.time; } } } void Fire() { if (hitEffectPrefab ! null) { Instantiate(hitEffectPrefab, targetPosition, Quaternion.identity); } // 伤害逻辑写在 EnemyHealth.ReceiveDamage() 中 } }targetPosition需要从最近目标处取出来所以在Update里当target ! null时把target.position存到一个成员变量中。这里的代码做了简化实际使用时要把索敌、开火、弹道分离。比较好的做法是给防御塔加一个TowerData配置射程、伤害、攻击速度都从配置读取。这样后期做升塔和切换塔类型时不需要改脚本。5.4 敌人血量和受伤反馈敌人被攻击后减少血量血量归零就销毁。这个逻辑放在EnemyHealth脚本中using UnityEngine; public class EnemyHealth : MonoBehaviour { public float maxHealth 50f; private float currentHealth; void Start() { currentHealth maxHealth; } public void ReceiveDamage(float damage) { currentHealth - damage; if (currentHealth 0) { Destroy(gameObject); // 这里可以增加金币数量 } } }为了让玩家在 VR 中能直观看到敌人掉血可以在敌人头顶放一个 3D 血条或者用 Canvas 的 World Space 血条。第一季可以先不处理血条用颜色变化和销毁表现代替。6. 手柄交互与 VR UI 面板6.1 用射线点击 VR 按钮VR 里的 UI 按钮不能直接用鼠标点击。把 Canvas 设置为 World Space 后给手柄的XR Ray Interactor添加一个XR UI Input Module。这个模块会把手柄扳机输入转换成 UI 点击事件。具体操作是在 Canvas 的 EventSystem 对象上挂XR UI Input Module再把左右手柄的XR Ray Interactor作为模块的交互器引用。运行时手柄射线碰到 UI 按钮时按钮会变高亮按下扳机键就会触发Button.onClick。第一季做一个简单建造面板点击“建造弓箭塔”按钮在选择的位置生成一个防御塔。为了让按钮感应更准确可以把按钮的Target Graphic设置成一个显眼的图片并给按钮加一块较大的透明点击区域。6.2 抓取与投掷防御塔除了点击建造VR 塔防还有一个很自然的交互直接用手柄抓住防御塔放到指定位置。XR Interaction Toolkit 里已经内置了XR Grab Interactable。给防御塔预制体添加这个组件再根据需求调整Movement Type选择Instantaneous抓取时塔不会延迟跟随Attach Transform指定手柄抓住塔时的握持点Colliders确认防御塔有合适的碰撞体否则无法被射线选中。抓取的时候要限制不要抓取已经放置的塔或者抓取后战斗逻辑暂停。第一季可以先允许抓取所有防御塔但要注意塔在移动过程中如果还在索敌会出现炮口乱转。建议抓取时把TowerAttack禁用放下后再启用。6.3 在地面指定防御塔位置玩家用射线指向地面然后按扳机键就能在地面上生成防御塔。这个需求需要一个简单的PlacementControllerusing UnityEngine; using UnityEngine.XR.Interaction.Toolkit; public class PlacementController : MonoBehaviour { public XRNode rightHand XRNode.RightHand; public GameObject towerPrefab; public float maxPlacementDistance 10f; public LayerMask groundLayerMask; void Update() { InputDevice device InputDevices.GetDeviceAtXRNode(rightHand); bool triggerPressed false; device.TryGetFeatureValue(CommonUsages.triggerButton, out triggerPressed); if (triggerPressed) { TryPlaceTower(); } } void TryPlaceTower() { Ray ray new Ray(Camera.main.transform.position, Camera.main.transform.forward); if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayerMask)) { Instantiate(towerPrefab, hit.point, Quaternion.identity); } } }这种实现方式最快但射线是从头显方向发射的。更符合 VR 操作的方法是使用手柄射线作为射线起点这样玩家可以转头看别的方向同时用手柄操作指定塔位。7. 构建部署到 Pico 47.1 Player Settings 关键配置项目能运行后开始打包 APK。先在 File → Build Settings 里把平台切到 Android然后打开 Player Settings重点看一下几项Company Name和Product Name自己定义但建议不要用中文部分安卓设备安装会有兼容问题Package Name必须设置比如com.yourcompany.kingdomdefensevrMinimum API Level按 PICO SDK 的官方要求设置通常需要 Android 10 及以上Scripting Backend选择 IL2CPPARM64 架构Texture Compression选择 ASTC这是一体机最常见的纹理格式。设置完成后在 Build Settings 窗口点击 Build生成 APK 文件。首次构建会花几分钟因为要编译 IL2CPP 和打包资源。7.2 安装 APK 到 Pico 4命令行安装是最高效的方式。用 USB 连接 Pico 4确认adb devices能识别到设备后把生成的 APK 拖到命令中adb install -r KingdomDefenseVR.apk安装成功后会显示Success。然后在 Pico 4 的应用列表里找到这个 App打开即可。如果安装失败常见原因有两个一是设备没有开开发者模式和 USB 调试二是包名与已安装应用冲突。使用adb uninstall com.yourcompany.kingdomdefensevr卸载后再装一次即可。7.3 Editor 预览打包前建议先在 Unity Editor 里跑通。PICO 官方 SDK 通常提供一个设备预览功能可以在编辑器里直接看到手柄和头显数据不一定要每次打包验证。如果没有官方 SDK使用PICO XR插件时也可以通过XR Interaction Toolkit的 Device Simulator 在编辑器里模拟手柄操作。8. 性能优化与资源占用观察VR 项目和普通 3D 项目最大的不同在于帧率要求。普通手游 30 FPS 可以接受VR 一体机至少需要 72 FPS 起步尽量稳定在 90 FPS否则眩晕感会非常明显。所以第一季规模不大但优化习惯要早点建立。打开 Unity 的 Game 视图点击 Stats 面板能看到 FPS、Draw Call、SetPass Call 和三角面数。运行场景后注意观察这几个指标Draw Call 超过 200说明合批和材质管理有问题三角面数突然飙升通常是大规模敌人生成时没有做对象池内存占用持续上涨多半是重复 Instantiate 和 Destroy 导致。第一季建议直接做对象池。敌人和防御塔子弹都不能频繁实例化、销毁。一个最简单的做法是预先创建 20 个敌人对象先用 SetActive(false) 隐藏需要时再 SetActive(true)。这样能明显减少 GC Alloc 和性能抖动。如果你以后要加粒子特效、敌人血条、音效优化压力会更大。粒子特效容易造成内存泄露和性能问题尤其在一体机上。建议每个特效预制体都检查ParticleSystem的Stop Action、Max Particles和Emission Rate不要放一堆永不停止的特效。又因为 Pico 4 是高分辨率双眼渲染抗锯齿、阴影质量和实时光照都要克制。第一季场景可以关闭实时阴影使用烘焙光照贴图画面干净而且性能高。方向光阴影建议关闭或只保留近距离阴影。9. 常见问题与排查方法问题现象可能原因排查方式解决方案编辑器运行后屏幕黑色XR 插件未启用或没有 XR Origin检查 Player Settings 的 XR Plug-in Management启用 PICO/OpenXR 后端重建 XR OriginPico 4 连接电脑后adb devices无设备USB 调试未打开或驱动异常重新插拔 USB检查设备是否弹出授权框开启开发者模式和 USB 调试重装驱动APK 安装失败包名冲突或 API Level 过低查看adb install错误信息卸载旧包按 SDK 要求调高 API Level手柄射线点击不到 UICanvas 不是 World Space没有 UI Input Module检查 Canvas 渲染模式和 EventSystem设置 World Space指定 Event Camera挂 XR UI Input Module摇杆无法移动XR Input 读取不到设备在设备上确认手柄已连接检查设备输入模式确认启用 XR Toolkit敌人卡在路径点路径点 Y 轴高低不平或敌人卡在碰撞体调整路径点位置路径点全部放在同一地面高度敌人移动时忽略 Y 轴差值VR 里画面抖动帧率低于设备刷新率打开 Stats 观察 FPS降低阴影、粒子数量使用对象池防御塔不自动攻击LayerMask 没有包含敌人层检查 TowerAttack 的 enemyLayerMask给敌人加Enemy层并在 Inspector 指定 LayerMask抓取防御塔后炮口乱转抓取时 TowerAttack 未暂停检查抓取交互器的事件回调在OnSelectEntered禁用 TowerAttackOnSelectExited启用这些坑不是只在《王国保卫战》项目里出现凡是用 Unity 做 Pico 4 VR 项目都绕不开。如果启动后出现“找不到 XR 设备”之类的报错优先检查插件版本和 Unity 版本是否匹配这是最容易浪费时间的点。10. 最佳实践与后续扩展《王国保卫战》第一季到这一步已经是一个可以运行的 VR 塔防原型了。接下来的第二季、第三季可以在这个基础上继续做很多东西。但扩展之前先把工程结构整理清楚。建议把脚本按功能拆目录Assets/ Scripts/ Player/ Enemy/ Tower/ UI/ Spawner/ Prefabs/ Enemy/ Tower/ Scenes/ Materials/防御塔的数据不要写死在脚本里用ScriptableObject做配置。每一种塔定义一份数据文件包含名称、射程、伤害、攻速、建造花费。后续加新塔时只需新建一个数据资产不用改代码。敌人波次也可以改成配置驱动。用一个WaveConfig列表每个波次包含敌人类型、数量、生成间隔和奖励金币。这样第一季的游戏逻辑会变得更整洁也更接近商业项目。如果你准备用 Pico 4 做更复杂的交互下一个版本可以重点验证手势识别Pico 4 支持手势追踪可以把手势做成“在地图上画一道区域触发范围攻击”传送点限制在场景里放多个固定观察点玩家通过传送在观察点之间切换塔防策略信息敌人血条、塔射程提示、金币反馈都做成可选的 VR 面板音效和动画敌人走路动画、塔的攻击动画、命中反馈场景性能烘焙光照、GPU Instancing、对象池、禁用动态阴影。实现这些之前先把手上的第一季 Demo 完整跑一遍从空场景、手柄移动、敌人出生、防御塔射击到 APK 安装 Pico 4。这套链路一旦跑通后面所有功能都是往这棵树上加叶子难度会低非常多。如果你正好卡在某一步优先排查版本兼容再排查代码逻辑。Unity 的报错日志通常已经很明确Console窗口第一行红色信息基本就是答案。做 VR 项目不要急着堆功能先保证你能在设备上看到认认真真运行的第一季 Demo再谈第二季的野心。