Architecture
This document provides guidelines for developing XRC Toolkit packages following the Logic-Input-Feedback architecture pattern.
Overview
All XRC packages should follow a consistent three-component architecture that separates concerns between core functionality, user input handling, and user feedback. This pattern ensures modularity, maintainability, and consistency across the entire XRC ecosystem.
Core Architecture Pattern: Logic-Input-Feedback
Every XRC package implements three distinct components that work together:
%%{init: {'theme':'neutral'}}%%
classDiagram
class PackageLogic {
-XRBaseInteractor m_Interactor
-float m_Parameter
+XRBaseInteractor interactor
+float parameter
+void SetParameter(float value)
+event Action~bool~ stateChanged
}
class PackageInput {
-InputActionProperty m_Action
-PackageLogic m_Logic
+void OnActionPerformed()
}
class PackageFeedback {
-PackageLogic m_Logic
+void OnStateChanged(bool state)
}
PackageInput --> PackageLogic : calls methods
PackageLogic --> PackageFeedback : fires events
1. Logic Component ({PackageName}Logic)
Responsibility: Implement the core functionality and business logic of the package.
Key Requirements: - Contains all mathematical calculations and state management - Exposes public properties with private serialized fields for Unity Inspector - Uses component events for communication with feedback components - Independent of input and feedback concerns - Often manages XR Interaction Toolkit components (interactors, colliders)
Example Pattern:
public class SphereSelectLogic : MonoBehaviour
{
[SerializeField]
private XRDirectInteractor m_Interactor;
public XRDirectInteractor interactor
{
get => m_Interactor;
set => m_Interactor = value;
}
[SerializeField]
private float m_Radius = 0.04f;
public float radius => m_Radius;
public void SetRadius(float radiusValue)
{
m_Radius = Mathf.Clamp(radiusValue, m_MinRadius, m_MaxRadius);
// Update collider and attach transform
}
}
2. Input Component ({PackageName}Input)
Responsibility: Handle all user input and translate it to method calls on the Logic component.
Key Requirements:
- Uses [RequireComponent(typeof({PackageName}Logic))] to enforce dependency
- Subscribes to Unity Input System actions in lifecycle methods
- Gets Logic component reference in Awake() or Start()
- Only handles input - no business logic
Example Pattern:
[RequireComponent(typeof(SphereSelectLogic))]
public class SphereSelectInput : MonoBehaviour
{
[SerializeField]
private InputActionProperty m_ChangeRadius;
private SphereSelectLogic m_Logic;
private void Awake()
{
m_Logic = GetComponent<SphereSelectLogic>();
}
private void Start()
{
m_ChangeRadius.action.performed += OnRadiusChanged;
}
private void OnEnable() => m_ChangeRadius.action.Enable();
private void OnDisable() => m_ChangeRadius.action.Disable();
private void OnRadiusChanged(InputAction.CallbackContext context)
{
Vector2 input = context.ReadValue<Vector2>();
float newRadius = m_Logic.radius + input.y * Time.deltaTime;
m_Logic.SetRadius(newRadius);
}
}
3. Feedback Component ({PackageName}Feedback)
Responsibility: Provide visual, audio, or haptic feedback based on the Logic component's state.
Key Requirements:
- Uses [RequireComponent(typeof({PackageName}Logic))] to enforce dependency
- Subscribes to Logic component events, unsubscribes in OnDisable()
- Creates visual elements programmatically when needed
- Reacts to state changes rather than driving them
Example Pattern:
[RequireComponent(typeof(GoGoLogic))]
public class GoGoFeedback : MonoBehaviour
{
[SerializeField]
private GameObject m_VirtualHandPrefab;
private GoGoLogic m_Logic;
private GameObject m_VirtualHand;
private void Start()
{
m_Logic = GetComponent<GoGoLogic>();
m_Logic.runStarted += OnRunStarted;
m_Logic.runStopped += OnRunStopped;
}
private void OnDisable()
{
m_Logic.runStarted -= OnRunStarted;
m_Logic.runStopped -= OnRunStopped;
}
private void OnRunStarted()
{
m_VirtualHand = Instantiate(m_VirtualHandPrefab);
}
private void OnRunStopped()
{
if (m_VirtualHand != null)
Destroy(m_VirtualHand);
}
}
Package Structure Requirements
File Organization
Runtime/
├── {PackageName}Logic.cs # Core functionality
├── {PackageName}Input.cs # Input handling
├── {PackageName}Feedback.cs # User feedback
└── XRC.Toolkit.{PackageName}.asmdef
Assembly Definition
Each package must include an assembly definition file:
{
"name": "XRC.Toolkit.{PackageName}",
"rootNamespace": "XRC.Toolkit.{PackageName}",
"references": [
"Unity.XR.Interaction.Toolkit",
"Unity.InputSystem"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
Namespace Convention
All scripts must use the namespace pattern: XRC.Toolkit.{PackageName}