0
\$\begingroup\$

I'm making a game engine from scratch and I'm currently handling the inputs. The low level handling is done, and now I would like to provide higher level input listeners, such as axis.

I've failed to find anything online on the maths behind inputs axis, so I though I'll give this community a shot.

I have two keys binded to an axis (let's say, W and S for forward / backward movement). Each key can be in a pressed or released state. What would be the approach to create a input axis value that goes from -1 to 1, smoothly and depending on the input ?

For example, when pressing W, the axis would smoothly move from 0 to 1 over the course of several frames, and back to 0 when the key is released.

I wondered about things like gravity or springs toward 0, while applying forces towards 1 (-1) when W (S) is pressed.

If anyone is interested, here is my current code for the axis (I'm doing it in Rust)

/// the stand alone axis
pub struct InputAxis {
    positive: winit::event::VirtualKeyCode,
    negative: winit::event::VirtualKeyCode,
    positive_state: bool,
    negative_state: bool,
    value: f32,
    speed: f32,
    springback: f32,
}

/// update code (raw input handling is not shown here, this func is called once per frame)
pub fn update(&mut self, delta: f32) {
    // key being pressed simulate a pushing force on the axis
    if self.positive_state {
        self.value += self.speed * delta;
    }
    if self.negative_state {
        self.value -= self.speed * delta;
    }
    // spring to return the axis to 0
    let spring = -self.value * self.springback * delta;
    self.value += spring;
    // clamp the axis value
    if self.value > 1. {
        self.value = 1.;
    } else if self.value < -1. {
        self.value = -1.;
    }
}

The code here technically works, but does not feel good at all. The values never fully reach zero after being moved, and I can't find a consistent balance between speed and springback.

Are there any well-known models that work well out here ?

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

There's actually two questions above:

Why does the code above not reach 0?

You never reach 0 because you are just scaling the value each frame. This means that you will get increasingly smaller values but never reach 0 - you basically modeled Zeno's paradox ;)

The simplest way to fix this is to set a lower bound and set your value to 0 if it ever reaches/crosses that bound:

value = value <= bound ? 0 : value;

This will work but you probably want more control in shaping response to input, which leads us to the next question...

What approach should I use for input?

The approach you're using will work but designers are bound to want more control depending on the style of gameplay (or even the gameplay itself - maybe there's effects that modify input behavior dynamically?).

Off the top of my head I can imagine designers wanting to tweak the parameters that are associated with easing functions:

Duration: How long does it take for input to settle, or how "tight" do the controls feel?

Curve: How do the values settle? Do they glide linearly to a stop? Do they taper off slowly? etc.

In the end I don't think you're likely to find a canonical approach to input handling due to the variety of genres and control schemes out there, but IMHO easing functions seem like a good general starting point that should cover a lot of design ground.

OLD ANSWER (TOTALLY WRONG: See comment)

I think the issue with your code is that there's no damping, which is why your input never settles to 0. Currently there's nothing that removes energy from the system except the clamping (which only works for large values) and numerical precision (which only works for tiny values). Therefore it'll keep oscillating indefinitely. You would want to add a damping parameter (a float that is less than 1) that will scale the energy of the system down each iteration:

let damping = 0.99;
let spring = -self.value * self.springback * delta * damping;

Springs are great at modeling these kinds of things but for inputs they may not be the perfect choice. A feature of springs is that they tend to bounce (i.e. over/under-shoot their target) which can be a problem for input: you don't want your character nudging past the edge of a cliff in a platformer, or your reticle to oscillate around your target in a shooter. You might be better off using some easing function to control the smoothing of the input. Like springs these can be very finely tuned but they give you more control, which you'll likely want with something that needs to be as precise and predictable as player input.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Thanks for the answer ! It's worth to note that I am not modeling a spring here: I am not changing the speed, the speed is a parameter on how reactive my input is. If I'm not mistaken, an actual spring would add force to velocity, then velocity to position, in which case you are correct. \$\endgroup\$ Jul 1, 2023 at 15:32
  • \$\begingroup\$ @LucioleMaléfique: You're right. I was off-base. Hopefully the updated answer is more useful. \$\endgroup\$
    – Mattia
    Jul 2, 2023 at 12:14
  • \$\begingroup\$ Thanks for the update ! That definitely lead me on the right tracks. I thing what I'm going to do is that raw inputs will give me target values (-1, 0, or 1) and i'm going to go with easing functions to move from current value to target value. I need to figure out the boundaries of my easing functions (0:1 / -1:0 / etc depending on direction, or value of input at the raw input change moment to target input ?). But this is the way, definitely an accepted answer ! \$\endgroup\$ Jul 2, 2023 at 14:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .