Claude
Skill
dotnet-mixed-reality
Work on C# and .NET-adjacent mixed-reality solutions around HoloLens, MRTK, OpenXR, Azure services, and integration boundaries where .NET participates in the stack.
Virus-scanned
Reviewed automatically before listing.
Download
postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-mixed-reality-bfa4ebd.zip · 8 KB
Install
skills CLI
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-mixed-reality
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Mixed Reality with .NET
Trigger On
- building or integrating mixed-reality solutions with C#
- working on HoloLens, MRTK, Azure mixed-reality services, or OpenXR-related code
- reviewing how .NET services support a mixed-reality client
Workflow
- Acknowledge that much of Microsoft mixed-reality guidance is Unity-centered even when the implementation language is C#; do not pretend it is a standard .NET desktop stack.
- Separate engine-side concerns, device capability concerns, and backend service integration so the system boundary stays understandable.
- Use MRTK and OpenXR guidance intentionally, and verify current toolkit status before choosing a package or template path. See references/patterns.md for established architecture patterns.
- Treat performance, input, and spatial UX as core constraints, not polish items.
- When .NET mostly lives on the backend for a mixed-reality product, route that backend work through the relevant ASP.NET Core, SignalR, or Azure skill instead of overloading this one.
- Validate with the actual device or emulator path whenever possible because editor-only success is not enough.
References
- references/patterns.md - MRTK service architecture, OpenXR feature plugins, input action patterns, spatial awareness observers, and cross-cutting patterns for dependency injection, object pooling, and graceful degradation.
- references/examples.md - Common HoloLens scenarios including spatial anchors, hand menus, spatial mapping physics, eye tracking, remote rendering, voice commands, QR code tracking, and shared multi-user experiences.
Deliver
- clear boundaries between device code and backend services
- mixed-reality guidance grounded in current Microsoft tooling
- realistic validation expectations for MR scenarios
Validate
- the chosen toolkit path is current enough for the project
- device-specific constraints are explicit
- backend and client responsibilities are not blurred
Files (dotnet-skills)
-
references
-
examples.md 16.9 KB
# Common HoloLens Scenarios ## Scenario: Spatial Anchors for Persistent Holograms Place holograms that persist across sessions using Azure Spatial Anchors: ```csharp public class PersistentHologramManager : MonoBehaviour { private CloudSpatialAnchorSession _anchorSession; private readonly Dictionary<string, CloudSpatialAnchor> _anchors = new(); public async Task InitializeAsync(string accountId, string accountKey, string accountDomain) { _anchorSession = new CloudSpatialAnchorSession(); _anchorSession.Configuration.AccountId = accountId; _anchorSession.Configuration.AccountKey = accountKey; _anchorSession.Configuration.AccountDomain = accountDomain; _anchorSession.AnchorLocated += OnAnchorLocated; _anchorSession.LocateAnchorsCompleted += OnLocateAnchorsCompleted; _anchorSession.Start(); } public async Task<string> PlaceHologramAsync(GameObject hologram, Pose worldPose) { // Create native anchor var nativeAnchor = hologram.AddComponent<WorldAnchor>(); // Create cloud anchor var cloudAnchor = new CloudSpatialAnchor { LocalAnchor = nativeAnchor.GetNativeSpatialAnchorPtr() }; // Wait for enough spatial data while (!_anchorSession.IsReadyForCreate) { await Task.Delay(100); float progress = _anchorSession.SessionStatus.RecommendedForCreateProgress; Debug.Log($"Spatial data collection: {progress:P0}"); } await _anchorSession.CreateAnchorAsync(cloudAnchor); _anchors[cloudAnchor.Identifier] = cloudAnchor; return cloudAnchor.Identifier; } public void LocateSavedAnchors(IEnumerable<string> anchorIds) { var criteria = new AnchorLocateCriteria { Identifiers = anchorIds.ToArray(), Strategy = LocateStrategy.AnyStrategy }; _anchorSession.CreateWatcher(criteria); } private void OnAnchorLocated(object sender, AnchorLocatedEventArgs args) { if (args.Status == LocateAnchorStatus.Located) { var anchor = args.Anchor; // Restore hologram at anchor position UnityDispatcher.InvokeOnMainThread(() => { RestoreHologramAtAnchor(anchor); }); } } } ``` ## Scenario: Hand Menu with MRTK Create a hand-attached menu that follows the user's palm: ```csharp public class HandMenuController : MonoBehaviour { [SerializeField] private GameObject menuRoot; [SerializeField] private float activationAngle = 30f; [SerializeField] private Handedness targetHand = Handedness.Left; private IMixedRealityHandJointService _jointService; private bool _isMenuActive; private void Start() { _jointService = CoreServices.GetInputSystemDataProvider<IMixedRealityHandJointService>(); } private void Update() { if (_jointService == null) return; if (_jointService.IsHandTracked(targetHand)) { var palmPose = _jointService.RequestJointTransform(TrackedHandJoint.Palm, targetHand); UpdateMenuVisibility(palmPose); if (_isMenuActive) { PositionMenu(palmPose); } } else { HideMenu(); } } private void UpdateMenuVisibility(Transform palmTransform) { // Check if palm is facing user (gazing at palm) var headPosition = CameraCache.Main.transform.position; var palmToHead = (headPosition - palmTransform.position).normalized; var palmNormal = palmTransform.up; float angle = Vector3.Angle(palmNormal, palmToHead); if (angle < activationAngle && !_isMenuActive) { ShowMenu(); } else if (angle > activationAngle + 10f && _isMenuActive) { HideMenu(); } } private void PositionMenu(Transform palmTransform) { menuRoot.transform.position = palmTransform.position + palmTransform.up * 0.1f; menuRoot.transform.rotation = Quaternion.LookRotation( palmTransform.forward, CameraCache.Main.transform.position - menuRoot.transform.position ); } private void ShowMenu() { _isMenuActive = true; menuRoot.SetActive(true); } private void HideMenu() { _isMenuActive = false; menuRoot.SetActive(false); } } ``` ## Scenario: Spatial Mapping for Physics Use spatial mapping meshes for hologram physics interactions: ```csharp public class SpatialPhysicsManager : MonoBehaviour, IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> { [SerializeField] private PhysicMaterial surfaceMaterial; [SerializeField] private LayerMask spatialMeshLayer; private readonly Dictionary<int, MeshCollider> _meshColliders = new(); private void OnEnable() { CoreServices.SpatialAwarenessSystem?.RegisterHandler<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>>(this); } private void OnDisable() { CoreServices.SpatialAwarenessSystem?.UnregisterHandler<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>>(this); } public void OnObservationAdded(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData) { var meshObject = eventData.SpatialObject; AddPhysicsToMesh(meshObject); } public void OnObservationUpdated(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData) { var meshObject = eventData.SpatialObject; UpdateMeshPhysics(meshObject); } public void OnObservationRemoved(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData) { if (_meshColliders.TryGetValue(eventData.Id, out var collider)) { Destroy(collider); _meshColliders.Remove(eventData.Id); } } private void AddPhysicsToMesh(SpatialAwarenessMeshObject meshObject) { var meshFilter = meshObject.Filter; if (meshFilter == null || meshFilter.sharedMesh == null) return; var collider = meshObject.GameObject.AddComponent<MeshCollider>(); collider.sharedMesh = meshFilter.sharedMesh; collider.material = surfaceMaterial; meshObject.GameObject.layer = (int)Mathf.Log(spatialMeshLayer.value, 2); _meshColliders[meshObject.Id] = collider; } private void UpdateMeshPhysics(SpatialAwarenessMeshObject meshObject) { if (_meshColliders.TryGetValue(meshObject.Id, out var collider)) { collider.sharedMesh = meshObject.Filter.sharedMesh; } } } ``` ## Scenario: Eye Tracking for Gaze Selection Implement eye-gaze targeting for accessible interaction: ```csharp public class EyeGazeSelector : MonoBehaviour { [SerializeField] private float dwellTimeThreshold = 0.8f; [SerializeField] private float gazeStabilityRadius = 0.02f; private IMixedRealityEyeGazeProvider _eyeGazeProvider; private GameObject _currentTarget; private float _dwellTimer; private Vector3 _lastGazePoint; private void Start() { _eyeGazeProvider = CoreServices.InputSystem?.EyeGazeProvider; if (_eyeGazeProvider == null) { Debug.LogWarning("Eye tracking not available. Falling back to head gaze."); } } private void Update() { if (!IsEyeTrackingAvailable()) { UseHeadGazeFallback(); return; } ProcessEyeGaze(); } private bool IsEyeTrackingAvailable() { return _eyeGazeProvider != null && _eyeGazeProvider.IsEyeTrackingEnabled; } private void ProcessEyeGaze() { var gazeOrigin = _eyeGazeProvider.GazeOrigin; var gazeDirection = _eyeGazeProvider.GazeDirection; if (Physics.Raycast(gazeOrigin, gazeDirection, out var hit, 10f)) { var hitObject = hit.collider.gameObject; if (hitObject != _currentTarget) { OnGazeTargetChanged(_currentTarget, hitObject); _currentTarget = hitObject; _dwellTimer = 0f; } else if (IsGazeStable(hit.point)) { _dwellTimer += Time.deltaTime; if (_dwellTimer >= dwellTimeThreshold) { OnDwellComplete(_currentTarget); _dwellTimer = 0f; } } _lastGazePoint = hit.point; } else { if (_currentTarget != null) { OnGazeTargetChanged(_currentTarget, null); _currentTarget = null; } } } private bool IsGazeStable(Vector3 currentPoint) { return Vector3.Distance(currentPoint, _lastGazePoint) < gazeStabilityRadius; } private void OnGazeTargetChanged(GameObject previous, GameObject current) { previous?.GetComponent<IGazeTarget>()?.OnGazeExit(); current?.GetComponent<IGazeTarget>()?.OnGazeEnter(); } private void OnDwellComplete(GameObject target) { target?.GetComponent<IGazeTarget>()?.OnDwellActivate(); } private void UseHeadGazeFallback() { var headRay = new Ray( CameraCache.Main.transform.position, CameraCache.Main.transform.forward ); // Similar raycast logic with head gaze } } public interface IGazeTarget { void OnGazeEnter(); void OnGazeExit(); void OnDwellActivate(); } ``` ## Scenario: Remote Rendering Integration Stream high-fidelity 3D content from Azure Remote Rendering: ```csharp public class RemoteRenderingManager : MonoBehaviour { private RemoteRenderingClient _client; private RenderingSession _session; private RemoteManager _remoteManager; public async Task ConnectAsync(RemoteRenderingConfig config) { var sessionConfig = new RenderingSessionCreationOptions { MaxLeaseInMinutes = 30, Size = RenderingSessionVmSize.Standard }; _client = ApiClientFactory.CreateRemoteRenderingClient( config.AccountId, config.AccountKey, config.AccountDomain ); _session = await _client.CreateNewRenderingSessionAsync(sessionConfig); await _session.ConnectAsync(new RendererInitOptions { SessionId = _session.SessionId }); _remoteManager = _session.Connection.RemoteManager; } public async Task<Entity> LoadModelAsync(string sasUrl) { var loadParams = new LoadModelFromSasOptions { ModelUri = sasUrl }; var result = await _remoteManager.LoadModelFromSasAsync(loadParams); return result.Root; } public void PositionModel(Entity modelRoot, Pose worldPose) { modelRoot.Position = worldPose.position.ToRemote(); modelRoot.Rotation = worldPose.rotation.ToRemote(); } public async Task DisconnectAsync() { if (_session != null) { await _session.StopAsync(); _session = null; } } } ``` ## Scenario: Voice Commands with MRTK Implement voice-activated commands: ```csharp public class VoiceCommandHandler : MonoBehaviour, IMixedRealitySpeechHandler { private readonly Dictionary<string, Action> _commands = new(); private void Start() { RegisterCommands(); // Ensure speech system is enabled if (CoreServices.InputSystem?.IsRegistered(gameObject) == false) { CoreServices.InputSystem?.RegisterHandler<IMixedRealitySpeechHandler>(this); } } private void RegisterCommands() { _commands["select"] = () => SelectCurrentTarget(); _commands["menu"] = () => ToggleMainMenu(); _commands["reset"] = () => ResetScene(); _commands["help"] = () => ShowHelp(); _commands["place here"] = () => PlaceObjectAtGaze(); } public void OnSpeechKeywordRecognized(SpeechEventData eventData) { var keyword = eventData.Command.Keyword.ToLowerInvariant(); if (_commands.TryGetValue(keyword, out var action)) { eventData.Use(); action.Invoke(); // Provide audio feedback AudioFeedback.PlayConfirmation(); } } private void SelectCurrentTarget() { var focusProvider = CoreServices.InputSystem?.FocusProvider; var focusedObject = focusProvider?.GetFocusedObject( focusProvider.PrimaryPointer ); focusedObject?.GetComponent<ISelectable>()?.Select(); } private void ToggleMainMenu() { /* Implementation */ } private void ResetScene() { /* Implementation */ } private void ShowHelp() { /* Implementation */ } private void PlaceObjectAtGaze() { /* Implementation */ } } ``` ## Scenario: QR Code Tracking Detect and track QR codes for content anchoring: ```csharp public class QRCodeTracker : MonoBehaviour { private QRCodeWatcher _watcher; private readonly Dictionary<Guid, QRCodeAnchor> _trackedCodes = new(); public event Action<QRCodeAnchor> OnQRCodeDetected; public event Action<Guid> OnQRCodeLost; private async void Start() { var accessStatus = await QRCodeWatcher.RequestAccessAsync(); if (accessStatus == QRCodeWatcherAccessStatus.Allowed) { _watcher = new QRCodeWatcher(); _watcher.Added += OnQRCodeAdded; _watcher.Updated += OnQRCodeUpdated; _watcher.Removed += OnQRCodeRemoved; _watcher.Start(); } else { Debug.LogError("QR code tracking access denied"); } } private void OnQRCodeAdded(object sender, QRCodeAddedEventArgs args) { UnityDispatcher.InvokeOnMainThread(() => { var code = args.Code; var anchor = CreateAnchorForCode(code); _trackedCodes[code.Id] = anchor; OnQRCodeDetected?.Invoke(anchor); }); } private void OnQRCodeUpdated(object sender, QRCodeUpdatedEventArgs args) { UnityDispatcher.InvokeOnMainThread(() => { if (_trackedCodes.TryGetValue(args.Code.Id, out var anchor)) { UpdateAnchorPose(anchor, args.Code); } }); } private void OnQRCodeRemoved(object sender, QRCodeRemovedEventArgs args) { UnityDispatcher.InvokeOnMainThread(() => { if (_trackedCodes.Remove(args.Code.Id)) { OnQRCodeLost?.Invoke(args.Code.Id); } }); } private QRCodeAnchor CreateAnchorForCode(QRCode code) { var spatialGraphNode = SpatialGraphInteropPreview.CreateLocatorForNode( code.SpatialGraphNodeId ); return new QRCodeAnchor { Id = code.Id, Data = code.Data, PhysicalSize = code.PhysicalSideLength, SpatialNode = spatialGraphNode }; } } public class QRCodeAnchor { public Guid Id { get; set; } public string Data { get; set; } public float PhysicalSize { get; set; } public object SpatialNode { get; set; } } ``` ## Scenario: Shared Experience with Photon Enable multi-user collaboration in mixed reality: ```csharp public class SharedExperienceManager : MonoBehaviourPunCallbacks { [SerializeField] private GameObject localAvatarPrefab; [SerializeField] private GameObject remoteAvatarPrefab; private Dictionary<int, GameObject> _remoteAvatars = new(); public async Task JoinSharedSessionAsync(string roomName) { PhotonNetwork.ConnectUsingSettings(); while (!PhotonNetwork.IsConnectedAndReady) { await Task.Yield(); } var roomOptions = new RoomOptions { MaxPlayers = 8, IsVisible = true }; PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default); } public override void OnJoinedRoom() { SpawnLocalAvatar(); SynchronizeSpatialAnchor(); } public override void OnPlayerEnteredRoom(Player newPlayer) { Debug.Log($"Player {newPlayer.NickName} joined"); } private void SpawnLocalAvatar() { var headPose = CameraCache.Main.transform; var avatar = PhotonNetwork.Instantiate( localAvatarPrefab.name, headPose.position, headPose.rotation ); } private void SynchronizeSpatialAnchor() { // Share Azure Spatial Anchor ID with other users // so all users align to the same world coordinate system if (PhotonNetwork.IsMasterClient) { // Master creates and shares anchor CreateAndShareWorldAnchor(); } else { // Others locate the shared anchor LocateSharedWorldAnchor(); } } } ``` -
patterns.md 7.7 KB
# MRTK and OpenXR Patterns ## MRTK Architecture Patterns ### Service-Based Architecture MRTK uses a service locator pattern for core subsystems. Access services through the central service registry: ```csharp // Accessing MRTK services var inputSystem = CoreServices.InputSystem; var spatialAwarenessSystem = CoreServices.SpatialAwarenessSystem; var diagnosticsSystem = CoreServices.DiagnosticsSystem; ``` ### Input Action Mapping Define input actions in configuration rather than hardcoding gesture detection: ```csharp // Define input action in MRTK configuration profile // Then handle in code via IMixedRealityInputHandler public class GrabHandler : MonoBehaviour, IMixedRealityInputHandler { public void OnInputDown(InputEventData eventData) { if (eventData.MixedRealityInputAction == grabAction) { // Handle grab start } } public void OnInputUp(InputEventData eventData) { if (eventData.MixedRealityInputAction == grabAction) { // Handle grab release } } } ``` ### Spatial Awareness Observer Pattern Subscribe to spatial mesh updates rather than polling: ```csharp public class SpatialMeshHandler : MonoBehaviour, IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> { public void OnObservationAdded(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData) { // New mesh detected var meshObject = eventData.SpatialObject; ProcessNewMesh(meshObject); } public void OnObservationUpdated(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData) { // Existing mesh updated } public void OnObservationRemoved(MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> eventData) { // Mesh no longer tracked } } ``` ### Profile Configuration Pattern Use scriptable object profiles for runtime configuration: ```csharp // Create custom profile inheriting from base [CreateAssetMenu(fileName = "CustomHandTrackingProfile", menuName = "MRTK/Custom Hand Tracking Profile")] public class CustomHandTrackingProfile : BaseMixedRealityProfile { [SerializeField] private float gestureThreshold = 0.8f; [SerializeField] private HandTrackingMode trackingMode = HandTrackingMode.Full; public float GestureThreshold => gestureThreshold; public HandTrackingMode TrackingMode => trackingMode; } ``` ## OpenXR Patterns ### Feature Plugin Pattern Implement OpenXR features as discrete plugins: ```csharp #if UNITY_OPENXR [OpenXRFeature( UiName = "Custom Hand Tracking", BuildTargetGroups = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA }, Company = "YourCompany", Version = "1.0.0", FeatureId = "com.yourcompany.openxr.handtracking" )] public class CustomHandTrackingFeature : OpenXRFeature { protected override bool OnInstanceCreate(ulong instance) { // Initialize OpenXR extension return base.OnInstanceCreate(instance); } protected override void OnSubsystemStart() { // Subsystem is starting } protected override void OnSubsystemStop() { // Subsystem is stopping } } #endif ``` ### Action-Based Input Pattern Use OpenXR action bindings for cross-platform input: ```csharp // Define action map in code or via InputActionAsset var selectAction = new InputAction("Select", InputActionType.Button); selectAction.AddBinding("<XRController>{RightHand}/trigger"); selectAction.AddBinding("<HandInteraction>{RightHand}/select"); selectAction.performed += ctx => OnSelectPerformed(); selectAction.canceled += ctx => OnSelectCanceled(); selectAction.Enable(); ``` ### Session State Management Handle OpenXR session lifecycle properly: ```csharp public class XRSessionManager : MonoBehaviour { private void OnEnable() { XRInputSubsystem.boundaryChanged += OnBoundaryChanged; } private void OnDisable() { XRInputSubsystem.boundaryChanged -= OnBoundaryChanged; } public void HandleSessionStateChange(XRSessionState state) { switch (state) { case XRSessionState.Ready: InitializeXRContent(); break; case XRSessionState.Focused: ResumeExperience(); break; case XRSessionState.Visible: PauseInteractiveContent(); break; case XRSessionState.Stopping: SaveSessionState(); break; } } } ``` ## Cross-Cutting Patterns ### Dependency Injection for XR Services Abstract XR services behind interfaces for testability: ```csharp public interface ISpatialAnchorService { Task<SpatialAnchor> CreateAnchorAsync(Pose pose, string identifier); Task<SpatialAnchor> LoadAnchorAsync(string identifier); Task DeleteAnchorAsync(string identifier); } public class AzureSpatialAnchorsService : ISpatialAnchorService { private readonly CloudSpatialAnchorSession _session; public async Task<SpatialAnchor> CreateAnchorAsync(Pose pose, string identifier) { var cloudAnchor = new CloudSpatialAnchor(); cloudAnchor.LocalAnchor = CreateNativeAnchor(pose); cloudAnchor.AppProperties["id"] = identifier; await _session.CreateAnchorAsync(cloudAnchor); return new SpatialAnchor(cloudAnchor); } } ``` ### Object Pooling for XR Performance Pool frequently instantiated objects to avoid GC pressure: ```csharp public class XRObjectPool<T> where T : Component { private readonly Queue<T> _available = new(); private readonly T _prefab; private readonly Transform _poolParent; public T Get() { if (_available.Count > 0) { var obj = _available.Dequeue(); obj.gameObject.SetActive(true); return obj; } return Object.Instantiate(_prefab, _poolParent); } public void Return(T obj) { obj.gameObject.SetActive(false); _available.Enqueue(obj); } } ``` ### Async Loading Pattern for MR Content Load heavy assets asynchronously to maintain frame rate: ```csharp public class MRContentLoader { public async Task<GameObject> LoadHologramAsync(string assetPath, CancellationToken ct) { var handle = Addressables.LoadAssetAsync<GameObject>(assetPath); while (!handle.IsDone) { ct.ThrowIfCancellationRequested(); await Task.Yield(); } if (handle.Status == AsyncOperationStatus.Succeeded) { return Object.Instantiate(handle.Result); } throw new InvalidOperationException($"Failed to load asset: {assetPath}"); } } ``` ### Graceful Degradation Pattern Handle missing device capabilities gracefully: ```csharp public class CapabilityManager { public bool HandTrackingAvailable { get; private set; } public bool EyeTrackingAvailable { get; private set; } public bool SpatialMappingAvailable { get; private set; } public void DetectCapabilities() { HandTrackingAvailable = IsSubsystemAvailable<XRHandSubsystem>(); EyeTrackingAvailable = IsSubsystemAvailable<XREyeTrackingSubsystem>(); SpatialMappingAvailable = IsSubsystemAvailable<XRMeshSubsystem>(); } private bool IsSubsystemAvailable<T>() where T : class, ISubsystem { var descriptors = new List<SubsystemDescriptorBase>(); SubsystemManager.GetSubsystemDescriptors(descriptors); return descriptors.Any(d => d is T); } public void ConfigureInputForCapabilities(InputController controller) { if (HandTrackingAvailable) { controller.EnableHandTracking(); } else { controller.EnableControllerFallback(); } } } ```
-
-
SKILL.md 2.3 KB
--- name: dotnet-mixed-reality version: "1.0.0" category: "AI" description: "Work on C# and .NET-adjacent mixed-reality solutions around HoloLens, MRTK, OpenXR, Azure services, and integration boundaries where .NET participates in the stack." compatibility: "Best for HoloLens, MRTK, Unity+C#, and service integrations around mixed reality." --- # Mixed Reality with .NET ## Trigger On - building or integrating mixed-reality solutions with C# - working on HoloLens, MRTK, Azure mixed-reality services, or OpenXR-related code - reviewing how .NET services support a mixed-reality client ## Workflow 1. Acknowledge that much of Microsoft mixed-reality guidance is Unity-centered even when the implementation language is C#; do not pretend it is a standard .NET desktop stack. 2. Separate engine-side concerns, device capability concerns, and backend service integration so the system boundary stays understandable. 3. Use MRTK and OpenXR guidance intentionally, and verify current toolkit status before choosing a package or template path. See [references/patterns.md](references/patterns.md) for established architecture patterns. 4. Treat performance, input, and spatial UX as core constraints, not polish items. 5. When .NET mostly lives on the backend for a mixed-reality product, route that backend work through the relevant ASP.NET Core, SignalR, or Azure skill instead of overloading this one. 6. Validate with the actual device or emulator path whenever possible because editor-only success is not enough. ## References - [references/patterns.md](references/patterns.md) - MRTK service architecture, OpenXR feature plugins, input action patterns, spatial awareness observers, and cross-cutting patterns for dependency injection, object pooling, and graceful degradation. - [references/examples.md](references/examples.md) - Common HoloLens scenarios including spatial anchors, hand menus, spatial mapping physics, eye tracking, remote rendering, voice commands, QR code tracking, and shared multi-user experiences. ## Deliver - clear boundaries between device code and backend services - mixed-reality guidance grounded in current Microsoft tooling - realistic validation expectations for MR scenarios ## Validate - the chosen toolkit path is current enough for the project - device-specific constraints are explicit - backend and client responsibilities are not blurred
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.