A collection of code and final products i’ve developed.
Project Alpha - Player Rig
| private void OnEnable() | |
| { | |
| EnvironmentManager.FindEnvironment(); | |
| if (EnvironmentManager.currentEnvironment != EnvironmentManager.CurrentEnvironment.StandaloneXR) | |
| { | |
| Cursor.lockState = CursorLockMode.Confined; | |
| Cursor.visible = false; | |
| } | |
| StartCoroutine(SetupRig()); | |
| } | |
| private void Update() | |
| { | |
| if (isReady == false) | |
| return; | |
| Move(); | |
| Rotate(); | |
| IsGrounded(); | |
| } | |
| private void LateUpdate() | |
| { | |
| // Remove later; Use in Debugging. | |
| EnvironmentManager.FindEnvironment(); | |
| Debug.Log($"Active Device Mode: {EnvironmentManager.currentEnvironment}\n" + | |
| $"Headset active: {EnvironmentManager.isXRHeadsetActive}"); | |
| } | |
| private void OnDisable() | |
| { | |
| UnsubscribeAll(); | |
| } | |
| private IEnumerator SetupRig() | |
| { | |
| if (HMD == null || leftController == null || rightController == null | |
| || cameraFxer == null || leftIKArm == null || rightIKArm == null) | |
| { | |
| Debug.LogError("One or more required references for the player rig are not assigned. " + | |
| "Please ensure HMD, leftHand, rightHand, cameraFxer, leftArmIK, and RightArmIK references are set in the inspector."); | |
| isReady = false; | |
| yield break; | |
| } | |
| if (leftController.isReady == false || rightController.isReady == false) | |
| { | |
| Debug.Log("Waiting for hand input controllers to be ready..."); | |
| isReady = false; | |
| yield return new WaitUntil(() => leftController.isReady && rightController.isReady); | |
| } | |
| SubscribeInputs(); | |
| SetPilotMode(); | |
| ResetTrackingSpace(); | |
| AssignReferences(); | |
| isReady = true; | |
| } | |
| #region Initalization & Helpers | |
| private void SubscribeInputs() | |
| { | |
| leftController.onThumbstickMove += OnLocomotion; | |
| leftController.onThumbstickPressed += OnSprint; | |
| rightController.onThumbstickMove += OnRotation; | |
| leftController.onMenuButtonPressed += OnPauseGame; | |
| leftController.onMenuButtonHeld += OnResetTrackingSpace; | |
| leftController.onSecondaryButtonPressed += OnSwapPilotModes; | |
| leftController.onTriggerPressed += OnUseAction; | |
| leftController.onGripPressed += OnGrabAction; | |
| rightController.onTriggerPressed += OnUseAction; | |
| rightController.onGripPressed += OnGrabAction; | |
| } | |
| private void UnsubscribeInputs() | |
| { | |
| leftController.onThumbstickMove -= OnLocomotion; | |
| rightController.onThumbstickMove -= OnRotation; | |
| leftController.onMenuButtonPressed -= OnPauseGame; | |
| leftController.onMenuButtonHeld -= OnResetTrackingSpace; | |
| leftController.onSecondaryButtonPressed -= OnSwapPilotModes; | |
| leftController.onTriggerPressed -= OnUseAction; | |
| leftController.onGripPressed -= OnGrabAction; | |
| rightController.onTriggerPressed -= OnUseAction; | |
| rightController.onGripPressed -= OnGrabAction; | |
| } | |
| /// <summary> | |
| /// Unsubscribes all event handlers from the player's hand input controllers to ensure that | |
| /// no unintended behavior occurs when switching modes or disabling the player rig. | |
| /// </summary> | |
| /// <exception cref="Exception">Thrown when the hand input controllers are not assigned.</exception> | |
| private void UnsubscribeAll() | |
| { | |
| if (leftController == null || rightController == null) | |
| { | |
| throw new Exception("hand input controller is not assigned."); | |
| } | |
| UnsubscribeInputs(); | |
| } | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <exception cref="Exception">Thrown when the hand input controllers are not assigned.</exception> | |
| private void SetPilotMode() | |
| { | |
| if (leftController == null || rightController == null) | |
| { | |
| throw new Exception("hand input controller is not assigned."); ; | |
| } | |
| if (EnvironmentManager.currentEnvironment != EnvironmentManager.CurrentEnvironment.StandalonePC) | |
| { | |
| if (pilotMode == PilotMode.CombatMode) | |
| { | |
| leftIKArm.SetState(MechArmIK.State.active); | |
| rightIKArm.SetState(MechArmIK.State.active); | |
| return; | |
| } | |
| } | |
| leftIKArm.SetState(MechArmIK.State.rest); | |
| rightIKArm.SetState(MechArmIK.State.rest); | |
| } | |
| /// <summary> | |
| /// Resets the tracking space offset based on the player's HMD position and the configured mech and cockpit settings. | |
| /// </summary> | |
| private void ResetTrackingSpace() | |
| { | |
| if (cockpitSettings.trackingSpaceOffset == null || cockpitSettings.mechSpaceOffset == null || | |
| HMD == null) | |
| { | |
| Debug.LogError("One or more required references for setting offsets are not assigned. " + | |
| "Please ensure trackingSpaceOffset, mechSpaceOffset, and HMD references are set in the inspector."); | |
| return; | |
| } | |
| var invalidHeightGuard = 0.0f; | |
| switch (EnvironmentManager.currentEnvironment) | |
| { | |
| case EnvironmentManager.CurrentEnvironment.StandaloneXR: | |
| case EnvironmentManager.CurrentEnvironment.PCVR: | |
| { | |
| if (HMD.localPosition.y <= 0.0f) | |
| { | |
| Debug.LogWarning("HMD local y position is at or below zero." + | |
| "This will cause issues when recalulating tracking space height."); | |
| invalidHeightGuard = STANDARD_PLAYER_HEIGHT; | |
| } | |
| break; | |
| } | |
| case EnvironmentManager.CurrentEnvironment.StandalonePC: | |
| { | |
| // Do nothing, because the default values are fine. | |
| break; | |
| } | |
| } | |
| //Reset the local position of the tracking space offset to ensure it starts from a known state before applying new offsets. | |
| cockpitSettings.trackingSpaceOffset.localPosition = Vector3.zero; | |
| // Calculate the standardized height offset by taking the standard player height and subtracting the HMD's local y position, | |
| // while also accounting for any invalid height scenarios with the invalidHeightGuard. | |
| // This ensures that the player's height is normalized based on the HMD's position, | |
| // providing a consistent experience regardless of the initial HMD height. | |
| var standardizeHeight = (STANDARD_PLAYER_HEIGHT - invalidHeightGuard) - HMD.localPosition.y; | |
| // Calculate the combined offsets for height, forward, and right based on the mech settings and cockpit settings. | |
| var heightOffset = mechSettings.mechHeight + cockpitSettings.heightOffset + standardizeHeight; | |
| var forwardOffset = cockpitSettings.trackingSpaceOffset.position.z + cockpitSettings.forwardOffset; | |
| var rightOffset = cockpitSettings.trackingSpaceOffset.position.x + cockpitSettings.rightOffset; | |
| var combinedOffsets = new Vector3(rightOffset, heightOffset, forwardOffset); | |
| cockpitSettings.trackingSpaceOffset.localPosition = combinedOffsets; | |
| // Find the inverse of the HMD's Y rotation to apply as a tracking space offset, | |
| // to ensure the tracking space is always oriented forwards, with the HMD being the dictator. | |
| var hmdForwards = HMD.localRotation.eulerAngles.y; | |
| cockpitSettings.trackingSpaceOffset.localRotation = Quaternion.Euler(0.0f, -hmdForwards, 0.0f); | |
| cockpitSettings.mechSpaceOffset.localPosition = Vector3.zero; | |
| cockpitSettings.mechSpaceOffset.localPosition = new Vector3( | |
| cockpitSettings.mechSpaceOffset.position.x, | |
| mechSettings.mechHeight, | |
| cockpitSettings.mechSpaceOffset.position.z | |
| ); | |
| } | |
| /// <summary> | |
| /// Assigns the references for the mech arm IK components to the player's hand transforms, | |
| /// </summary> | |
| private void AssignReferences() | |
| { | |
| leftIKArm.xrController = leftController.transform; | |
| rightIKArm.xrController = rightController.transform; | |
| leftIKArm.isReady = true; | |
| rightIKArm.isReady = true; | |
| } | |
| #endregion |
Project Alpha is an XR Game, that i am currently working on in Unity 6. The player rig class is one of my favourite accomplishments so far.
I purpose built, the class to handle inputs from all supported sources and proactively handle different player environments based on the incoming input system.
In the example, i am showing the core enable and startup behaviours.
Note Tasks
My first winUI App, that is open source and I am currently developing, to resolve issues I had with notepad, I wanted a tool where I could create a collection notes and swap between them on-demand without multiple button clicks or going through the file explorer.
App visuals
| public static async Task SaveNoteFile(string title, string content, | |
| StorageFolder saveLocation, NoteFile? file = null) | |
| { | |
| if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(content)) | |
| return; | |
| var hash = GetHash(content); | |
| var saveFile = await FindNoteFile($"{title}.json", saveLocation); | |
| if (file == null) | |
| { | |
| file = new NoteFile | |
| { | |
| FileTitle = title, | |
| DateCreated = DateTime.Now, | |
| FileContent = content, | |
| FileHash = hash | |
| }; | |
| } | |
| else | |
| { | |
| file.FileTitle = title; | |
| file.FileContent = content; | |
| file.FileHash = hash; | |
| } | |
| if (file.FileTitle != title || saveFile == null) | |
| { | |
| saveFile = await saveLocation.CreateFileAsync($"{title}.json", | |
| CreationCollisionOption.ReplaceExisting); | |
| } | |
| var json = JsonSerializer.Serialize(file); | |
| await FileIO.WriteTextAsync(saveFile, json); | |
| } |
In this code example I am showcasing one of my favourite systems, which is the save file system. This short method is running as task and ran in parallel, to improve product responsiveness and avoid hanging the application for large save files.
| private async void SaveEntry() | |
| { | |
| NoteEditor.TextDocument.GetText( | |
| Microsoft.UI.Text.TextGetOptions.FormatRtf, | |
| out var content); | |
| content = DocumentUtils.CleanText(content); | |
| await DocumentUtils.SaveNoteFile(FileTitle.Text, content, | |
| storageFolder, NoteFile); | |
| IsDirty = false; | |
| forceDirty = false; | |
| IsEditingExistingNote = true; | |
| RepaintAppElements(); | |
| RefreshTasksList(); | |
| } |
Note the following code examples wrapping has been changed to fit better on this webpage, you can view the original code on the GItHub repository.