Advertisement

Implementing an input system in your game (specifically GLFW / LWJGL)?

Started by December 14, 2016 03:49 PM
5 comments, last by TheChubu 8 years ago

In LWJGL, input can either be polled or caught with a callback. Polling is very easy to implement in your code. The piece of code that you want to run when a certain button is pressed is just surrounded by an if-statement. In pseudo:


if (key_space.getState() == pressed) { executeCode() }

However, with callbacks I am at a bit of a loss. You can set a callback and that function gets called every time a button is pressed.

This works well, but I'm having an issue with understanding how to correctly implement this.

For example, say I have the following class:


class Player {
    void move() {
      //moves the player
    }
}

To access it with the callback, which most likely won't be in the same package as the player, I will have to make the move-method public. I would also have to find a way to make the player object either pass to the callback or at least make it visible to the callback. Otherwise, it won't be accessible.

This thread gave a solution of implementing your own polling wrapper around the callback, so that the callback basically has a list of inputs and sets them true or false based on input.

What would be the best course of action to keep the move method private?

Is the method in that thread a recommended approach?

Are there negatives to the approach?

Well, it all depends on what you're trying to achieve. For simple cases, polling directly the input system or registering a callback that will update the game state is enough. When it gets more complex, I would advise the method recommended in the thread you provided.

You could potentially have an event queue, register callbacks in the input system that would add an event to that queue every time a key you're interested in is pressed/released and, at the beginning of each frame, go through that queue and update the game state accordingly. Which means that you can update the speed and direction of your Player object and then use that data in your move method and keep it private. Why is it important to keep the move method private anyway?

Advertisement

I go with the approach of using callbacks to manage a set of flags and have built a polling system around that. The polling system can answer questions about the current state of the keyboard: "Is this key up/down?". It can also answer questions about rising or falling edge transitions: "was this key pressed/released in the last frame?".

I don't keep my move() methods private though. In fact that feels like an anti-goal to me. If the move() method is private then how can anything instruct the player on how to move! Probably the Player class then ends up being responsible for sourcing its own inputs. But assuming the Player class is also responsible for modelling the player then that's at least 2 responsibilities it has (a violation of the Single Responsibility Principle).

Well, it all depends on what you're trying to achieve. For simple cases, polling directly the input system or registering a callback that will update the game state is enough. When it gets more complex, I would advise the method recommended in the thread you provided.

You could potentially have an event queue, register callbacks in the input system that would add an event to that queue every time a key you're interested in is pressed/released and, at the beginning of each frame, go through that queue and update the game state accordingly. Which means that you can update the speed and direction of your Player object and then use that data in your move method and keep it private. Why is it important to keep the move method private anyway?

I go with the approach of using callbacks to manage a set of flags and have built a polling system around that. The polling system can answer questions about the current state of the keyboard: "Is this key up/down?". It can also answer questions about rising or falling edge transitions: "was this key pressed/released in the last frame?".

I don't keep my move() methods private though. In fact that feels like an anti-goal to me. If the move() method is private then how can anything instruct the player on how to move! Probably the Player class then ends up being responsible for sourcing its own inputs. But assuming the Player class is also responsible for modelling the player then that's at least 2 responsibilities it has (a violation of the Single Responsibility Principle).

It seems like the best case then. I'm mostly wondering because of people explaining that polling could cause input to be missed (E.G. if a button was pressed for a shorter time than it takes to get through a single frame, the polling could potentially miss an input).

As for keeping the Player movement private, that's something my college was bent on hammering in; keep everything private unless you really need to not do so. Considering I mostly do this game project for self-study, I thought it would be important for me to keep my style the same as the convention.

As for keeping the Player movement private, that's something my college was bent on hammering in; keep everything private unless you really need to not do so.


Seeing how you need it public in order to make the player move, I'd say you have a pretty strong case of “really need to not make it private” here.

Also consider that your public move() function doesn't have to contain the movement logic: It can simply set a flag inside the player to indicate that it's currently moving in a given direction. The movement itself can then happen in the Player's update function.

