============================================================ KatzuLabs Networking ============================================================ KatzuLabs Networking KatzuLabs Networking is a Unity multiplayer system built on EOS. This documentation is written for real production use, not just demo scenes. What this guide focuses on How to get stable auth and room join flow on both PC and Quest How to keep player sync, voice, and ownership clean over time How to debug fast using KatzuConsole and runtime counters How to ship to multiple stores with the right provider setup Read order if you are new Getting Started Authentication Sessions and Rooms Object Sync (KatzuView) Voice Chat Troubleshooting Support links Official KatzuLabs Networking Page networking@katzulabs.com Big picture Player boots game -> Auth provider login (KatzuAuth) -> EOS connected state (KatzuSessionManager) -> Create or join session -> P2P peer links (KatzuNetworkManager) -> Object state sync (KatzuView) -> Voice stream path (KatzuVoiceManager/KatzuSpeaker) -> Host migration if host is lost ============================================================ Credits and Use Rights ============================================================ Credits and Use Rights KatzuLabs Networking is a free tool that anyone can use without the need of credits, this is free, do whatever you want with it, we do not care what you do with these scripts or information as long as you don't complain at us because of an error you committed. You can change the script names, the images, the everything, even claim it as yours. This tool was made to help other people have an easier time when making a networking system for VR, you have full authorization to change anything in here, we are not responsible for the things you do with the scripts, if you sell it, put malware so it does anything to people projects, or you get in any trouble, it is all your fault, we are not responsible. Anyways have fun with it <3 ============================================================ Architecture Overview ============================================================ Architecture Overview Understanding the runtime order will save you days of debugging. Core runtime classes and role ClassRoleWhen it is used most KatzuAuthProvider login + EOS ConnectStartup and re-auth KatzuSessionManagerRoom state and player stateCreate/join/leave and profile sync KatzuNetworkManagerP2P send/receive and packet routingEvery frame during session KatzuViewObject-level network syncPlayers, props, interactables KatzuVoiceManagerMic, encode/decode, voice routingWhile recording is active KatzuHostMigrationHost timeout and handoverHost disconnect scenarios Practical rules Never run join/create actions before auth is truly complete. Never assume ownership if the network does not confirm it. Keep one source of truth for room state: KatzuSessionManager. Where most bugs happen Startup race conditions (auth vs session calls) Missing KatzuView on runtime-spawned prefabs Repeated join/create button spam from UI Voice permission and mic device mismatch ============================================================ Feature Map ============================================================ Feature Map Use this map to quickly find where each feature is configured. Map FeatureMain classSecondary classes Provider authKatzuAuthMetaPlatformInitGate, KatzuPermissionManager Public/private roomsKatzuSessionManagerKatzuSessionJoinHelper Player profile syncKatzuSessionManagerKatzuPlayer, KatzuPlayerSpawner Object ownershipKatzuViewKatzuNetworkManager Voice capture/playbackKatzuVoiceManagerKatzuSpeaker MigrationKatzuHostMigrationKatzuSessionManager Editor diagnosticsKatzuNetworkingConsoleWindowKatzuConsole Why this matters When something breaks, go to the right class first. Randomly editing unrelated classes causes regressions. ============================================================ Getting Started ============================================================ Getting Started This section is for teams who want a clean first setup and predictable first test result. Outcome you should have at the end Two clients can create/join the same room Both clients can see player movement Both clients can hear voice chat No high-severity errors in KatzuConsole diagnostics Do these pages in order Install Checklist Scene Bootstrap First Multiplayer Test Default Values That Work ============================================================ Install Checklist ============================================================ Install Checklist Use this quick checklist before writing any custom network code. Required runtime components (one each) ComponentWhy KatzuAuthProvider login and EOS connect identity KatzuSessionManagerRoom lifecycle and player data sync KatzuNetworkManagerP2P packet routing KatzuVoiceManagerVoice capture/transmit/playback KatzuHostMigrationHost failover support Optional but strongly recommended KatzuNetworkingConsoleWindow (editor tool) Concentus (if you use compressed voice) If you skip this checklist, most later bugs are setup bugs, not code bugs. ============================================================ Scene Bootstrap ============================================================ Scene Bootstrap The goal is to avoid startup race conditions. You want auth first, then room actions. Recommended startup order Load scene and create manager objects Wait for auth completion Enable room action buttons Create or join room Start game logic after session state is ready Working gate script using System.Collections; using UnityEngine; using KatzuLabs.Networking; public class NetworkBootstrapGate : MonoBehaviour { public GameObject lobbyUiRoot; private IEnumerator Start() { if (lobbyUiRoot != null) lobbyUiRoot.SetActive(false); while (KatzuAuth.Instance == null) yield return null; while (!KatzuAuth.Instance.IsAuthComplete()) { if (KatzuAuth.Instance.HasAuthFailed()) { Debug.LogError("Auth failed: " + KatzuAuth.Instance.GetLastAuthError()); yield break; } yield return null; } if (lobbyUiRoot != null) lobbyUiRoot.SetActive(true); } } ============================================================ First Multiplayer Test ============================================================ First Multiplayer Test Run this exact test before you add gameplay complexity. Steps Client A creates public room Client B joins public room Check both see player count = 2 Move both players and verify remote movement Speak on both clients and verify audio in both directions Pass/fail sheet template Build: ______________________ Platform A: _________________ Platform B: _________________ Join pass: [ ] Yes [ ] No Movement sync pass: [ ] Yes [ ] No Voice A->B pass: [ ] Yes [ ] No Voice B->A pass: [ ] Yes [ ] No Notes: ___________________________________________ ============================================================ Default Values That Work ============================================================ Default Values That Work These values are safe starting points, not universal best values. SettingStart value KatzuView.sendRate20 KatzuVoiceManager.sampleRate16000 KatzuVoiceManager.voiceSendRate50 KatzuVoiceManager.opusBitrate32000 KatzuVoiceManager.reliableVoiceTransmissiontrue ============================================================ Authentication Overview ============================================================ Authentication Overview KatzuAuth is a multi-provider auth layer that ends in EOS Connect identity. Useful methods IsAuthComplete() HasAuthFailed() GetCurrentAuthStatus() GetLastAuthError() GetProductUserId() GetDisplayName() ============================================================ Provider Matrix ============================================================ Provider Matrix ProviderBest forNeeds store SDK? OculusMeta Quest buildsYes SteamSteam buildsYes OpenIDCustom backend identityNo itch.ioitch deploymentsDepends on setup DeviceIdCustom launcher/dev buildsNo ============================================================ Oculus (Quest) ============================================================ Oculus (Quest) This is the most common source of startup auth issues on Android builds. Required setup Install Oculus Platform SDK Set Meta Mobile App ID in Oculus platform settings Select provider = Oculus in KatzuAuth Run on real Quest hardware Quick status script using UnityEngine; using KatzuLabs.Networking; public class QuestAuthQuickCheck : MonoBehaviour { public void PrintAuthState() { if (KatzuAuth.Instance == null) return; Debug.Log("Status: " + KatzuAuth.Instance.GetCurrentAuthStatus()); Debug.Log("Failed: " + KatzuAuth.Instance.HasAuthFailed()); Debug.Log("Error: " + KatzuAuth.Instance.GetLastAuthError()); } } ============================================================ Steam ============================================================ Steam Steam auth should use official ticket flow when Steam SDK is available. Checklist Provider set to Steam Ticket type matches your expected flow Runtime display name is valid EOS user id is returned after login Bad practice to avoid Do not ship production with manual debug ticket fallback hardcoded. ============================================================ OpenID, itch.io, DeviceId ============================================================ OpenID, itch.io, DeviceId These providers are best for custom store and launcher scenarios. Simple decision rule No trusted token backend yet -> DeviceId Trusted backend token exists -> OpenID itch-specific flow -> itch provider settings DeviceId is usually the fastest stable path for early versions. ============================================================ Status UI and Retry ============================================================ Status UI and Retry Auth UI should show status steps, not just a spinner. using UnityEngine; using TMPro; using KatzuLabs.Networking; public class AuthStatusBinder : MonoBehaviour { public TMP_Text statusText; public TMP_Text errorText; void Update() { if (KatzuAuth.Instance == null) return; if (statusText != null) statusText.text = KatzuAuth.Instance.GetCurrentAuthStatus(); if (errorText != null) errorText.text = KatzuAuth.Instance.HasAuthFailed() ? KatzuAuth.Instance.GetLastAuthError() : ""; } public void Retry() { if (KatzuAuth.Instance != null) KatzuAuth.Instance.ManualRetry(); } } ============================================================ Auth Failure Playbook ============================================================ Auth Failure Playbook Collect first Provider selected Platform/build target Status text Last error text Then classify FailureCommon cause Init timeoutSDK or app ID config InvalidCredentialsStale/incorrect token or nonce InvalidParametersMissing required login fields ============================================================ Sessions and Rooms ============================================================ Sessions and Rooms KatzuSessionManager controls room lifecycle and session-level player state. Main actions CreatePublicSession() CreatePrivateSession(code) JoinPublic() JoinCode(code) LeaveSession() ============================================================ Public vs Private ============================================================ Public vs Private TypeMethodGood for PublicJoinPublicQuick play PrivateJoinCodeFriend lobbies Useful wrapper using UnityEngine; using KatzuLabs.Networking; public class SessionButtonsSimple : MonoBehaviour { public void CreatePublic() => KatzuSessionManager.Instance.CreatePublicSession(); public void JoinPublic() => KatzuSessionManager.Instance.JoinPublic(); public void JoinCode(string code) => KatzuSessionManager.Instance.JoinCode(code); public void Leave() => KatzuSessionManager.Instance.LeaveSession(); } ============================================================ Codes and Banned Words ============================================================ Codes and Banned Words KatzuSessionManager supports banned words for both names and room codes. Lists bannedSessions bannedNames Behavior Case-insensitive matching Name masking using # Session code blocking/trimming based on your logic ============================================================ Name, Color, Cosmetics ============================================================ Name, Color, Cosmetics This is session-level profile sync that affects what others see. using UnityEngine; using KatzuLabs.Networking; public class ProfileApplyPanel : MonoBehaviour { public string slotName = "Hat"; public string cosmeticName = "TopHat"; public void ApplyName(string value) { KatzuSessionManager.Instance.SetPlayerName(value); } public void ApplyColor(float r, float g, float b) { KatzuSessionManager.Instance.SetPlayerColor(new Color(r, g, b, 1f)); } public void Equip() { KatzuSessionManager.Instance.EquipCosmetic(slotName, cosmeticName); } } ============================================================ Mute, Ghost, Fully Hear ============================================================ Mute, Ghost, Fully Hear These are local-only filters. They change what you hear/see, not what everyone sees. FilterResult MuteYou cannot hear target player GhostYou hide target player's renderers Fully HearYou hear target player at max distance settings using Epic.OnlineServices; using UnityEngine; using KatzuLabs.Networking; public class LocalFilterController : MonoBehaviour { public void SetMute(ProductUserId userId, bool mute) { bool m, g, f; if (!KatzuSessionManager.Instance.TryGetLocalPlayerFilters(userId, out m, out g, out f)) { m = false; g = false; f = false; } KatzuSessionManager.Instance.SetLocalPlayerFilters(userId, mute, g, f); } } ============================================================ Session Helpers ============================================================ Session Helpers Helpers are for convenience UI integration. KatzuSessionJoinHelper Quick wrappers for create/join/leave Good for menu buttons KatzuSessionHelper Session member list and room attributes Optional log noise suppression with disableLogs ============================================================ Session Attributes ============================================================ Session Attributes Use attributes for lightweight room metadata like map, mode, round, or score target. using UnityEngine; using KatzuLabs.Networking; public class SessionAttributeExample : MonoBehaviour { public void SetRound(int round) { KatzuSessionHelper.Instance.SetSessionAttribute("round", round.ToString()); } public int GetRound() { string value = KatzuSessionHelper.Instance.GetSessionAttribute("round"); int parsed = 0; int.TryParse(value, out parsed); return parsed; } } ============================================================ KatzuView Overview ============================================================ KatzuView Overview KatzuView is the network sync component for object state and ownership. Fast setup Add KatzuView to object Auto assign observables Auto assign settings from observables Disable channels you do not use What it can sync Transform and player data Sound and rigidbody state Material/light/animation state ============================================================ KatzuView Settings Reference ============================================================ KatzuView Settings Reference This is the page you should give to any AI or developer before asking for object sync scripts. It explains how each inspector setting changes behavior. Important API TransferOwnership(ProductUserId) ForceTransferOwnership(ProductUserId) ForceTakeOwnership() RequestOwnershipTransferFromHost() RPC(string, ...) Core settings (what they do) SettingWhat it controlsRecommended default ObservablesWhich data channels this object can sendOnly enable what object needs Send RateHow often updates are sent15-30 for moving objects Reliable (per channel)Guaranteed delivery vs low-latency deliveryReliable for events, optional for transforms Use Distance InterestSkips updates for far clientsOn for non-critical objects Interest RadiusDistance threshold for interest filterTune per map size Always Sync With HostHost always receives updates regardless of distanceOn for authority-sensitive objects On Owner LeaveObject behavior when owner disconnectsMakeHostOwner for shared interactables Auto Assign ObservablesEditor helper to auto detect channelsUse once, then review manually Auto Assign SettingsEditor helper to tune reliability/rates from channelsUse as baseline only On Owner Leave mode reference ModeResultGood use case DoNothingObject remains with old stateDecorative/non-critical props DeleteObjectObject is removed for everyoneTemporary projectiles/effects MakeHostOwnerHost takes ownership and continues syncShared gadgets and gameplay items Minimal setup checklist for a networked object Add KatzuView to object root Add required observables only Pick ownership policy (who writes state) Set On Owner Leave mode based on gameplay Test with two clients and host migration If behavior looks random, it is almost always a settings mismatch (ownership/rate/reliability), not magic network issues. ============================================================ Observables ============================================================ Observables Observables define which data types a view is allowed to send. Object typeSuggested observable set Player rootTransform + PlayerData (+ Voice) Physics propRigidbody (+ Transform) Visual effect objectLight or Material Animated interactableAnimation (+ optional Sound) Less enabled channels means less traffic and lower update cost. ============================================================ Observable Types Deep Dive ============================================================ Observable Types Deep Dive Each observable is a specific payload shape. Enable only the channels that object truly needs. ObservablePayload ideaTypical useCommon mistake Transformposition/rotation/(optional scale)Players, moving propsUsing too high send rate for static objects Player Dataname/color/cosmetic stateAvatar profile syncTrying to send every frame instead of on change Soundplay/stop clip events and paramsSFX eventsUsing this as voice transport Rigidbodyvelocity/angular velocity/statePhysics objectsAllowing non-owner to write rigidbody state Materialmaterial index/color/float paramsSkin/theme changesSyncing full material assets repeatedly Animationstate/trigger/normalized timeDoors, gadgets, NPC animSpamming triggers every frame Lightenabled/intensity/colorSwitches and alarmsTreating light data as high-frequency stream Channel selection rule Continuous motion -> Transform and/or Rigidbody Event action -> RPC or Sound observable Visual style change -> Material observable Character profile -> Player Data observable Good AI instruction for observable setup Set up KatzuView for a pickup object: - Transform observable reliable=false sendRate=20 - Rigidbody observable reliable=false sendRate=20 - RPC for pickup/drop events reliable=true - OnOwnerLeave=MakeHostOwner - Distance interest on with radius tuned to room size ============================================================ Ownership ============================================================ Ownership Ownership decides who can write authoritative state for a view. Main methods TransferOwnership(newOwner) ForceTransferOwnership(newOwner) ForceTakeOwnership() RequestOwnershipTransferFromHost() using UnityEngine; using KatzuLabs.Networking; public class GrabRequestOwnership : MonoBehaviour { public KatzuView targetView; public void RequestControl() { if (targetView == null) return; targetView.RequestOwnershipTransferFromHost(); } } ============================================================ Reliability and Send Rates ============================================================ Reliability and Send Rates Most lag spikes are caused by wrong reliability choices or too many high-rate channels. Reliable vs unreliable ModeBest forAvoid for ReliableState changes and events (equip, open, grab start/end)High-frequency transform spam UnreliableContinuous motion updatesOne-time important events Send rate guide Object typeSuggested rate Player body/head/hands20-40 Physics prop in active use15-30 Door/button stateEvent-driven + occasional state refresh Cosmetic/profile dataOn-change only Bandwidth sanity check Count active KatzuView objects in scene Count channels enabled per object Multiply by send rate to estimate update pressure Lower rates on non-critical objects first Quick anti-stutter tips Do not use reliable for per-frame motion unless absolutely needed Keep interest filtering enabled for map-wide props Prefer event RPCs over continuous boolean re-sends ============================================================ On Owner Leave ============================================================ On Owner Leave Choose behavior for what happens to this object when owner disconnects. Modes DoNothing DeleteObject MakeHostOwner Recommendation Shared gameplay objects usually work best with MakeHostOwner. ============================================================ RPC ============================================================ RPC Use RPC for event-style actions, not continuous state spam. using UnityEngine; using KatzuLabs.Networking; public class NetworkedLightSwitch : MonoBehaviour { public KatzuView view; public Light targetLight; public void Toggle() { if (view == null || targetLight == null) return; bool next = !targetLight.enabled; targetLight.enabled = next; view.RPC("OnLightToggleRpc", next); } public void OnLightToggleRpc(bool enabled) { if (targetLight != null) targetLight.enabled = enabled; } } ============================================================ Sound Sync ============================================================ Sound Sync Sound sync is for gameplay SFX events. Voice chat should stay on the voice system, not KatzuView sound events. When to use sound observable Play one-shot world SFX (button click, alarm, explosion) Start/stop looped machine sounds Sync sound state to late joiners (if implemented by your script) When not to use it Microphone/voice chat data High-rate audio waveform streaming AudioSource settings note Keep your local AudioSource tuning (volume, spatial blend, min/max distance) as your game design needs. Network sync should trigger events, not overwrite your entire source profile every frame. using UnityEngine; using KatzuLabs.Networking; public class NetworkedSfxTrigger : MonoBehaviour { public KatzuView view; public AudioSource source; public AudioClip clickClip; public void TriggerClick() { if (source != null && clickClip != null) source.PlayOneShot(clickClip); if (view != null) view.RPC("OnRemoteClick"); } public void OnRemoteClick() { if (source != null && clickClip != null) source.PlayOneShot(clickClip); } } ============================================================ Rigidbody Sync ============================================================ Rigidbody Sync Rigidbody sync works best when one owner is authoritative and everyone else is read-only for that object. Authority rules Only owner writes physics state (velocity/position impulses) Non-owners never apply gameplay impulses to that rigidbody Transfer ownership before grab interaction starts Recommended setup Enable Transform + Rigidbody observables Use moderate send rate (15-30) Use MakeHostOwner on owner leave for shared props Clamp extreme velocities in gameplay code to reduce abuse Example owner-only throw using UnityEngine; using KatzuLabs.Networking; public class OwnerThrow : MonoBehaviour { public KatzuView view; public Rigidbody body; public float maxThrowSpeed = 15f; public void Throw(Vector3 velocity) { if (view == null || body == null) return; if (!view.IsMine()) return; Vector3 v = Vector3.ClampMagnitude(velocity, maxThrowSpeed); body.velocity = v; } } ============================================================ Material and Animation Sync ============================================================ Material and Animation Sync Use event/state sync for materials and animations. Avoid heavy per-frame payload updates for simple cosmetic changes. Material sync best practices Sync compact values (index, color, scalar), not full material assets Apply on change only Use a deterministic material list order across clients Animation sync best practices Sync state names/hashes and one-shot triggers Do not resend same trigger every frame For locomotion, sync compact parameters not full animator internals Example: synced skin + animation trigger using UnityEngine; using KatzuLabs.Networking; public class CosmeticAnimSync : MonoBehaviour { public KatzuView view; public Renderer targetRenderer; public Material[] materials; public Animator animator; public void SetSkin(int index) { if (targetRenderer == null || materials == null || index = materials.Length) return; targetRenderer.sharedMaterial = materials[index]; if (view != null) view.RPC("OnSkinChangedRpc", index); } public void PlayActivate() { if (animator != null) animator.SetTrigger("Activate"); if (view != null) view.RPC("OnActivateRpc"); } public void OnSkinChangedRpc(int index) { if (targetRenderer == null || materials == null || index = materials.Length) return; targetRenderer.sharedMaterial = materials[index]; } public void OnActivateRpc() { if (animator != null) animator.SetTrigger("Activate"); } } ============================================================ Exclusive Grab Lock Pattern ============================================================ Exclusive Grab Lock Pattern This pattern solves: one player can grab object, everyone else is blocked until release. Design rules Object has a single lockOwner value Only current lock owner can move/update the object Grab request must fail if lock already owned by another player Release clears lock and ownership can be transferred again State model Unlocked -> player requests grab -> ownership transfer accepted -> lockOwner = requesting player -> Locked -> player releases or timeout/disconnect -> lockOwner = none -> Unlocked Important API RequestOwnershipTransferFromHost() TransferOwnership(...) ForceTakeOwnership() (optional fallback) RPC(...) to replicate lock state UI Reference implementation using Epic.OnlineServices; using UnityEngine; using KatzuLabs.Networking; public class ExclusiveGrabLock : MonoBehaviour { public KatzuView view; public Transform followTarget; private ProductUserId _lockOwner; private bool _isLocked; public bool TryBeginGrab(ProductUserId requester, Transform hand) { if (_isLocked && !IsSame(_lockOwner, requester)) return false; if (view != null) view.RequestOwnershipTransferFromHost(); _lockOwner = requester; _isLocked = true; followTarget = hand; NotifyLockState(); return true; } public void EndGrab(ProductUserId requester) { if (!_isLocked || !IsSame(_lockOwner, requester)) return; _isLocked = false; _lockOwner = null; followTarget = null; NotifyLockState(); } private void Update() { if (!_isLocked || followTarget == null) return; if (view != null && !view.IsMine()) return; transform.position = followTarget.position; transform.rotation = followTarget.rotation; } private void NotifyLockState() { if (view != null) view.RPC("OnGrabLockStateRpc", _isLocked, _lockOwner != null ? _lockOwner.ToString() : ""); } public void OnGrabLockStateRpc(bool locked, string ownerId) { // UI hook point: show locked/unlocked and owner info. } private bool IsSame(ProductUserId a, ProductUserId b) { return a != null && b != null && a.ToString() == b.ToString(); } } ============================================================ Interest and Performance ============================================================ Interest and Performance Interest filtering reduces unnecessary updates for far objects. Useful settings useDistanceInterest interestRadius alwaysSyncWithHost Optimization process Start with minimal channels enabled Turn on interest for non-critical objects Measure packet queue and fps Tune send rates by object type ============================================================ Voice Chat ============================================================ Voice Chat Voice flow is mic capture -> encode -> send -> decode -> speaker playback. Main classes KatzuVoiceManager KatzuSpeaker KatzuNetworkManager Success check Sent counter increases while speaking Remote receive counter increases Remote played counter increases ============================================================ Voice Setup ============================================================ Voice Setup Required pieces One KatzuVoiceManager in scene Each remote player has KatzuSpeaker + AudioSource Mic permission flow works on target platform Control script using UnityEngine; using KatzuLabs.Networking; public class VoiceControlPanel : MonoBehaviour { public void StartMic() { if (KatzuVoiceManager.Instance != null) KatzuVoiceManager.Instance.StartRecording(); } public void StopMic() { if (KatzuVoiceManager.Instance != null) KatzuVoiceManager.Instance.StopRecording(); } } ============================================================ Quality Tuning ============================================================ Quality Tuning Tune one variable at a time and retest both directions. SettingStart value sampleRate16000 voiceSendRate50 opusBitrate32000 reliableVoiceTransmissiontrue maxPacketLossConcealmentFrames6 If voice cuts out Keep reliable voice on Watch dropped frame counter trend Reduce other traffic if packet queue spikes ============================================================ Device Selection ============================================================ Device Selection Always expose mic switch controls for PC users. using UnityEngine; using KatzuLabs.Networking; public class MicDeviceButtons : MonoBehaviour { public void NextMic() { if (KatzuVoiceManager.Instance != null) KatzuVoiceManager.Instance.SelectNextMicrophone(); } public void PrevMic() { if (KatzuVoiceManager.Instance != null) KatzuVoiceManager.Instance.SelectPreviousMicrophone(); } } ============================================================ Counters and Debug ============================================================ Counters and Debug CounterMeaning SentLocal captured and transmitted voice frames ReceivedFrames arrived from remote peer PlayedFrames decoded and sent to speaker DroppedFrames discarded Practical use If Sent rises but Received does not, investigate transport. If Received rises but Played does not, investigate speaker playback path. ============================================================ Common Voice Fixes ============================================================ Common Voice Fixes Cannot hear others Check remote player has KatzuSpeaker and AudioSource Check local mute/ghost/fully-hear filters Check played counter on receiver Audio stutters badly Enable reliable voice transmission Check packet queue pressure Tune send rate/bitrate with real network tests ============================================================ Templates Overview ============================================================ Templates Overview Templates are scene-level scripts for quick interactions (usually collider + tag based). What templates are good at Fast world-space button behavior Simple join/create flows Cosmetic/color and leaderboard interactions ============================================================ PublicToggle ============================================================ PublicToggle One-touch public room flow for VR/world-space interactions. Set interaction tag correctly Use cooldown to prevent duplicate triggers Show state feedback in text/material ============================================================ PrivateToggle ============================================================ PrivateToggle Private room join/create flow with code string handling. Main failure case is empty code input. Validate before calling join. ============================================================ ChangeQueue, Color, Cosmetic ============================================================ ChangeQueue, Color, Cosmetic ChangeQueue: set queue before create/join ChangeColor: update player color channels ChangeCosmetic: equip/take-off by slot/name Good UX: always display current value after each interaction. ============================================================ CodeComputerTemplate ============================================================ CodeComputerTemplate A keypad-style world object that builds join code text and runs join/leave actions. Checklist Assign backspace, enter, leave colliders Assign key collider + key string list Set interaction tag and cooldown Wire current lobby and join target text lists ============================================================ LeaderboardTemplate ============================================================ LeaderboardTemplate Binds room players to slot UI and supports mute/ghost toggles. Expected behavior Name text and color update per player Optional host suffix display Mute/Ghost toggles update local filters Cannot mute/ghost self ============================================================ SessionInfoDisplay ============================================================ SessionInfoDisplay Displays room state like code, players, queue, and region in text form. Keep this read-only and separate action buttons elsewhere. ============================================================ Katzu Console Overview ============================================================ Katzu Console Overview Open from Unity menu: KatzuLabs > KatzuLabsNetworking > KatzuConsole TabUse OverviewCurrent manager/state snapshot DiagnosticsAuto-fixable and help-linked issues LogsSeverity/category filtering PerformanceFPS, ping, packet, and voice counters ============================================================ Diagnostics Rules ============================================================ Diagnostics Rules Diagnostics stop common setup errors before runtime QA. High priority examples Missing core manager script Missing KatzuView on networked player object Missing required voice dependency for configured mode ============================================================ Log API ============================================================ Log API using UnityEngine; using KatzuLabs.Networking; public static class NetLog { public static void SessionInfo(string msg, Object ctx = null) => KatzuConsole.Info(msg, "Session", ctx); public static void VoiceWarn(string msg, Object ctx = null) => KatzuConsole.Warning(msg, "Voice", ctx); public static void AuthError(string msg, Object ctx = null) => KatzuConsole.Error(msg, "Auth", ctx); } ============================================================ Team Workflow ============================================================ Team Workflow Run diagnostics and fix high severity issues Run two-client join test Run movement sync test Run voice both directions test Save pass/fail notes with build hash ============================================================ Publishing Overview ============================================================ Publishing Overview Publishing is mostly about matching the auth provider to the store and validating real multiplayer behavior in release builds. Provider by storefront Store / ChannelRecommended providerFallback for internal testing Meta Quest storeOculusDeviceId SteamSteamDeviceId Custom launcherOpenID (if backend exists)DeviceId QA lab buildDeviceIdDeviceId Release checklist Provider-specific SDK is present in project Provider selected in KatzuAuth matches the build target Two-device auth/join/leave test passes Voice both directions passes Owner leave and host migration behavior passes No high severity diagnostics in KatzuConsole Important API KatzuAuth.IsAuthComplete() KatzuSessionManager.CreatePublicSession() KatzuSessionManager.JoinCode(string) KatzuSessionManager.LeaveSession() ============================================================ Meta Quest ============================================================ Meta Quest Quest builds usually fail from setup drift (App ID, permission flow, or wrong provider selected). Must-pass checklist Meta Mobile App ID configured in Oculus platform settings Provider set to Oculus in KatzuAuth Microphone permission appears and app recovers correctly after grant Quest can join PC room and PC can join Quest room After host quit, migration behavior is correct for your game Practical validation script using UnityEngine; using KatzuLabs.Networking; public class QuestReleaseCheck : MonoBehaviour { public void PrintStatus() { if (KatzuAuth.Instance == null) { Debug.LogError("KatzuAuth missing."); return; } Debug.Log("Auth Status: " + KatzuAuth.Instance.GetCurrentAuthStatus()); Debug.Log("Auth Complete: " + KatzuAuth.Instance.IsAuthComplete()); Debug.Log("Auth Error: " + KatzuAuth.Instance.GetLastAuthError()); if (KatzuSessionManager.Instance != null) Debug.Log("In Session: " + KatzuSessionManager.Instance.IsInSession()); } } If first boot hangs after mic prompt Delay voice auto-start until permission manager reports success Add a retry loop (10 attempts with delay) Show status text so user sees what step failed ============================================================ Steam ============================================================ Steam Steam release should use official Steam identity flow when the Steam SDK is installed. Must-pass checklist Steamworks SDK present and initialized in build Provider set to Steam in KatzuAuth Steam user and display name are visible in runtime auth debug Two-account room join test passes in non-editor build Voice, movement, and owner transfer tests pass Build guard example using UnityEngine; using KatzuLabs.Networking; public class SteamBuildGuard : MonoBehaviour { public void ValidateProviderForBuild() { #if UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX if (KatzuAuth.Instance == null) { Debug.LogError("KatzuAuth missing."); return; } Debug.Log("Current provider: " + KatzuAuth.Instance.GetProviderName()); #endif } } If your project does not include Steam SDK, keep Steam provider unselected to avoid package-missing noise. ============================================================ Custom Store / Launcher ============================================================ Custom Store / Launcher For non-store builds, use DeviceId first for speed, then move to OpenID when your token backend is stable. Recommended progression Phase 1: DeviceId in internal/alpha builds Phase 2: Add OpenID token endpoint Phase 3: Switch production channel to OpenID Phase 4: Keep DeviceId only for emergency fallback builds Important API KatzuAuth.ManualRetry() KatzuAuth.GetLastAuthError() KatzuSessionManager.JoinPublic() Do not mix permanent account logic with DeviceId. DeviceId is device-scoped identity, not account identity. ============================================================ Troubleshooting ============================================================ Troubleshooting Use this order. Skipping steps usually wastes time. Triage order Open KatzuConsole and clear high severity diagnostics Confirm auth status and last error text Confirm room state transitions (create/join/leave) Confirm player registration and KatzuView ownership Confirm voice counters (sent/received/played/dropped) Quick symptom map SymptomCheck first Join button does nothingAuth complete + busy lock Players in same room but not visibleKatzuView observables + spawn pipeline Voice "cuts words"send rate, jitter buffer, packet drops Objects freeze after owner leavesOnOwnerLeave policy ============================================================ Auth Problems ============================================================ Auth Problems Important API KatzuAuth.GetCurrentAuthStatus() KatzuAuth.HasAuthFailed() KatzuAuth.GetLastAuthError() KatzuAuth.ManualRetry() Failure matrix Error patternMost likely causeFix direction Init timeout on QuestMeta SDK/App ID mismatchValidate platform init + App ID InvalidParametersMissing required EOS login fieldsVerify credentials/user info payload InvalidCredentialsExpired token/nonceRefresh token/nonce before login retry SDK missing errorsSelected provider package absentInstall package or choose different provider Workflow Log selected provider and platform at startup Log each auth stage start/success/failure Retry only after updating token/nonce input ============================================================ Join Problems ============================================================ Join Problems Checklist Auth is complete before join action Only one join request runs at a time Room code is sanitized before request Banned-word rule is applied consistently Session full and join failure are surfaced to user Example: busy lock for join using UnityEngine; using KatzuLabs.Networking; public class JoinBusyGate : MonoBehaviour { private bool _busy; public void JoinPublic() { if (_busy) return; if (KatzuAuth.Instance == null || !KatzuAuth.Instance.IsAuthComplete()) return; _busy = true; KatzuSessionManager.Instance.JoinPublic(); Invoke(nameof(ClearBusy), 1.2f); } private void ClearBusy() => _busy = false; } ============================================================ Player Sync Problems ============================================================ Player Sync Problems If player prefab spawns but does not move Check player object has KatzuView Check transform observable is enabled Check ownership is local for local player object Check send/update loop is not disabled by feature toggles If remote prefab keeps delete/create looping Inspect duplicate register/unregister flow Ensure leave cleanup and spawn logic are not racing Check host migration state transitions Important API KatzuSessionManager.GetPlayerCount() KatzuView.TransferOwnership(...) KatzuView.RequestOwnershipTransferFromHost() ============================================================ Voice Problems ============================================================ Voice Problems Read counters in this order Counter patternMeaningAction Sent = 0No captureCheck mic permission/device/start-recording Sent rises, Received = 0No transport pathCheck routing and packet flow Received rises, Played = 0No playback pathCheck KatzuSpeaker + AudioSource route Dropped rises quicklyJitter/queue pressureTune send rate/buffer/reliability Quality checklist Use sample rate 16000 as stable baseline Keep reliable voice on if network is unstable Tune bitrate and packet loss concealment with real devices Verify users are not locally muted/ghosted unexpectedly ============================================================ Migration Problems ============================================================ Migration Problems Common failures SymptomLikely cause No migration triggeredHost heartbeat checker missing/disabled Migration loops foreverHeartbeat timeout too aggressive or reconnect spam Objects freeze after host leavesOnOwnerLeave mode not aligned with design Checklist after host quit test Session still exists and clients remain in room Authority-sensitive objects now owned by valid peer/host New joiners can still enter session Voice still works after migration event ============================================================ API Reference Overview ============================================================ API Reference Overview This reference documents the public runtime APIs in the KatzuLabs Networking scripts. Each API entry includes purpose, usage, and example calls. Scope Included: public runtime APIs from Auth, Lobby, Networking, Voice, Player, and Templates. Excluded: editor-only APIs and private/internal methods. How to read API pages Use the method table to find exact call Read parameter notes and side effects Copy the example and adapt variable names Test with two clients for networking logic ============================================================ KatzuAuth API ============================================================ KatzuAuth API Main auth entry point. Handles provider flow and EOS Connect state. Methods APIWhat it doesExample call ManualRetry()Restarts auth attempt flowKatzuAuth.Instance.ManualRetry(); IsAuthComplete()True when auth and EOS connect are readyif (KatzuAuth.Instance.IsAuthComplete()) { ... } HasAuthFailed()True when auth has hard-failedif (KatzuAuth.Instance.HasAuthFailed()) { ... } GetCurrentAuthStatus()Current status text for UI/debugstatusLabel.text = KatzuAuth.Instance.GetCurrentAuthStatus(); GetLastAuthError()Last error textDebug.LogError(KatzuAuth.Instance.GetLastAuthError()); GetProductUserId()Local EOS ProductUserIdvar puid = KatzuAuth.Instance.GetProductUserId(); GetOculusUsername()Resolved display name (legacy-compatible)nameText.text = KatzuAuth.Instance.GetOculusUsername(); GetDisplayName()Display name helperplayerName = KatzuAuth.Instance.GetDisplayName(); ============================================================ SessionManager Room API ============================================================ SessionManager Room API Room lifecycle: create, join, leave, queue/region setup, and join callbacks. Methods APIPurposeExample AuthenticateWithDeviceID()Starts device ID auth pathKatzuSessionManager.Instance.AuthenticateWithDeviceID(); OnAuthComplete(ProductUserId,string)Auth callback hooksession.OnAuthComplete(userId, userName); OnOculusAuthComplete(ProductUserId,string)Legacy Meta auth callbacksession.OnOculusAuthComplete(userId, userName); SearchAndJoinSession()Alias that joins public sessionssession.SearchAndJoinSession(); JoinPublic()Join open session in queue/regionsession.JoinPublic(); JoinCode(string)Join by custom/private codesession.JoinCode("ABC123"); JoinPrivateSession(string)Alias for code joinsession.JoinPrivateSession("MYROOM"); CreatePublicSession()Create public roomsession.CreatePublicSession(); CreatePrivateSession(string)Create room with codesession.CreatePrivateSession("TEAM42"); CreateSession()Alias for public createsession.CreateSession(); JoinPrivate(string)Alias for code joinsession.JoinPrivate("CODE"); SetQueue(string)Select queue/bucketsession.SetQueue("Ranked"); SetRegion(string)Set region hintsession.SetRegion("NA-East"); OnJoinedPublicSession(string)Internal/flow callback after public joinsession.OnJoinedPublicSession(name); OnJoinedPrivateSession(string)Internal/flow callback after code joinsession.OnJoinedPrivateSession(code); LeaveSession()Leave current room and cleanupsession.LeaveSession(); Moderation setting SettingPurposeExample kickingTypeControls who can kick players in this sessionsession.kickingType = KatzuSessionManager.KickingType.HostOnly; KickingType modes ModeBehavior HostOnlyOnly host can kick clients ClientsAskHostClients send a kick request to host; host auto-approves InstantAllowAny client can directly kick any client DisabledKicking is off for everyone ============================================================ SessionManager Player API ============================================================ SessionManager Player API Player profile and local filter APIs (name/color/cosmetics + mute/ghost/fully-hear). Methods APIPurposeExample SetPlayerName(string)Updates local player name and sync pathsession.SetPlayerName("Ava_X3"); SetPlayerColor(Color)Updates local player colorsession.SetPlayerColor(Color.green); EquipCosmetic(string,string)Enable cosmetic in slotsession.EquipCosmetic("Hat","TopHat"); TakeOffCosmetic(string,string)Disable specific cosmeticsession.TakeOffCosmetic("Hat","TopHat"); TakeOffSlotCosmetics(string)Clear slot cosmeticssession.TakeOffSlotCosmetics("Hat"); TriggerPlayerJoined(ProductUserId)Manual joined event triggersession.TriggerPlayerJoined(userId); TriggerPlayerLeft(ProductUserId)Manual left event triggersession.TriggerPlayerLeft(userId); UpdatePlayerNameInSession(ProductUserId,string)Force update known player namesession.UpdatePlayerNameInSession(userId,"Player2"); TryGetLocalPlayerFilters(ProductUserId,out bool,out bool,out bool)Get local mute/ghost/fully-hear statesession.TryGetLocalPlayerFilters(id,out m,out g,out f); SetLocalPlayerFilters(ProductUserId,bool,bool,bool)Set local-only hearing/visibility filterssession.SetLocalPlayerFilters(id,true,false,false); KickPlayer(ProductUserId)Attempts to kick target player using active kickingType rulessession.KickPlayer(targetId); KickPlayerById(string)Same as KickPlayer but with ProductUserId stringsession.KickPlayerById(targetIdString); RegisterRemotePlayerAsHost(ProductUserId)Host-side registration for remote playersession.RegisterRemotePlayerAsHost(id); UnregisterRemotePlayerAsHost(ProductUserId)Host-side unregistersession.UnregisterRemotePlayerAsHost(id); ============================================================ SessionManager State API ============================================================ SessionManager State API State queries, debug helpers, naming helpers, and migration acceptance methods. State/query methods APIPurposeExample IsConnected()Network connected stateif (s.IsConnected()) { ... } IsAuthenticated()Auth state in session managerbool ok = s.IsAuthenticated(); IsInSession()True when in active roomif (!s.IsInSession()) return; IsPublicSession()True when current room is publicmodeText.text = s.IsPublicSession() ? "Public" : "Private"; GetCurrentSessionName()Current EOS session namevar n = s.GetCurrentSessionName(); GetCurrentSessionCode()Current lobby codecodeLabel.text = s.GetCurrentSessionCode(); GetCurrentSessionBucket()Current queue bucketqueueLabel.text = s.GetCurrentSessionBucket(); GetCurrentSessionRegion()Current regionregionLabel.text = s.GetCurrentSessionRegion(); GetLocalUserId()Local ProductUserIdvar id = s.GetLocalUserId(); GetPlatformInterface()EOS platform interfacevar p = s.GetPlatformInterface(); GetSessionsInterface()EOS sessions interfacevar si = s.GetSessionsInterface(); IsSessionOwner()True if local client owns sessionif (s.IsSessionOwner()) { ... } GetSessionOwner()Current host ProductUserIdvar owner = s.GetSessionOwner(); GetKnownSessionPlayerIds()Known player id listvar ids = s.GetKnownSessionPlayerIds(); GetDisplayPlayerObjectName(ProductUserId,string)Display name for spawned object labelobj.name = s.GetDisplayPlayerObjectName(id); GetKnownPlayerName(ProductUserId,string)Known player name lookupvar name = s.GetKnownPlayerName(id,"Player"); GetDisplayPlayerNumber(ProductUserId)Stable display player indexint n = s.GetDisplayPlayerNumber(id); DebugListActiveSessions()Print active sessions for debugs.DebugListActiveSessions(); Migration/control methods APIPurposeExample ForceClearSessionState()Emergency local state resets.ForceClearSessionState(); AcceptMigrationAsNewHost(...)Apply host-side migration acceptances.AcceptMigrationAsNewHost(...); AcceptMigrationAsClient(...)Apply client-side migration acceptances.AcceptMigrationAsClient(...); ============================================================ SessionHelper API ============================================================ SessionHelper API Utility helper for room attributes and member info. APIPurposeExample RefreshSessionInfo()Refresh cached helper data from active roomKatzuSessionHelper.Instance.RefreshSessionInfo(); SetSessionAttribute(string,string)Write room metadata key/valuehelper.SetSessionAttribute("mode","Infection"); GetSessionAttribute(string)Read room metadata valuestring mode = helper.GetSessionAttribute("mode"); GetSessionMembers()Get known room member listvar members = helper.GetSessionMembers(); GetPlayerCount()Current room player countint c = helper.GetPlayerCount(); IsSessionFull()True if room hit max playersbool full = helper.IsSessionFull(); ============================================================ SessionJoinHelper API ============================================================ SessionJoinHelper API UI-friendly wrapper for join/create/leave actions. APIPurposeExample JoinPublic()Join a public sessionjoinHelper.JoinPublic(); JoinCode(string)Join by custom codejoinHelper.JoinCode("ABC123"); JoinPrivate(string)Alias for JoinCodejoinHelper.JoinPrivate("TEAM01"); LeaveCurrentSession()Leave active roomjoinHelper.LeaveCurrentSession(); QuickJoin()Convenience join flowjoinHelper.QuickJoin(); CreatePublic()Create public roomjoinHelper.CreatePublic(); CreatePrivate(string)Create private room codejoinHelper.CreatePrivate("CODE99"); IsInSession()Current room stateif (joinHelper.IsInSession()) ... GetSessionInfo()Formatted session summary stringlabel.text = joinHelper.GetSessionInfo(); ============================================================ HostMigration API ============================================================ HostMigration API Host migration control and status surface. APIPurposeExample NotifyGracefulLeave()Tell peers host is leaving intentionallymigration.NotifyGracefulLeave(); ReceiveMigrationMessage(ProductUserId,byte[])Handle migration payload from peermigration.ReceiveMigrationMessage(sender,data); IsHostAlive()Health/availability state for current hostbool alive = migration.IsHostAlive(); ============================================================ KatzuView Sync API ============================================================ KatzuView Sync API Object data synchronization APIs for transform/player profile/snapshot flow. APIPurposeExample AutoAssignObservables()Editor/runtime helper to auto-detect observablesview.AutoAssignObservables(); AutoAssignSettingsFromObservables()Auto tune defaults from enabled observablesview.AutoAssignSettingsFromObservables(); SyncTransform(Transform,Transform,Transform)Sync head + hands dataview.SyncTransform(head,left,right); SyncPlayerName(string)Sync player name fieldview.SyncPlayerName("Ava_X3"); SyncPlayerColor(Color)Sync player colorview.SyncPlayerColor(Color.cyan); SyncCosmetic(string,string,bool)Sync cosmetic on/off stateview.SyncCosmetic("Hat","Cap",true); ReceiveNetworkData(KatzuNetworkData,ProductUserId)Apply incoming payload for this viewview.ReceiveNetworkData(data,senderId); CanProvideSnapshotState()Can this view provide snapshot payloadif (view.CanProvideSnapshotState()) ... AppendSnapshotPackets(List<KatzuNetworkData>)Append snapshot packets for new peersview.AppendSnapshotPackets(packetList); Serialize()Serialize network payload databyte[] bytes = data.Serialize(); Deserialize(byte[])Deserialize payload bytes to data objectvar data = KatzuView.Deserialize(raw); ============================================================ KatzuView Ownership API ============================================================ KatzuView Ownership API Ownership transfer and RPC execution surface. APIPurposeExample RPC(string,params object[])Send networked method eventview.RPC("OnDoorOpenRpc", true); ReceiveRPC(string,object[])Apply incoming RPC eventview.ReceiveRPC(name,args); TransferOwnership(ProductUserId)Normal ownership transfer requestview.TransferOwnership(newOwnerId); ForceTransferOwnership(ProductUserId)Forced transfer pathview.ForceTransferOwnership(newOwnerId); ForceTakeOwnership()Force local ownership takeview.ForceTakeOwnership(); RequestOwnershipTransferFromHost()Ask host to transfer object ownershipview.RequestOwnershipTransferFromHost(); TakeOwnership()Local ownership claim helperview.TakeOwnership(); ConfigureOwnership(ProductUserId,bool)Set owner and local/mine stateview.ConfigureOwnership(ownerId, isMine); Find(int)Find view by idvar v = KatzuView.Find(viewId); FindByOwner(ProductUserId)Find first view by ownervar v = KatzuView.FindByOwner(userId); ============================================================ NetworkManager API ============================================================ NetworkManager API Low-level packet transport and peer state queries. Connection APIs APIPurposeExample ConnectToPeer(ProductUserId)Open connection to peernet.ConnectToPeer(peerId); CloseConnection(ProductUserId)Close peer connectionnet.CloseConnection(peerId); IsConnectedToPeer(ProductUserId)Peer connected?if (net.IsConnectedToPeer(peerId)) ... GetConnectedPeerCount()Connected peer countint c = net.GetConnectedPeerCount(); GetConnectedPeers()Connected peer listvar peers = net.GetConnectedPeers(); Send APIs APIPurposeExample SendNetworkData(KatzuNetworkData,bool)Broadcast payloadnet.SendNetworkData(data,false); SendNetworkData(KatzuNetworkData,bool,IList<ProductUserId>)Send to selected peersnet.SendNetworkData(data,true,targets); SendNetworkDataToPeer(ProductUserId,KatzuNetworkData,bool)Send to one peernet.SendNetworkDataToPeer(peer,data,true); SendVoiceBinary(byte[],bool)Broadcast voice payloadnet.SendVoiceBinary(payload,false); SendToAllPeers(byte[],byte,PacketReliability)Raw channel send all peersnet.SendToAllPeers(bytes,channel,reliability); SendToPeers(IList<ProductUserId>,byte[],byte,PacketReliability)Raw channel send target peersnet.SendToPeers(peers,bytes,channel,reliability); SendToPeer(ProductUserId,byte[],byte,PacketReliability)Raw channel send one peernet.SendToPeer(peer,bytes,channel,reliability); Metrics APIs APIPurposeExample GetEstimatedPingMs()Estimated ping valuefloat ms = net.GetEstimatedPingMs(); HasEstimatedPing()Ping estimate available?if (net.HasEstimatedPing()) ... GetQueuedPacketCount()Current queued incoming packetsint q = net.GetQueuedPacketCount(); GetBufferedVoiceSenderCount()Voice senders with buffered dataint b = net.GetBufferedVoiceSenderCount(); GetPendingPingCount()Pending ping probesint p = net.GetPendingPingCount(); ============================================================ VoiceManager API ============================================================ VoiceManager API Microphone capture, transmit, receive, speaker registration, and voice runtime counters. Recording and setup APIPurposeExample SetupQuestControls()Initialize Quest-specific voice controlsvm.SetupQuestControls(); OnMicrophonePermissionGranted()Notify voice manager permission is availablevm.OnMicrophonePermissionGranted(); StartRecording()Begin microphone capture/transmitvm.StartRecording(); StopRecording()Stop microphone capture/transmitvm.StopRecording(); Receive/register APIs APIPurposeExample ReceiveVoiceBinary(byte[],ProductUserId,KatzuSpeaker)Route incoming binary voice payloadvm.ReceiveVoiceBinary(data,sender,speaker); ReceiveVoiceData(byte[],ProductUserId)Handle incoming voice data pathvm.ReceiveVoiceData(data,sender); RegisterSpeaker(KatzuSpeaker)Add speaker to playback registryvm.RegisterSpeaker(speaker); UnregisterSpeaker(KatzuSpeaker)Remove speaker from registryvm.UnregisterSpeaker(speaker); Microphone selection APIs APIPurposeExample SetMicrophoneType(MicrophoneType)Set mic modevm.SetMicrophoneType(KatzuVoiceManager.MicrophoneType.PushToTalk); SetMicrophone(string)Select device by namevm.SetMicrophone("Headset Mic"); GetAvailableMicrophones()List device namesvar mics = vm.GetAvailableMicrophones(); GetCurrentMicrophoneIndex()Current selected mic indexint idx = vm.GetCurrentMicrophoneIndex(); SetMicrophoneByIndex(int)Select mic by indexvm.SetMicrophoneByIndex(1); SelectNextMicrophone()Cycle to next micvm.SelectNextMicrophone(); SelectPreviousMicrophone()Cycle to previous micvm.SelectPreviousMicrophone(); Status and metrics APIs APIPurposeExample GetCurrentVoiceLevel()Live local voice levelfloat lvl = vm.GetCurrentVoiceLevel(); GetVoiceActivationMinLevel()Min threshold valuefloat min = vm.GetVoiceActivationMinLevel(); GetVoiceActivationMaxLevel()Max visual threshold valuefloat max = vm.GetVoiceActivationMaxLevel(); IsCurrentlyTransmitting()Whether local is sending voice nowbool tx = vm.IsCurrentlyTransmitting(); GetRecordingClip()Current AudioClip used by recorderAudioClip c = vm.GetRecordingClip(); GetCurrentMicrophoneDisplayName()Readable selected mic labelnameLabel.text = vm.GetCurrentMicrophoneDisplayName(); GetSentVoiceFrameCount()Total sent frame counterint s = vm.GetSentVoiceFrameCount(); GetReceivedVoiceFrameCount()Total received frame counterint r = vm.GetReceivedVoiceFrameCount(); GetPlayedVoiceFrameCount()Total played frame counterint p = vm.GetPlayedVoiceFrameCount(); GetDroppedVoiceFrameCount()Total dropped frame counterint d = vm.GetDroppedVoiceFrameCount(); GetLastVoiceSenderKey()Last sender id keystring key = vm.GetLastVoiceSenderKey(); GetLastReceivedVoiceRms()Last received RMS valuefloat rr = vm.GetLastReceivedVoiceRms(); GetLastPlayedVoiceRms()Last played RMS valuefloat pr = vm.GetLastPlayedVoiceRms(); ============================================================ Speaker/VoiceView/Recorder API ============================================================ Speaker/VoiceView/Recorder API Playback components and local voice helper APIs. KatzuSpeaker methods APIPurposeExample PlayAudioData(float[])Queue PCM samples to outputspeaker.PlayAudioData(samples); ClearBuffer()Flush playback bufferspeaker.ClearBuffer(); SetOwner(ProductUserId)Bind speaker to remote owner idspeaker.SetOwner(userId); SetSpatialAudio(bool)Enable/disable spatial modespeaker.SetSpatialAudio(true); SetFullyHear(bool)Apply fully-hear local modespeaker.SetFullyHear(true); SetVolume(float)Set output volumespeaker.SetVolume(0.8f); GetVolume()Read output volumefloat v = speaker.GetVolume(); GetLastPacketRms()Last packet RMS levelfloat rms = speaker.GetLastPacketRms(); IsSpeaking()Current speaking statebool talking = speaker.IsSpeaking(); GetQueuedSamples()Queued sample countint q = speaker.GetQueuedSamples(); KatzuVoiceView methods APIPurposeExample ReceiveVoiceData(byte[],ProductUserId)Receive voice payload pathvoiceView.ReceiveVoiceData(data,sender); SetTransmitVoice(bool)Enable/disable local transmitvoiceView.SetTransmitVoice(true); SetReceiveVoice(bool)Enable/disable receive playbackvoiceView.SetReceiveVoice(true); MutePlayer()Mute this player in voice viewvoiceView.MutePlayer(); UnmutePlayer()Unmute this playervoiceView.UnmutePlayer(); IsMuted()Muted statebool m = voiceView.IsMuted(); IsTalking()Talking indicator statebool t = voiceView.IsTalking(); SetTalkingIndicator(GameObject)Bind talking indicator objectvoiceView.SetTalkingIndicator(indicator); SetShowTalkingIndicator(bool)Show/hide indicator behaviorvoiceView.SetShowTalkingIndicator(true); KatzuVoiceRecorder methods APIPurposeExample ProcessBuffer(float[])Process microphone sample bufferfloat[] outBuf = recorder.ProcessBuffer(inBuf); ResetRecorder()Reset internal recorder staterecorder.ResetRecorder(); ============================================================ KatzuConsole API ============================================================ KatzuConsole API Central categorized log writer used by runtime and diagnostics flows. APIPurposeExample Log(level,message,category,context)Raw log entry writer with level/categoryKatzuConsole.Log(...); Info(string,string,Object)Info level messageKatzuConsole.Info("Joined room","Session"); Warning(string,string,Object)Warning level messageKatzuConsole.Warning("Queue full","Session"); Error(string,string,Object)Error level messageKatzuConsole.Error("Auth failed","Auth"); ============================================================ Player and Spawner API ============================================================ Player and Spawner API Avatar behavior APIs from KatzuPlayer and KatzuPlayerSpawner. KatzuPlayer methods APIPurposeExample InitializeAsLocal(GameObject,GameObject,GameObject)Bind local head/hands targetsplayer.InitializeAsLocal(head,left,right); InitializeAsRemote()Setup remote avatar modeplayer.InitializeAsRemote(); SetPlayerName(string)Apply display nameplayer.SetPlayerName("Ava_X3"); SetPlayerColor(Color)Apply avatar colorplayer.SetPlayerColor(Color.magenta); SetCosmeticState(string,string,bool,bool,bool)Core cosmetic state setterplayer.SetCosmeticState("Hat","Cap",true,true,true); EnableCosmetic(string,string)Enable named cosmeticplayer.EnableCosmetic("Hat","Cap"); DisableCosmetic(string,string)Disable named cosmeticplayer.DisableCosmetic("Hat","Cap"); OnRemoteTransformUpdate(...)Apply incoming remote transform updateplayer.OnRemoteTransformUpdate(...); OnRemoteNameUpdate(string)Apply remote name updateplayer.OnRemoteNameUpdate(name); OnRemoteColorUpdate(Color)Apply remote color updateplayer.OnRemoteColorUpdate(color); OnRemoteCosmeticUpdate(string,string,bool)Apply remote cosmetic updateplayer.OnRemoteCosmeticUpdate(slot,item,true); RefreshNetworkDebugInfo()Refresh runtime debug labelsplayer.RefreshNetworkDebugInfo(); GetPlayerName()Read current namevar n = player.GetPlayerName(); GetPlayerColor()Read current colorvar c = player.GetPlayerColor(); IsLocal()Is this local avatar?bool local = player.IsLocal(); KatzuPlayerSpawner methods APIPurposeExample SpawnRemotePlayer(ProductUserId)Spawn remote avatar instancespawner.SpawnRemotePlayer(userId); DespawnPlayer(ProductUserId)Remove one player avatarspawner.DespawnPlayer(userId); RefreshAllSpawnedPlayerNames()Refresh display names for all spawned avatarsspawner.RefreshAllSpawnedPlayerNames(); GetPlayerInstance(ProductUserId)Get spawned avatar object by user idvar go = spawner.GetPlayerInstance(userId); GetAllPlayers()Get all spawned avatarsvar list = spawner.GetAllPlayers(); DespawnAllPlayers()Remove all spawned avatarsspawner.DespawnAllPlayers(); SetSpawnPoints(Transform[])Replace spawn point listspawner.SetSpawnPoints(points); AddSpawnPoint(Transform)Add one spawn pointspawner.AddSpawnPoint(extraPoint); ============================================================ Template APIs ============================================================ Template APIs Public methods available in template scripts for scene/UI wiring. PublicToggle APIPurposeExample ManualJoin()Trigger join flow manuallypublicToggle.ManualJoin(); ManualLeave()Trigger leave flow manuallypublicToggle.ManualLeave(); GetCurrentState()Readable state textstateText.text = publicToggle.GetCurrentState(); PrivateToggle APIPurposeExample ManualJoin()Manual private joinprivateToggle.ManualJoin(); ManualLeave()Manual leaveprivateToggle.ManualLeave(); GetCurrentState()Readable state textlabel.text = privateToggle.GetCurrentState(); SetSessionCode(string)Set private code inputprivateToggle.SetSessionCode("CODE9"); IsInThisSession()Is local in this target sessionbool inRoom = privateToggle.IsInThisSession(); CodeComputerTemplate APIPurposeExample HandleTouchedCollider(Collider,Collider)Route keypad touch interactioncodeComputer.HandleTouchedCollider(key,other); Bind(CodeComputerTemplate,Collider)Bind key helper to owner + colliderkey.Bind(codeComputer,keyCollider); LeaderboardTemplate APIPurposeExample HandleButtonTouched(LeaderboardTouchButton,Collider)Handle mute/ghost touch eventsboard.HandleButtonTouched(button,other); Templates with inspector-driven flow ChangeQueue, ChangeColor, ChangeCosmetic, and SessionInfoDisplay are primarily trigger/inspector-driven and expose behavior through serialized fields and collision events rather than many public callable methods. ============================================================ FAQ ============================================================ FAQ Can I use Katzu networking without Meta or Steam auth? Yes. Use DeviceId or OpenID in KatzuAuth. DeviceId is easiest for internal testing. OpenID is better when you have a proper account backend. Can I sync rigidbodies, materials, sounds, and animations? Yes. Add the matching observable types in KatzuView and disable the ones you do not need for performance. Can only one player grab/own an object at a time? Yes. Use KatzuView ownership transfer requests and enforce ownership checks before writing state. Are mute and ghost local-only? Yes. Local filters are for your client only unless you write your own server-authoritative moderation layer. What happens if owner leaves? It depends on each KatzuView object setting: DoNothing, DeleteObject, or MakeHostOwner. Does script file line count hurt runtime performance? No. Runtime cost depends on what code does each frame and network payload size, not how many lines are in the file. Can this support game types like tag, building, or co-op gadgets? Yes. The framework supports those patterns if you implement ownership, authoritative rules, and synchronization correctly. ============================================================ Code Recipes ============================================================ Code Recipes This section gives focused, topic-specific snippets you can copy and adapt. Each recipe solves one problem. How to use recipes Read the problem statement at top of each recipe Copy the snippet into your own script Rename methods and fields to your project style Hook methods to UI/buttons/events in inspector Test with two clients, not just one editor instance Recipe categories CategoryRecipes Auth and join flowAuth Gate, Busy Join, Safe Leave Then Join, Reconnect Loop Room and profileRoom Code Sanitizer, Profile Apply UI, Session Attribute Score Player controlsLocal Filters Panel, Ownership Grab Action Object and eventsRPC Light Toggle VoicePush To Talk, Mic Cycle, Voice Debug Label Ops and debugConsole Category Logger, Host Migration Banner ============================================================ Auth Gate Coroutine ============================================================ Auth Gate Coroutine Use this when your UI should stay disabled until auth is complete or failed. Important API KatzuAuth.IsAuthComplete() KatzuAuth.HasAuthFailed() KatzuAuth.GetLastAuthError() using System.Collections; using UnityEngine; using KatzuLabs.Networking; public class WaitForAuthGate : MonoBehaviour { public GameObject blockedUiRoot; public GameObject readyUiRoot; public float timeoutSeconds = 30f; private IEnumerator Start() { if (blockedUiRoot != null) blockedUiRoot.SetActive(true); if (readyUiRoot != null) readyUiRoot.SetActive(false); float elapsed = 0f; while (KatzuAuth.Instance == null && elapsed ============================================================ Busy Join Button ============================================================ Busy Join Button Prevents duplicate join calls from fast button taps. using UnityEngine; using UnityEngine.UI; using KatzuLabs.Networking; public class BusyJoinButton : MonoBehaviour { public Button joinButton; public float lockSeconds = 1.0f; private bool _busy; public void OnJoinPublicPressed() { if (_busy) return; if (KatzuAuth.Instance == null || !KatzuAuth.Instance.IsAuthComplete()) return; _busy = true; if (joinButton != null) joinButton.interactable = false; KatzuSessionManager.Instance.JoinPublic(); Invoke(nameof(ClearBusy), lockSeconds); } private void ClearBusy() { _busy = false; if (joinButton != null) joinButton.interactable = true; } } ============================================================ Room Code Sanitizer ============================================================ Room Code Sanitizer Sanitizes room input and strips banned terms before join. Checklist Uppercase for consistency Keep only A-Z and 0-9 Apply banned-word replacement Reject empty result using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; using KatzuLabs.Networking; public class RoomCodeJoiner : MonoBehaviour { public List bannedParts = new List(); public void JoinByUserInput(string rawInput) { string code = Sanitize(rawInput); if (string.IsNullOrEmpty(code)) { Debug.LogWarning("Join code empty after sanitize."); return; } KatzuSessionManager.Instance.JoinCode(code); } private string Sanitize(string raw) { if (string.IsNullOrEmpty(raw)) return string.Empty; string value = raw.ToUpperInvariant(); value = Regex.Replace(value, "[^A-Z0-9]", ""); for (int i = 0; i 12) value = value.Substring(0, 12); return value; } } ============================================================ Profile Apply UI ============================================================ Profile Apply UI Applies name, color, and cosmetic from one panel. using UnityEngine; using TMPro; using KatzuLabs.Networking; public class ProfilePanelApply : MonoBehaviour { public TMP_InputField nameInput; [Range(0, 100)] public int redPercent = 100; [Range(0, 100)] public int greenPercent = 100; [Range(0, 100)] public int bluePercent = 100; public string slotName = "Hat"; public string cosmeticName = "TopHat"; public void ApplyAll() { if (KatzuSessionManager.Instance == null) return; string nextName = nameInput != null ? nameInput.text : "Player"; KatzuSessionManager.Instance.SetPlayerName(nextName); Color nextColor = new Color(redPercent / 100f, greenPercent / 100f, bluePercent / 100f, 1f); KatzuSessionManager.Instance.SetPlayerColor(nextColor); KatzuSessionManager.Instance.EquipCosmetic(slotName, cosmeticName); } } ============================================================ Local Filters Panel ============================================================ Local Filters Panel Toggles mute, ghost, and fully-hear for one remote player without affecting others. using Epic.OnlineServices; using UnityEngine; using KatzuLabs.Networking; public class LocalPlayerFilterPanel : MonoBehaviour { public ProductUserId selectedUserId; public void ToggleMute() { Toggle(filter => KatzuSessionManager.Instance.SetLocalPlayerFilters(selectedUserId, !filter.mute, filter.ghost, filter.fullyHear)); } public void ToggleGhost() { Toggle(filter => KatzuSessionManager.Instance.SetLocalPlayerFilters(selectedUserId, filter.mute, !filter.ghost, filter.fullyHear)); } public void ToggleFullyHear() { Toggle(filter => KatzuSessionManager.Instance.SetLocalPlayerFilters(selectedUserId, filter.mute, filter.ghost, !filter.fullyHear)); } private void Toggle(System.Action apply) { if (KatzuSessionManager.Instance == null || selectedUserId == null) return; bool mute, ghost, fullyHear; if (!KatzuSessionManager.Instance.TryGetLocalPlayerFilters(selectedUserId, out mute, out ghost, out fullyHear)) { mute = false; ghost = false; fullyHear = false; } apply((mute, ghost, fullyHear)); } } ============================================================ Ownership Grab Action ============================================================ Ownership Grab Action Requests ownership from host first, then force-takes as fallback if your game rules allow it. using System.Collections; using UnityEngine; using KatzuLabs.Networking; public class OwnershipGrabAction : MonoBehaviour { public KatzuView targetView; public float fallbackDelaySeconds = 0.5f; public bool allowForceFallback = false; public void TryGrab() { if (targetView == null) return; targetView.RequestOwnershipTransferFromHost(); if (allowForceFallback) StartCoroutine(FallbackForceTake()); } private IEnumerator FallbackForceTake() { yield return new WaitForSeconds(fallbackDelaySeconds); if (targetView != null) targetView.ForceTakeOwnership(); } } ============================================================ Networked Light Toggle RPC ============================================================ Networked Light Toggle RPC Use RPC for event-style interactions where state change should happen instantly on all clients. using UnityEngine; using KatzuLabs.Networking; public class RpcLightToggleExample : MonoBehaviour { public KatzuView view; public Light target; public void Toggle() { if (view == null || target == null) return; bool next = !target.enabled; target.enabled = next; view.RPC("OnToggleLightRpc", next); } public void OnToggleLightRpc(bool enabled) { if (target != null) target.enabled = enabled; } } ============================================================ Voice Push To Talk ============================================================ Voice Push To Talk Starts recording only while a key is held. Useful for PC debug or non-VR clients. using UnityEngine; using KatzuLabs.Networking; public class PushToTalkVoice : MonoBehaviour { public KeyCode pushToTalkKey = KeyCode.V; private bool _recording; private void Update() { if (KatzuVoiceManager.Instance == null) return; bool pressed = Input.GetKey(pushToTalkKey); if (pressed && !_recording) { KatzuVoiceManager.Instance.StartRecording(); _recording = true; } else if (!pressed && _recording) { KatzuVoiceManager.Instance.StopRecording(); _recording = false; } } } ============================================================ Mic Cycle Buttons ============================================================ Mic Cycle Buttons Lets players switch microphone input device during runtime. using UnityEngine; using TMPro; using KatzuLabs.Networking; public class MicCyclePanel : MonoBehaviour { public TMP_Text micNameText; public void NextMic() { if (KatzuVoiceManager.Instance == null) return; KatzuVoiceManager.Instance.SelectNextMicrophone(); RefreshLabel(); } public void PrevMic() { if (KatzuVoiceManager.Instance == null) return; KatzuVoiceManager.Instance.SelectPreviousMicrophone(); RefreshLabel(); } public void RefreshLabel() { if (KatzuVoiceManager.Instance == null || micNameText == null) return; micNameText.text = KatzuVoiceManager.Instance.GetSelectedMicrophoneName(); } } If your voice manager uses different getter names, keep the flow and map to your actual methods. ============================================================ Voice Debug Label ============================================================ Voice Debug Label Shows live voice counters on screen to quickly isolate capture vs transport vs playback problems. using UnityEngine; using TMPro; using KatzuLabs.Networking; public class VoiceDebugLabel : MonoBehaviour { public TMP_Text label; private void Update() { if (label == null || KatzuVoiceManager.Instance == null) return; // Replace getter names below with your exact KatzuVoiceManager API if different. label.text = "Status: " + KatzuVoiceManager.Instance.GetCurrentStatusName() + "\ " + "Sent: " + KatzuVoiceManager.Instance.GetSentFrameCount() + "\ " + "Received: " + KatzuVoiceManager.Instance.GetReceivedFrameCount() + "\ " + "Played: " + KatzuVoiceManager.Instance.GetPlayedFrameCount() + "\ " + "Dropped: " + KatzuVoiceManager.Instance.GetDroppedFrameCount(); } } ============================================================ Host Migration Banner ============================================================ Host Migration Banner Shows a temporary message to players while host migration/recovery is in progress. using UnityEngine; using TMPro; using KatzuLabs.Networking; public class HostMigrationBanner : MonoBehaviour { public TMP_Text bannerText; private void Update() { if (bannerText == null || KatzuSessionManager.Instance == null) return; bool recovering = KatzuSessionManager.Instance.IsRecoveringSessionState(); bannerText.gameObject.SetActive(recovering); if (recovering) bannerText.text = "Reconnecting room host state..."; } } If your project does not expose a direct recovery status method, map this to your own migration state flag. ============================================================ Session Attribute Score ============================================================ Session Attribute Score Stores a session-wide value (example: target score) in room attributes. using UnityEngine; using KatzuLabs.Networking; public class SessionScoreAttribute : MonoBehaviour { public void SetTargetScore(int score) { if (KatzuSessionHelper.Instance == null) return; KatzuSessionHelper.Instance.SetSessionAttribute("target_score", score.ToString()); } public int GetTargetScoreOrDefault(int fallback = 10) { if (KatzuSessionHelper.Instance == null) return fallback; string raw = KatzuSessionHelper.Instance.GetSessionAttribute("target_score"); int value; return int.TryParse(raw, out value) ? value : fallback; } } ============================================================ Console Category Logger ============================================================ Console Category Logger Writes categorized logs that are easy to filter in KatzuConsole. using UnityEngine; using KatzuLabs.Networking; public static class NetDiagnosticsLog { public static void Auth(string message, Object context = null) { KatzuConsole.Info(message, "Auth", context); } public static void SessionWarn(string message, Object context = null) { KatzuConsole.Warning(message, "Session", context); } public static void VoiceError(string message, Object context = null) { KatzuConsole.Error(message, "Voice", context); } } ============================================================ Safe Leave Then Join ============================================================ Safe Leave Then Join Prevents race bugs when user switches rooms quickly. using System.Collections; using UnityEngine; using KatzuLabs.Networking; public class SafeLeaveJoinFlow : MonoBehaviour { public void LeaveThenJoin(string nextCode) { StartCoroutine(LeaveThenJoinRoutine(nextCode)); } private IEnumerator LeaveThenJoinRoutine(string code) { if (KatzuSessionManager.Instance == null) yield break; if (KatzuSessionManager.Instance.IsInSession()) { KatzuSessionManager.Instance.LeaveSession(); float wait = 0f; while (KatzuSessionManager.Instance.IsInSession() && wait ============================================================ Reconnect Attempt Loop ============================================================ Reconnect Attempt Loop A controlled retry loop for reconnecting to a known room code after disconnect. using System.Collections; using UnityEngine; using KatzuLabs.Networking; public class ReconnectLoop : MonoBehaviour { public string lastKnownRoomCode = ""; public int maxAttempts = 5; public float delaySeconds = 2f; public void StartReconnect() { StartCoroutine(TryReconnectRoutine()); } private IEnumerator TryReconnectRoutine() { if (KatzuSessionManager.Instance == null || string.IsNullOrEmpty(lastKnownRoomCode)) yield break; for (int i = 0; i