Specialized components
Physics and spatial systems
Input system
19 min
overview the input system enables user interaction with the 3d environment by detecting, processing, and routing user actions to the appropriate entities it bridges the gap between physical input devices (mouse, keyboard, xr controllers) and the virtual objects in the scene, allowing users to select, manipulate, and interact with the 3d world core components the input system consists of three primary components that work together to process user interactions inputsourcecomponent the inputsourcecomponent represents a physical or virtual input device and captures its raw input data it serves as the entry point for user actions into the system key properties source raw device data (e g , xr controller information) buttons current state of all buttons on the device raycaster for pointer based devices, provides an invisible ray for targeting intersections list of entities intersected by the raycaster // simplified from src/input/components/inputsourcecomponent tsx export const inputsourcecomponent = definecomponent({ name 'inputsourcecomponent', schema s object({ source s type\<xrinputsource>({ default {} as xrinputsource }), buttons s type\<buttonstatemap\<typeof defaultbuttonbindings>>({ default {} }), raycaster s class(() => new raycaster()), intersections s array(/ /) // additional properties }), // implementation details }); inputcomponent the inputcomponent is attached to entities that can respond to user input it defines which actions an entity listens for and provides a mechanism to query the current state of these actions key properties buttonbindings maps semantic action names to physical buttons inputsources references to entities with inputsourcecomponent that are currently targeting this entity buttons a proxy object that provides the current state of all defined actions // simplified from src/input/components/inputcomponent ts export const inputcomponent = definecomponent({ name 'inputcomponent', schema s object({ buttonbindings s record(s string(), / /), inputsources s array(s entity()), cachedbuttons s type\<buttonstatemap\<any>>(), buttons s serializedclass(/ /) // additional properties }), // method to get button states for an entity getbuttons (entity) => { // implementation details return buttonstateproxy; }, // additional methods }); clientinputsystem the clientinputsystem is the central processor that coordinates input handling it runs each frame to update input states, perform raycasting, and connect input sources to their targets key responsibilities update the state of all input sources perform raycasting for pointer based inputs determine which entities are being targeted update the inputsources list on targeted entities' inputcomponent s // simplified concept from src/input/systems/clientinputsystem tsx function execute() { // update xr controller transforms for (const entity of xrcontrollersquery()) { updatexrcontrollertransform(entity); } // update pointer raycasters (mouse, touch) for (const entity of pointersquery()) { updatepointerraycaster(entity); } // update gamepad button states for (const entity of inputsourcesquery()) { clientinputfunctions updategamepadinput(entity); } // determine targets and assign input sources for (const entity of inputsourcesquery()) { clientinputfunctions assigninputsources(entity); } } input processing workflow the input system follows a specific sequence of operations to process user input 1\ input capture when a user interacts with an input device browser events (e g , pointerdown , keydown ) are captured by event listeners these events are translated into state changes in the appropriate inputsourcecomponent for pointer based inputs, the position is converted to normalized coordinates // simplified concept from src/input/functions/clientinputhooks tsx function handlepointerevent(event, canvas) { // find or create the input source entity for this pointer const sourceentity = getpointerentity(event pointerid); // calculate normalized coordinates ( 1 to 1) const normalizedx = (event clientx / canvas width) 2 1; const normalizedy = (event clienty / canvas height) 2 + 1; // update the input source component setcomponent(sourceentity, inputsourcecomponent, { buttons { // update button states based on event type primaryclick event type === 'pointerdown' ? { down true, pressed true } { / / } }, position { x normalizedx, y normalizedy } }); } 2\ raycasting and target detection during each frame, the clientinputsystem updates the raycaster for each pointer based input source performs raycasting to find intersected entities sorts intersections by distance (closest first) // simplified concept from src/input/functions/clientinputheuristics ts function findraycastedinput(sourceentity) { const inputsource = getcomponent(sourceentity, inputsourcecomponent); const raycaster = inputsource raycaster; // get all entities with inputcomponent const interactiveentities = getentitieswithinputcomponent(); // perform raycasting const intersections = raycaster intersectobjects(interactiveentities, true); // sort by distance return intersections sort((a, b) => a distance b distance); } 3\ input source assignment after determining targets, the system updates the inputsources list on each targeted entity's inputcomponent handles input focus and capture for exclusive input control // simplified concept from src/input/functions/clientinputfunctions ts function assigninputsources(sourceentity) { // find entities intersected by the source's raycaster const intersections = findraycastedinput(sourceentity); // for each intersected entity for (const intersection of intersections) { const targetentity = intersection object entity; // get the target's inputcomponent const inputcomponent = getcomponent(targetentity, inputcomponent); // add this source to the target's inputsources list inputcomponent inputsources add(sourceentity); } } 4\ input query and response game logic can then query the input state for specific entities // in game logic code function processentityinput(entity) { // get the current button states for this entity const buttons = inputcomponent getbuttons(entity); // check if a specific action was triggered if (buttons interact? down) { // the interact action was just initiated this frame performinteraction(entity); } if (buttons select? pressed) { // the select action is currently active (held down) updateselection(entity); } } input flow example the following sequence diagram illustrates the flow of a mouse click on an interactive object sequencediagram participant user participant browser participant inputsource as inputsourcecomponent (mouse) participant clientinputsys as clientinputsystem participant targetinputcomp as inputcomponent (target) participant gamelogic user >>browser clicks mouse browser >>inputsource raw pointer event note right of inputsource updates button states note over clientinputsys engine loop tick clientinputsys >>inputsource reads state and position clientinputsys >>clientinputsys performs raycast note right of clientinputsys ray hits target entity clientinputsys >>targetinputcomp adds inputsource to inputsources list gamelogic >>targetinputcomp inputcomponent getbuttons() targetinputcomp >>gamelogic { interact { down true, } } gamelogic >>gamelogic process interaction button state representation the input system uses a standardized buttonstate object to represent the state of each button or action property description down true only for the first frame the button is pressed pressed true for the entire duration the button is held down up true only for the first frame the button is released value for analog inputs, a value between 0 and 1 this representation allows for precise control over when actions are triggered // trigger once when button is first pressed if (buttons action? down) { triggeronetimeaction(); } // continuous effect while button is held if (buttons action? pressed) { applycontinuouseffect(); } // trigger when button is released if (buttons action? up) { completeaction(); } input binding the inputcomponent allows for flexible mapping between physical inputs and semantic actions // define custom actions mapped to specific inputs setcomponent(entity, inputcomponent, { buttonbindings { jump \[keyboardbutton space, gamepadbutton a], fire \[mousebutton primaryclick, gamepadbutton righttrigger], crouch \[keyboardbutton controlleft, gamepadbutton b] } }); this approach provides several advantages input device independence (same action works with different devices) semantic clarity in code (check for "jump" instead of "space key") support for input remapping without changing game logic xr input integration the input system seamlessly extends to xr (virtual/augmented reality) controllers xr controllers are represented as entities with inputsourcecomponent , transformcomponent , and xrspacecomponent the clientinputsystem updates their transforms based on the xr session data raycasting is performed from the controller's position and orientation the same input binding and query mechanisms work for xr inputs this unified approach allows developers to create interactions that work across desktop, mobile, and xr platforms with minimal code changes next steps with an understanding of how users can interact with the virtual environment, the next chapter explores the physics system, which simulates physical interactions between objects next physics system docid\ ddtqld02fhez3s5xhqvot