I'm mostly wondering because of people explaining that polling could cause input to be missed (E.G. if a button was pressed for a shorter time than it takes to get through a single frame, the polling could potentially miss an input).

Correct. If you had a slow frame your user could manage to press and release a key in-between successive polls and thereby you will never know that the key was pressed. This is related to signal processing in general; if you sample slower than the Nyquist rate then you'll mis-represent the shape of the signal.

Using callbacks you won't miss input events. However if all you did was to keep an active/inactive flag updated using callbacks and then you poll that state flag then you can still miss input events just like before. This is why I also keep rising/falling edge flags, so that even if a key is not currently up/down I can still use polling to see if it has been pressed/released since the last frame.

Advertisement

It seems like the best case then. I'm mostly wondering because of people explaining that polling could cause input to be missed (E.G. if a button was pressed for a shorter time than it takes to get through a single frame, the polling could potentially miss an input).
Thats why some people dont tie their frame time to their polling rate.

As for the input system, myself I also use LWJGL3 and thus GLFW. I've set it up like this:

There are actions that do stuff. These have a "kind". For example, an instance of move left action has ActionKind.MOVE_LEFT. So when an input event mapped to MOVE_LEFT happens, that action handler gets called.

Entities contain a mappings component, which holds mappings from actual device inputs (ie, 'W' key press, X axis movement, etc) to action kinds. So mappings are switchable, and individual to each entity that has them.

So each frame, I poll all the input devices, then for each entity, I iterate over the devices mapped by that entity, calling the actions mapped to the events said devices produced.

Each action receives an InputEvent, which is a context object, containing a reference to the device that raised the event, a reference to the World instance, the entity that the action was mapped into, and a 'value' short. That value is a normalizing factor between input devices. For a key press it could be Short.MAX_VALUE, for a mouse movement event it could be some value depending on how much the mouse was moved in one direction (negative) or the other (positive).

That way you can have an action that assumes analog inputs, say that you're moving with a stick, and can handle being mapped from a keyboard or a game pad. In the case of the keyboard you'd be either moving 100% or not moving at all, whereas if you map the action to a stick axis, you can regulate the movement speed. The action being indifferent to the actual device its being mapped to.

Right now my code doesnt actually does this since I only use a keyboard but the API makes it feasible.

    public static final class MoveRight extends Action {
        MoveRight () {
            super( ActionKind.MOVE_RIGHT );
        }

        @Override
        public final void accept ( final InputEvent event ) {
            // Zero is released, 1 is pressed. Using the sign bit of the value.
            // So (-0 >>> 31) gives positive zero. (-1 >>> 31) gives one.
            final int val = -event.value() >>> 31;
            // fetch the velocity component of the entity and update it.
            event.world().getHandler( Velocity.class ).get( event.entity() ).x = val;
        }
    }

I map that MOVE_RIGHT kind to a friendlier action name "actMvRight" which is what I use in my config file:

keyboard :
actMvRight : D

At load time I fetch the event id with the name of 'D' from the keyboard InputDevice and the action mapped to the "actMvRight" name to build the mappings. Getting the event IDs from GLFW is rather easy with reflection, just get all the static fields beginning with "GLFW_KEY" in the GLFW class, and map them to a substring starting at the end of that name. So a "GLFW_KEY_D" field becomes "D" for the config file, and you can map an action instance ot the actual field value at load time (which is just an int).

The nice thing is that its not tied to any "player" entity, you could add those mappings to a random cube and you'd be able to move it with the keyboard. Moreover, you could map wasd for the player entity, the arrow keys for the cube entity, and the input system would handle it just fine.

Its versatile enough that for handling resizing of the window, I made the WindowContext an InputDevice itself, that produces resize events. A couple actions listen for them and call the appropriate method in RenderDevice to resize all the frame buffers.

It isnt perfect. For example, for button combinations, like "ctrl+something", I'd need to expand that a bit. Maybe an action that holds the state of the "ctrl" (or whatever key mapped to it), and a way to query it via the InputEvent. There are probably cleaner ways to handle it.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

This topic is closed to new replies.

Advertisement