15. 拿起来,丢出去
VRC Pickup 加在带 Collider 和 Rigidbody 的物体上,VRChat 才允许抓取。对准物体发起 Interact 可拿起;键位在 VRChat Settings → Controls。
这一章把第二部那颗 GlowBall 改造成一个能拿起来的小球:抓起来时灯加亮、放下时变暗、拿在手里按 Use(使用键,同在 Settings → Controls)时切颜色。
让物体可被抓取的三件套
Section titled “让物体可被抓取的三件套”VRChat 不需要写任何代码就能让一个物体可拿起。组合三个组件:
- Collider:决定玩家手能不能「碰」到它。Box / Sphere / Capsule 都行。
- Rigidbody:让它有物理表现。重力下坠、丢出去带初速度、撞到墙会反弹。
- VRC Pickup:告诉 VRChat「这个物体可以被抓」。
三件齐了,物体就具备被拿的资格。要在拿起、放下、按 Use 那一刻做点别的,再加一段 UdonSharp 监听对应事件。
第一步:从 GlowBall 复制一个 PickupBall
Section titled “第一步:从 GlowBall 复制一个 PickupBall”打开第二部那间小屋。
第二部把 GlowBall 做成了 Prefab。直接抓 Prefab 改不太合适,那会影响所有原始光球。第 8 章提过一个解法:
选中场景里的一个 GlowBall Prefab Instance,右键 Prefab → Unpack Prefab。它的名字从蓝色变回普通颜色,跟磁盘上那份模具断开了。改它不会影响其他实例。
把这个 Unpack 之后的物体重命名为 PickupBall,位置摆在桌面上方一点(没桌子就放半空中,比如 0, 1.2, 0)。它身上现在已经有 Sphere、Mesh Filter、Mesh Renderer、Sphere Collider、Light,缺 Rigidbody 和 VRC Pickup。
第二步:加 Rigidbody 和 VRC Pickup
Section titled “第二步:加 Rigidbody 和 VRC Pickup”选中 PickupBall:
Add Component→ 搜Rigidbody,加上。Use Gravity默认勾选。Add Component→ 搜VRC Pickup,加上。
加 VRC Pickup 时如果物体上还没 Rigidbody,VRChat 会主动补一个。提前手工加是为了让物理行为更可控,比如关掉重力做「漂浮的发光体」。
VRC Pickup Inspector 里几个常看的字段(左侧是 Inspector 显示名):
Interaction Text Hold # 鼠标对准时头顶提示Use Text Use # 拿在手里时显示的提示Pickupable True # 是否允许被抓(false 暂时锁住)Proximity 2 # 可被触及的最大距离(米)Orientation Any # 持握朝向:Any(不强制)/ Grip / GunExact Gun None # Orientation = Gun 时手心对齐这个 TransformExact Grip None # Orientation = Grip 时手心对齐这个 TransformAuto Hold Auto Detect # 松开抓取键后是否仍持握:Auto Detect / Yes / No # 只对 Desktop、Vive 这种没有独立 Use 输入的方案生效Allow Manipulation When Equipped False # VR 里允许玩家在手里旋转物体Disallow Theft False # 别人能不能从你手里抢走Orientation 和 Auto Hold 留默认值 Any 即可。下面单独说 Gun / Grip。
持握朝向:Any、Grip、Gun
Section titled “持握朝向:Any、Grip、Gun”Orientation 决定物体被抓进手里时怎么对齐玩家手部:
| 值 | 适用 | 行为 |
|---|---|---|
Any | 荧光棒、杯子、装饰球 | 不强制角度,抓起来就能用 |
Grip | 剑柄、工具握把 | 按 Exact Grip 锚点对齐「握持」姿势 |
Gun | 手枪、手电筒 | 按 Exact Gun 锚点对齐「枪口/灯头朝前」 |
要做 Grip 或 Gun:
- 在物体下新建空 GameObject,命名
GripAnchor或GunAnchor。 - 移动 / 旋转这个空物体,让它的轴向跟期望的握法一致(Scene 视图里切到物体本地坐标轴看)。
- Inspector 里
VRC Pickup→Orientation选Grip或Gun。 - 把锚点拖进
Exact Grip或Exact Gun槽。
Build & Test 里抓一次,不对就回 Unity 只改锚点旋转,不必改脚本。
Sphere Collider 这一章可能比 GlowBall 自带的稍小,建议把 Radius 调成 0.4,让玩家容易「对准」小球。第 13 章那条规则在这里也成立:抓不准多半是 Collider 太小。
第三步:先 Build & Test 一次
Section titled “第三步:先 Build & Test 一次”不写一行代码,先跑一次。Build & Test 进世界。
桌面玩家:走到 PickupBall 附近,光标对准,头顶提示 Hold。Interact 一次拿起,球贴在视角中心。再 Interact 一次放下,球受重力下坠。
VR 玩家:手伸到球上,用 Pickup 抓起,松手扔出去,球带着初速度飞出去再撞地。
不到半分钟,世界里多了一个能玩的物理小球。这就是为什么 Pickup 是 VRChat 世界最让人愿意试的交互之一:它不靠界面、不靠脚本,伸手抓就行,符合人在真实空间的直觉。
桌面下扔球的初速度跟鼠标视角晃动的快慢相关。VR 下扔球的初速度跟手的实际挥动速度相关。Rigidbody 物理表现完全交给 Unity 标准物理引擎,不需要写一行代码。
第四步:用三个事件让球更有个性
Section titled “第四步:用三个事件让球更有个性”光是能抓起来已经不错。再加点反馈:
- 拿起时灯亮一档。
- 放下时灯回原来亮度。
- 拿在手里按 Use 时切换颜色。
Assets/Scripts/ 下新建 PickupGlow.cs:
using UdonSharp;using UnityEngine;
public class PickupGlow : UdonSharpBehaviour{ public Light targetLight;
[Tooltip("待机和被抓起时的两档亮度")] public float restIntensity = 1.0f; public float heldIntensity = 3.5f;
[Tooltip("Use 键按下后会循环切换的几种颜色")] public Color[] colors;
private int colorIndex = 0;
private void Start() { if (targetLight != null) { targetLight.intensity = restIntensity; } }
public override void OnPickup() { if (targetLight != null) { targetLight.intensity = heldIntensity; } }
public override void OnDrop() { if (targetLight != null) { targetLight.intensity = restIntensity; } }
public override void OnPickupUseDown() { if (colors == null || colors.Length == 0) return; if (targetLight == null) return;
colorIndex = (colorIndex + 1) % colors.Length; targetLight.color = colors[colorIndex]; }}把它挂到 PickupBall 上,把 PickupBall 自身的 Light 组件拖到 Target Light 槽里。
Colors 数组需要手工填几条。Inspector 里点开 Colors → Size 改成 3 → 三条颜色分别填:
Element 0:橙黄(#FFB347)Element 1:青蓝(#6EE7F9)Element 2:粉紫(#F472B6)
存盘,重新 Build & Test。
抓起来:球更亮了。
放下:球回原色亮度。
拿在手里按 Use:球颜色循环切换。
四个事件,每一个 VRChat 都给了名字:
OnPickup 被拿起的瞬间,调一次OnDrop 被放下的瞬间,调一次OnPickupUseDown 拿在手里按下 Use 的瞬间,调一次OnPickupUseUp 拿在手里松开 Use 的瞬间,调一次跟第 13 章 Interact 一样,这些事件方法都得写 public override。漏 override 是不会报错但行为消失的沉默错误。
处理掉到地下的烦恼
Section titled “处理掉到地下的烦恼”Rigidbody 加上重力之后,球扔出去多半会滚到玩家够不着的地方,甚至直接掉出地图。两条常用的解法:
方案 A:监听物体的 Y 坐标,掉到一定高度自动归位。
public Transform respawnPoint;
private Vector3 spawnPos;private Quaternion spawnRot;
private void Start(){ spawnPos = transform.position; spawnRot = transform.rotation;}
private void Update(){ if (transform.position.y < -10f) { transform.position = spawnPos; transform.rotation = spawnRot; var rb = GetComponent<Rigidbody>(); if (rb != null) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } }}简单粗暴,单人世界够用。
方案 B:给物体挂 VRC Object Sync,用它自带的 Respawn 字段。
VRC Object Sync 这一章先不当主角,原因是它真正发挥作用要等多人同步聊清楚。但它有一个副作用刚好能用:勾选 Allow Collision Ownership Transfer 和 Use Gravity 之后,它会自动维护一个出生位姿,物体掉到一定高度(默认很低)会被传回生成位置。
挂法:给 PickupBall Add Component → 搜 VRC Object Sync → 加上。其它字段保持默认。
这一章里这两个方案都先选一个就行,第六部《和别人一起》会专门讲 VRC Object Sync 的网络同步行为,到时候回头看会更通透。
给小球做一份 Prefab
Section titled “给小球做一份 Prefab”第 8 章总结过的判断规则在这里又应了:要做第二个、第三个 Pickup(颜色不同的小球、形状不同的方块),从头配三遍是浪费时间。
把 PickupBall 拖进 Assets/Prefabs/,做成 Prefab。下次想加一个绿色版,从 Prefabs 拖一份出来,改 Albedo 和 Light 颜色就完事。
VRChat 大多数玩具世界的根基就是这套:一个被反复打磨好的「可拿起物体」 Prefab,再批量做颜色和形状变体。基础物理逻辑和事件接线只调一次。
自己改三个版本
Section titled “自己改三个版本”把 PickupGlow 当骨架,加一点:
- 在
OnPickup里同时播放一个Click音效。提示:再加一个public AudioSource clickSound字段,里面调Play()。 - 让球被丢出去时(
OnDrop)同时给Rigidbody加一个向上的力,模拟「自带反弹」。提示:Rigidbody 有一个方法叫AddForce,第 11 章用过类似的「取一个引用 + 调一个方法」的模式。 - 把循环颜色数组改成两组:白天组(暖色三色)、夜晚组(冷色三色)。再加一个布尔字段控制走哪一组。
第三个会让你顺便练一遍「用 if 选不同数组」。如果脑子里第一反应是「再写一个脚本」,那是把简单事写复杂的征兆,能用变量解决就别加脚本。
一段抓取交互的骨架
Section titled “一段抓取交互的骨架”回头看这一章的结构,跟第 13、14 章惊人地相似:
- 一个 GameObject + 一个 Collider(非 Trigger) + 一个 Rigidbody + 一个 VRC Pickup,决定「能抓」。
- 一段 UdonSharp,重写
OnPickup/OnDrop/OnPickupUseDown,决定「被抓时干什么」。 - 通过
public字段引用到的目标对象(这一章是 Light 和颜色数组)。 - 一组具体动作:改
Light.intensity、改Light.color、Play()音效。
主动 Interact → 被动 OnPlayerTriggerEnter → 抓握 OnPickup:第四部到现在已经走过三种触发方式,剩下「坐到上面 / 看到镜子里 / 走过一道传送门」三种常见配件,第 16 章一次拿下。