The Interaction System provides a flexible framework for implementing interactive behaviors in the Kanda SDK. It manages relationships between interacting entities and interactable targets through a binding system.
Core Concepts
Interactors and Interactables
The system is built around two primary component types:
- Interactors: Entities that can initiate interactions (e.g., VR controllers, mouse cursors)
- Interactables: Entities that can be interacted with (e.g., buttons, grabbable objects)
These components establish the basic interactive relationship:
public struct Interactor : IComponentData { }
public struct Interactable : IComponentData { public ActivationTypes ActivationTypes; }
Hover Detection
Before interaction can occur, interactors detect potential targets through a hover system.
By default, these hover systems check for collisions with entities on the Interactable
physics layer.
Point Interactors
Use spherical distance checks to find targets within range:
public struct PointInteractor : IComponentData
{
public float Radius;
}
Ray Interactors
Cast a ray to detect distant targets:
public struct RayInteractor : IComponentData
{
public float Length;
}
Hover data for each interactor is stored in a buffer, allowing tracking of multiple potential targets:
public struct InteractorHoverData : IBufferElementData
{
public Entity OnEntity;
public float Distance;
}
The hover status for each interactable can be accessed by querying for HoveredInteractable
.
Interactor Input and Activation
Interactors are controlled through the InteractorInput
component, which is then processed by built-in systems to manage activations and bindings:
public struct InteractorInput : IInputComponentData
{
public bool IsPrimaryActive;
public bool IsSecondaryActive;
}
Input should be written in the GhostInputSystemGroup
to ensure proper network synchronization. Here's an example input system:
[UpdateInGroup(typeof(GhostInputSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial struct MyInteractorInputSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
foreach (var (myController, interactorInput) in SystemAPI
.Query<RefRO<MyController>, RefRW<InteractorInput>>())
{
interactorInput.ValueRW.IsPrimaryActive = myController.ValueRO.SelectPressed;
interactorInput.ValueRW.IsSecondaryActive = myController.ValueRO.GrabPressed;
}
}
}
Bindings
When an interactor is activated, it binds to a single interactable entity.
Bindings are only created when ActivationType
of interactor and interactables match. It is possible to select which activation types are supported for each interactable in the Interactable
component.
Example cases:
- Interactable.ActivationType = Primary: If user interacts with primary button, then a new binding will be created.
- If user interacts with Secondary button, then no new binding will be created.
- Interactable.ActivationType = Primary | Secondary: If user interacts with any of Primary or Secondary buttons, then a new binding will be created.
[Flags]
public enum ActivationTypes
{
Primary = 1,
Secondary = 2
}
public struct InteractionBinding : IComponentData
{
public Entity WithEntity;
public ActivationTypes ActivationTypesActive;
}
Advanced Use Cases
Primary/Secondary Interactions
Different activation types enable varied behaviors with the same interactor-interactable pair:
public partial struct MyWeaponSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
foreach (var (bindings, entity) in SystemAPI.Query<RefRW<InteractionBinding>>()
.WithAll<ActiveInteractable, MyWeaponTag>()
.WithEntityAccess())
{
if (binding.ValueRW.ActivationTypesActive == ActivationTypes.Secondary)
{
if (binding.ValueRW.ActivationTypesActive == ActivationTypes.Primary)
{
FireWeapon(entity);
}
}
}
}
private void FireWeapon(Entity weaponEntity) { }
}
Creating Custom Interactor Types
The Interaction System is designed to be extensible, allowing you to create custom interactor types for specialized interaction needs. Here's how to implement a new interactor type:
- Define the Component:
public struct ConeInteractor : IComponentData
{
public float Length;
public float Angle;
}
- Create the Hover System:
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup))]
[UpdateBefore(typeof(InteractionBindingSystem))]
public partial struct ConeInteractorHoverSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
}
}
- Add Authoring Component:
public class ConeInteractorAuthoring : MonoBehaviour
{
public float Length = 5f;
public float Angle = 45f;
private class Baker : Baker<ConeInteractorAuthoring>
{
public override void Bake(ConeInteractorAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent<Interactor>(entity);
AddComponent(entity, new ConeInteractor
{
Length = authoring.Length,
Angle = math.radians(authoring.Angle)
});
AddComponent<InteractorInput>(entity);
AddComponent<ActiveInteractor>(entity);
SetComponentEnabled<ActiveInteractor>(entity, false);
AddComponent<InteractorActivationData>(entity);
AddBuffer<InteractorHoverData>(entity);
AddComponent<InteractionBinding>(entity);
}
}
}
Custom interactor types integrate seamlessly with the existing binding and activation systems - you only need to implement the hover detection logic specific to your interaction style.
To further customize binding behaviour, you can refrain from adding the InteractionBinding
buffer and implement your own component-system combination instead.
Examples of Custom Interactors
- Volume Interactors: Detect interactables within arbitrary shapes
- Pattern Interactors: Detect specific movement patterns or gestures
- Multi-Point Interactors: Combine multiple detection points
These can be implemented in either this package, downstream packages, or directly in projects using the SDK.
Remember that custom interactors should:
- Handle physics queries efficiently
- Clear hover data when no valid targets are found
- Follow the same optimization patterns as built-in interactors