Community Examples
TL;DR: A survey of user-built WASM projects shared in CVR’s testing community. Each section lists what the project demonstrates, where to get it, and (where relevant) which API calls need updating for the current CCK (Preview.27-WASM.27). Most scripts authored before Preview.27-WASM.26 use the old property-style CVR APIs (Player.Username, Prop.RootTransform, …) and will not compile against the refactored method-style surface (GetUsername(), GetRootTransform(), …).
GB Emulator
Section titled “GB Emulator”- World ID:
w+acebfff3-ec94-4d05-bdc0-cd3e72b6eee4 - Source: https://github.com/NotAKidoS/chilloutvr-gameboy
- ROM folder:
%LOCALAPPDATA%/ChilloutVR/LocalStorage/acebfff3-ec94-4d05-bdc0-cd3e72b6eee4/ - Usage: Drop
.gbfiles into the folder above. PressInsertin-game to cycle to the next detected ROM.
A full Game Boy emulator running inside WASM scripting. Shows what’s possible when you push the runtime: full CPU emulation, PPU rendering, MBC3 mapper, joypad mapping.
Demonstrates:
- Raw ROM read via
FileStorage.GetFiles()+ file-content access (world-only +FileStorageReadRawFilespermission). - Continuous per-frame CPU emulation inside
Update(within the 20 ms epoch deadline). Texture2Doutput for the LCD display.System.Diagnostics.Stopwatchused for timing — strictly outside the sandbox’s forbidden-namespace list, butStopwatchis a pure timer and works in practice. The validator flagsSystem.Diagnosticson principle; treat that specific warning as a false positive for Stopwatch-only usage.
Status: likely works with the current CCK provided permissions are requested. The repository’s Assets/test_UdonProgramSources/ folder name is historical — the scripts were adapted from a prior Udon (VRChat) port.
Optime GBA
Section titled “Optime GBA”A port of the Optime GBA emulator. Ships with its own Proxies/ directory — manually-written WasmBehaviour, ExternallyVisibleAttribute, and type shims (Avatar, AvatarPoint, BufferReadWriter, FileStorage, LocalPlayer, Networking, Player, Prop, World) that predate the CCK’s own stubs.
Notes:
- The included
Proxies/WasmBehaviour.csuses runtime reflection to dispatchTriggerScriptEvent. This requiresCCKWasmProjectDescriptor.enableReflection = true, which enlarges the compiled.wasmsignificantly. - If you rebase onto the current CCK, delete
Proxies/and let the CCK’s source generator + stubs handle dispatch instead — the module gets smaller and you can leave reflection disabled. - Property-style API calls in the emulator code (e.g.
player.Username) will need to move to theGet*()method style.
Status: older snapshot; needs refactor against current CCK for clean builds.
Example Gimmicks World
Section titled “Example Gimmicks World”- World ID:
w+54dbf379-6456-4b8d-bfe4-a4c3a509a337 - Source package:
WasmExamplesWorld.unitypackage(requires ProBuilder + TextMeshPro + the latest WASM CCK).
Showcases:
- Player
join/leave/respawnevent logging. - Networked text chat.
- Prop destruction zone (lift + rotate + shrink animation then
Prop.Destroy()). - Player clone (mirror-style render of the local player).
- Mini world (scaled world-in-a-world with scale-correct camera).
- Simple logging tricks.
The package contains seven scripts. Six still work cleanly; two need small edits for the current CCK.
Scripts in the package
Section titled “Scripts in the package”| Script | Status | Notes |
|---|---|---|
CameraDepthHelper.cs | OK | Empty behaviour marker; nothing to break. |
NetworkManager.cs | Fix needed | Uses sender.Username — refactor to sender.GetUsername(). Also requires the world to hold AccessUserIdentity only for GetUserId(); GetUsername() no longer needs that permission as of Preview.27-WASM.27. |
PlayerClone.cs | OK | Already uses LocalPlayer.GetPosition() / GetRotation() / GetPlaySpaceScale(). |
PropDestroyer.cs | Fix needed | prop.RootTransform → prop.GetRootTransform(). |
SimpleLogger.cs | Mostly OK | Uses System.IO.Path.GetFileNameWithoutExtension(filePath) — pure string manipulation, not a filesystem call. Validator flags System.IO.Path as forbidden on principle; in practice this specific method works. Treat as a warning-level concern. |
SmallWorld.cs | OK | Already uses GetPlaySpaceScale(). |
TextWall.cs | Fix needed | Uses player.Username in four places — AddGlobalUserText, AddPlayerJoinedMessage, AddPlayerLeftMessage, AddPlayerRespawnedMessage. Each needs player.GetUsername(). |
Required edits for current CCK
Section titled “Required edits for current CCK”- SimpleLogger.Info($"Received message from {sender.Username}: {message.Length} bytes");+ SimpleLogger.Info($"Received message from {sender.GetUsername()}: {message.Length} bytes");- Transform rootTransform = prop.RootTransform;+ Transform rootTransform = prop.GetRootTransform();// TextWall.cs — apply to every Username reference- InternalAddMessage($"\n<color=#4ECDC4><b>{player.Username}</b></color> {text}");+ InternalAddMessage($"\n<color=#4ECDC4><b>{player.GetUsername()}</b></color> {text}");The rest of each script compiles against the current CCK unchanged.
Cross-reference against the sandbox validator
Section titled “Cross-reference against the sandbox validator”If you open Script Validator and paste an unmodified TextWall.cs, it reports:
`Player.Username` not found(4 hits). The closest-match hint isGetUsername.- Informational:
Game events detected: OnPlayerJoined, OnPlayerLeft, OnPlayerRespawned.
Running the patched version shows only the expected informational events.
Simple Whitelist
Section titled “Simple Whitelist”A compact pattern to gate a GameObject or a callback behind a list of user IDs. Useful for reward-only gear, admin overlays, or dev-only debug panels.
Original snippet (outdated)
Section titled “Original snippet (outdated)”As posted, the snippet uses the old property-style Player.UserId, which no longer compiles against the current CCK:
if (whitelist == _localPlayer.UserId) { ... }Player.UserId was refactored to GetUserId() in Preview.27-WASM.26. Since UserId still requires the AccessUserIdentity world permission (unlike GetUsername() which was released from the gate), the world has to request that permission first.
Fixed version (current CCK)
Section titled “Fixed version (current CCK)”using UnityEngine;using WasmScripting;using CVR;
[DefaultExecutionOrder(-100)]public partial class Whitelist : WasmBehaviour{ [SerializeField] private string[] _whitelist; [SerializeField] private GameObject _rewardObject;
public bool IsWhitelisted { get; private set; }
private Player _localPlayer; private bool _permissionRequested;
private void Start() { _localPlayer = LocalPlayer.PlayerObject;
// GetUserId() requires AccessUserIdentity. Request it once, then re-check on change. if (!WorldPermissions.CurrentPermissions.AccessUserIdentity && !_permissionRequested) { WorldPermissions.Request(new WorldPermissions { AccessUserIdentity = true }); _permissionRequested = true; return; }
WhitelistCheck(); }
public void OnWorldPermissionsChanged() { if (WorldPermissions.CurrentPermissions.AccessUserIdentity) WhitelistCheck(); }
private void WhitelistCheck() { if (_localPlayer == null) { Debug.LogError("Whitelist: _localPlayer null"); return; } if (_whitelist == null || _whitelist.Length == 0) return;
string userId = _localPlayer.GetUserId(); if (string.IsNullOrEmpty(userId)) return;
foreach (string entry in _whitelist) { if (entry == userId) { IsWhitelisted = true; RewardPlayer(); return; } } IsWhitelisted = false; }
private void RewardPlayer() { if (_rewardObject != null) _rewardObject.SetActive(true); }}Changes from the original:
UserId→GetUserId()(method form).- Added a one-shot
WorldPermissions.Request({ AccessUserIdentity = true })soGetUserId()returns a real value rather than redaction. - Added
OnWorldPermissionsChanged()to re-run the check once the user approves the prompt. If they deny, the whitelist silently resolves to “not whitelisted”. - Added explicit null / empty guards that short-circuit without spam-logging.
Consuming from another script
Section titled “Consuming from another script”public partial class AdminPanel : WasmBehaviour{ public Whitelist whitelist;
private void Start() { if (whitelist != null && whitelist.IsWhitelisted) ShowAdminUi(); }}Whitelist.IsWhitelisted caches the result after the initial check; no repeated binding calls per frame.
Permissions reminder
Section titled “Permissions reminder”Both the Gimmicks world’s NetworkManager / TextWall (when they use GetUsername()) and the Whitelist pattern (via GetUserId()) touch identity-gated APIs. Make sure the world requests the right flag:
GetUsername()— no permission needed as of Preview.27-WASM.27.GetUserId()— requiresAccessUserIdentity.
See World Permissions for the full request/approve flow.
Related
Section titled “Related”- Script Validator — paste any of the above scripts to see current-CCK errors.
- API Conventions — the properties →
Get*()refactor context. - CCK Changelog — when each API moved.
- Events — player / prop / network events referenced above.