Kanda Interactions 0.7.1
Loading...
Searching...
No Matches
Interaction System

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; // Detection radius for nearby interactables
}

Ray Interactors

Cast a ray to detect distant targets:

public struct RayInteractor : IComponentData
{
public float Length; // Length of the interaction ray
}

Hover data for each interactor is stored in a buffer, allowing tracking of multiple potential targets:

public struct InteractorHoverData : IBufferElementData
{
public Entity OnEntity; // The hovered interactable
public float Distance; // Distance to the interactable
}

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; // Primary interaction (e.g., trigger)
public bool IsSecondaryActive; // Secondary interaction (e.g., grip)
}

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>>())
{
// Map your input data to interactor activations
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, // Primary activation (e.g., trigger pull)
Secondary = 2 // Secondary activation (e.g., grip button)
}
public struct InteractionBinding : IComponentData
{
public Entity WithEntity; // The entity we're bound to
public ActivationTypes ActivationTypesActive; // The type of activations used for this binding
}

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())
{
// First check for grip
if (binding.ValueRW.ActivationTypesActive == ActivationTypes.Secondary)
{
// Then handle shooting if gripped
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:

  1. Define the Component:
    // Example: Cone-shaped interactor for wide-angle detection
    public struct ConeInteractor : IComponentData
    {
    public float Length; // Length of the cone
    public float Angle; // Cone angle in radians
    }
  2. Create the Hover System:
    [UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup))]
    [UpdateBefore(typeof(InteractionBindingSystem))]
    public partial struct ConeInteractorHoverSystem : ISystem
    {
    public void OnUpdate(ref SystemState state)
    {
    // Implement cone-shaped detection logic
    // Update InteractorHoverData buffer for valid targets
    }
    }
  3. 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);
    // Add cone interactor components
    AddComponent<Interactor>(entity);
    AddComponent(entity, new ConeInteractor
    {
    Length = authoring.Length,
    Angle = math.radians(authoring.Angle)
    });
    // Add standard interactor components
    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