+2 votes

https://docs.godotengine.org/en/stable/tutorials/misc/state_design_pattern.html

In this tutorial it is 'stated', somewhere towards the end, it wouldn't be best practice to put input detection in a specific class. In this case the script that is attached to the kineticbody2d in a player controller

I am now wondering, what would be best practice in a case like that?

  • If PersistentState would be extending an Actor class would that be a place to put it?
  • Would a costom class that extends reference work?
  • Or another node, where would that one go?

I am thankful for any information

From the docs:
The persistent_state.gd script contains code for detecting input. This was to make the tutorial simple, but it is not usually best practice to do this.

    # Input code was placed here for tutorial purposes.
    func _process(_delta):
    if Input.is_action_pressed("ui_left"):
        move_left()
    elif Input.is_action_pressed("ui_right"):
        move_right()
in Engine by (20 points)

1 Answer

+2 votes

I can't tell you the best way to handle input in your game, and neither can anyone else. :)


I can't tell you what the best input system is for you because there is no single best solution for game input, or most game problems for that matter. I can however provide a few different examples of how I might lay out input for different specific games.

Before I give examples of high level architecture ideas. You should learn about the main ways godot gives you to look at input. You can...

  • Check what the state of a button, key, joystick is this frame.
  • Bind simple inputs into named "actions" and receive notifications when those actions are triggered, you can bind multiple buttons to a single "action" so that your game hears that the player wants to jump, and not that the player hit space on the keyboard, or X on the controller.
  • Receive input events that haven't yet been consumed by the UI or any other object.

Most Games Where You Control A Single Character

.

  • Bind input keys onto actions.
  • Receive them directly on the player class or player controller. When input comes in, check the player's state enum in a switch to determine what to do with that input. (KISS)
  • More complicated input may be received by other objects the player has attached to them, or other objects in the world. An "interact" action that needs to scan the world around the player for things that can be interacted with probably deserves it's own object that listens for that action.

Fighting Game With Local Multiplayer

.

  • Use an input manager singleton.
  • This object will track different connected input devices that players are using, and store the input for each player each frame into a buffer.
  • In the buffer we'll store a struct with three things in it, the directional input the player had from the arrow keys or joystick, and an enum with the action the player had pressed (punch, jump, none, kick), and how much time has passed since the frame before this one.
  • We will also listen for when important buttons are pressed and released and store how long they were held down for.
  • When the in game character's state machine hits a point where it wants to know about what the person controlling it wants to do it asks the input manager for either basic input state, or something more complex.
  • An example of more complex might be when an attack animation finishes, the player can ask the input manager to scan back through the buffer and do some kind of pattern recognition for what the player's input over the last second or so means. if we notice the joystick was moved in a particular pattern and kick was press, or the punch action was held for most of that time while forward was being held, we can construct a complex input key to see if there are any matching combo moves the player has to continue from the attack they just finished.

Real Time Strategy With Networked Multiplayer

.

  • Use an input manager singleton.
  • This object will both listen to unreceived input, as well as bound actions and translate them into more abstract "commands". A command being something like "move units with id's x and y to position z".
  • The input manager may need to track mouse state across multiple frames and do raycasts into the game world (so we can know what units will be selected when input signaling that we want to move them happens).
  • Commands are sent to a network manager to be synchronized with other players, and executed on each players machine at the same time (this is a lock step networking architecture, which may or may not fit your particular RTS!).
  • There may also be objects like the game camera that can just listen to input events from Godot directly and process them, generally these are objects don't need to be synchronized across the network.

All three of these input systems are quite different from eachother, and they're built around the specific ideas that I have in my head for what each of these types games should be. Your version of these ideas can be different!
Lot's of people are going to try to tell you what the "best" thing to do is in games and programming, there is no best :). Try to learn how not to add extra complication, and how to figure out what your true needs actually are.
If someone tells you something is good or bad, ask why, and then ask yourself if that reason applies to what you're making.

I may have gone a little overboard here because this is a question everyone asks when they get started. And every developer needs to hear at least once not to let yourself be bullied by people with big sticks about the "right" or "best" ways make your idea. Try stuff, make mistakes, get messy, and don't be afraid to throw away your work to build it better.

by (22 points)

First of all, thank you, there is definitely something to get from this.

I guess I know there isn't one best solution for every case.

So I want to understand why the input detection in the script mentioned in the tutorial is considered "not best practice" (bad practice?).
There must be something about this that is bad, when it is mentioned like that.
I would like to know what exactly. Is it a coupling thing or a readability thing or the one thing should handle just one thing, thing?

You mentioned some things like saving input for later use or to look at the last few events in cases where it's relevant to know.
A singleton as a input manager.

Interesting stuff, I might want to look into.

I am working on a 2d sidescroller and my main character got pretty convoluted. So I tried it with an enum + match state-machine but then I found this tutorial on how to implement the state pattern with scripts, and I really like having a class for each state.

In basically every basic tutorial, they just put the input detection in the player script, you also mentioned that this is ok for some scenarios.

Now they tell me that it is not best practice

So assuming the only thing that's bad, is to have that code in that script...
I made a class StateInput which is instanciated in the player class (persistent_state from the tutorial) where a setup function is called to give itself to the StateInput.

And then I call methods on that member like:

input_reciver = player class in this case

Func update ():
If Input.is_action_pressed("move_left"):
    input_reciever.move_left()

Now if I plug in another receiver like a different character then it should just work at least if all the methods are implemented in this case move_left().
Amiright?

Or maybe it would be better to not call functions on the receiver and instead save the input in a direction field like:

If move left
    direction = -1

And in the receiver get it from the object

var direction = state_input.direction

Velocity *= direction 

Or something like that

One reason the example code here may not be considered best practice is because it needs to poll input every frame, which is wasteful of cpu cycles (this may be very important on a mobile phone, or not at all on a desktop PC. Learn about the hardware you want your game to run on and how to profile it to know if any operation is wasteful enough that you should worry about it). The author chose to use it here because it's very simple and easy to understand, which is always a desirable trait. And it allows each "state" a player can be in to handle input in a different way. If the player is in the jump state, we may not want to allow them to jump again until they leave the jump state for example.

The way you're sending input to the player definitely works, if only one object out in the world needs to receive input from your manager this will probably never cause you problems. It could get messy if you want many objects to listen to your system for input, or if you want to have an object stop listening for a period of time. You can reduce coupling here and allow more objects to listen to your input system using signals or C# events (they're the same thing).

As for using your input system to set state somewhere. State is complexity that needs to be managed. If you don't need to remember what the input data looks like on future frames, It's only adding an additional thing to worry about in your system. You may want to transform your movement input from key presses / joystick input into a standard format and pass that to the input receiver, input_reciever.move(Vector2 move_direction). You could integrate multiplying by the time delta on the either sender or the receiver's side.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.