Current best way to read generic inputs in the main loop

Hello everyone!

Could you help me to figure out what are the current best practices to fulfill these requirements:

  • Have a generic input manager to detect keyboard/mouse clicks/gamepad inputs
  • I’m using ECSY to structure my multiplayer game. Therefore I must check for inputs in the main loop of the game. One component/system would store the inputs, such as the keyboard arrows being pressed down. The other system, like movementSystem, would see if, for example, down arrow is pressed down (data stored in the input component) and then execute some movement code. Therefore I would like to do some if/switch statements that just do something like inputSystem.keyDown === downArrowKeyCode.

These are only the simplest of things that need to be done, after reading the keyboard inputs, I would need to do the same with gamepad. And I will also need mouse clicks later on…

hello and welcome!

let me ping @PolygonalSun

1 Like

Hey Panuchka,

Generally speaking, for what you’re describing, this system would need to cache inputs for all available devices that you wish to support. For Keyboards and Mice, this could easily be accomplished by taking advantage of event listeners for events like “keydown” or “pointermove”. Gamepads are a bit different. They only have events that fire when a device is connected or disconnected so a gamepad would need to be updated either as part of the polling process (checking for data) or as part of the game loop (may be a bit overkill).

In the code base, we do have something that works similar to what you’ve described and what I’ve mentioned, the [DeviceSourceManger].(Use the DeviceSourceManager for Inputs - Babylon.js Documentation). It’s a system to manager user input devices and provide inputs.

Here’s a simple example of how to use it: Babylon.js Playground

Give it a shot and let me know if this will help with what you’re working on.

1 Like

Wow thank you both for the replies, so fast! :heart_eyes:

DeviceSourceManager did the trick for getting things set up for ECS type of code organization. I actually started to experiment with DeviceInputSystem and got somewhat similar results but the querying/polling with deviceInputSystem.getDeviceSource(DeviceType.Keyboard).getInput(65) === 1 style is better than I had in mind.

Anyone who is interested, for basic ASWD keyboard movement, I have two systems.
My ActionSystem has a component Action that stores the deviceSourceManager instance and some other helping data. Every tick my ActionSystem checks if some keys have been clicked and stores the data in Action (I have left, right, up, down for now).
Then, in MovementSystem, I take the Action component, Mesh component and Movement component combined, and use them to move the game character.

Next job to do is the mesh mouse clicking, going to see how that goes… :grin:

So, I can detect mouse clicks easily with, for example this (mouse left button):
deviceInputSystem.getDeviceSource(DeviceType.Mouse).getInput(2) === 1

However, I would also require to see where the user clicked. The basic use case for this in my game, is to check if player wants to open a door. Player clicks the door mesh and it opens. Or, a player clicks a 3D box and sees its contents.

So my next question regarding this is; what would be the best practice in this case to:

  • Get the position of the click in this engine tick (we are still in ECS world, so we must poll things).
  • See, if the click hit some game object / mesh.

Can I utilize DeviceSourceManager for this as well, or do I have to do something else… :thinking:

Sorry to bother you with ping @PolygonalSun, but could you provide some direction to figure out mouse handling as I asked in the previous post? :sunglasses:

So there are a couple ways to do it.

The down and dirty way to do it is to just check what the values of X (PointerInput.Horizontal) and Y (PointerInput.Vertical) are when you get a click. Since the values for any connected device are being cached by the DeviceSourceManager (well, it’s the DeviceInputSystem that’s doing the actual caching), you can get the values of that device at any time.
Here’s a PG that shows a basic way to do it: Playground Example.

// You can also use the observable version onBeforeRenderObservable
scene.beforeRender = () => {
        const mouse = dsm.getDeviceSource(BABYLON.DeviceType.Mouse);
        if (mouse) {
            if (mouse.getInput(BABYLON.PointerInput.LeftClick) === 1) {
                console.log(mouse.getInput(BABYLON.PointerInput.Horizontal) + "," + mouse.getInput(BABYLON.PointerInput.Vertical));
            }
        }
    };

After checking that the device exists, we just poll the same device using getInput
Note, this when the left click in this example is pressed, the coordinates will continue to be reported until until the left click is released.

A better and more proper way that you could do it is to take advantage of the onInputChangedObservable. Here’s a PG that demonstrates this: Playground Example.

device.onInputChangedObservable.add((inputData) => {
                if (inputData.inputIndex === BABYLON.PointerInput.LeftClick && inputData.currentState === 1) {
                    console.log(device.getInput(BABYLON.PointerInput.Horizontal) + "," + device.getInput(BABYLON.PointerInput.Vertical));
                }
            });

The PG that I provided may have a couple more moving parts but basically, we use the onDeviceConnectedObservable object to set up the above snippet. From that Observable, we can can use the above code to add a callback to print the coordinates when an left click is observed (1 is what we use for a clicked or pressed value). I hope this helps a bit. In any case, if you have any other questions, please feel free to ask.

1 Like

Thank you, this information was just what I needed! :gift_heart: