DEV Community

Cover image for Directional Inputs in Mini Micro
JoeStrout
JoeStrout

Posted on

Directional Inputs in Mini Micro

This week we're going to look at several ways to get directional inputs in Mini Micro, whether that's from the keyboard or a gamepad/joystick.

The best way to learn this stuff is always to do it, so I encourage you to download Mini Micro — or at least open the web version in another tab — and follow along!

The Hard Way: key.pressed

One thought is to use the key.pressed method to directly check for keys you want. For example, you can check if the left-arrow key is pressed with code like this:

while true
    if key.pressed("left") then print "left"
    yield
end while
Enter fullscreen mode Exit fullscreen mode

Run this code, and you'll find that whenever you press the left-arrow key, the message "left" appears on the screen.

Try this: Add three additional lines handling "right", "up", and "down"!

This isn't a terrible approach if you only want to use the arrow keys for your directional inputs. However, players who prefer to use WASD or a gamepad will be disappointed.

Of course you could also check for WASD explicitly:

    if key.pressed("A") then print "left"
Enter fullscreen mode Exit fullscreen mode

...but this isn't a great solution in general, because people have different keyboard layouts. I, for example, use Dvorak; my W, A, S, and D keys are spread all over the keyboard. And even if you don't care about that, it still doesn't allow anyone to use a joystick or gamepad.

The Easy Way: key.axis

A better approach for directional inputs is to use key.axis. This method takes the name of an axis, and returns the current value of the input for that axis, in the range -1 to 1. In particular, if you pass "Horizontal" or "Vertical", it returns the primary directional input, which can come from arrow keys, WASD, or the main axis input of any connected gamepad or joystick!

Try it first with this simple code:

while true
    dx = key.axis("Horizontal")
    if dx != 0 then print dx
    yield
end while  
Enter fullscreen mode Exit fullscreen mode

Run this code, and you should observe values between -1 and 1 (inclusive) appearing as you press any of those horizontal inputs. (Press Control-C to break out of this infinite loop.)

Output of key.axis

You'll notice a lot of in-between values, even if you're using a binary (hard on/off) input like a keyboard or d-pad. That's because, by default, key.axis does a bit of smoothing to make the input a little more joystick-like. If you don't want this, you can turn the smoothing off with the optional second parameter:

    dx = key.axis("Horizontal", false)
Enter fullscreen mode Exit fullscreen mode

Use this version in the above code, and you'll see only values of -1 and 1. (Of course it also reports 0, but the code above only prints nonzero values.)

Demo 1: Visualizing the 2D input

Try this longer program, which draws a circular graph of the inputs in real time.

clear

drawInputGraph = function(dx, dy)
    gfx.color = color.gray
    gfx.clear
    cx = 80; cy = 80  // (graph center)
    r = 50   // (graph radius)
    gfx.drawEllipse cx-r, cy-r, r*2, r*2
    gfx.line cx, cy-r, cx, cy+r
    gfx.line cx-r, cy, cx+r, cy
    x = cx + dx * r; y = cy + dy * r
    gfx.line cx, cy, x, y, color.yellow
    gfx.fillEllipse x-5, y-5, 10, 10, color.yellow
end function

while true
    yield
    dx = key.axis("Horizontal")
    dy = key.axis("Vertical")
    drawInputGraph dx, dy
end while
Enter fullscreen mode Exit fullscreen mode

This makes a nifty little ball-and-stick display. As you use the arrow keys, WASD, or any game pad, you should see the yellow ball move around accordingly.

Ball and stick display of smoothed, unnormalized inputs

Try this: What happens if you pass false as the second parameter on each of the key.axis calls? How does the behavior of the display change?

One thing you may notice is that when you press two directions at once, like left+up, the ball extends right out of the circle. That's because the input in this case is (-1, 1), and that's a total distance of sqrt(2), or about 1.4 times farther than an orthogonal input like (-1, 0) or (0, 1). If you're using this input to move a sprite, it means the sprite will move faster diagonally than it does horizontally or vertically.

To fix that, you can simply detect when this is the case, and normalize your inputs. Insert the following code right after you get dx and dy, starting on line 20:

    dlen = sqrt(dx^2 + dy^2)
    if dlen > 1 then
        dx /= dlen
        dy /= dlen
    end if
Enter fullscreen mode Exit fullscreen mode

Now when you run and manipulate the inputs, you should find that the yellow ball never leaves the circle — the combined length of the directional input never exceeds 1.

Ball and stick display of normalized, smoothed input

Demo 2: Moving a Sprite

Let's try another program. (You might want to save your previous program, then reset to clear it out for this next one.) Enter the following code:

clear
spr = new Sprite
spr.image = file.loadImage("/sys/pics/Fighter.png")
spr.x = 480; spr.y = 320
spr.scale = 0.5
display(4).sprites.push spr
while true
    yield
    dx = key.axis("Horizontal")
    dy = key.axis("Vertical")
    if dx == 0 and dy == 0 then continue
    spr.rotation = atan(dy, dx) * 180/pi
    spr.x += dx * 10
    spr.y += dy * 10
end while
Enter fullscreen mode Exit fullscreen mode

This one loads the Fighter image from /sys/pics as a Sprite and adds it to display 4 (which by default is already configured as a SpriteDisplay). Then, the main loop moves the sprite around (by adding some multiple of dx and dy to its position). It also points the sprite in whatever direction it's going, using atan(dy, dx) to get the angle of the input in radians, and multiplying this by 180/pi to convert to degrees.

Fighter movement demo

Try this: Add the drawInputGraph function from the previous demo to this one, and invoke it from the main loop. You should be able to make a program that draws the ball-and-stick display, and lets you fly the ship around the screen, all at the same time.

Most of the top-down images in /sys/pics, like this one, are oriented so that they face to the right. That matches a mathematical angle of 0 degrees. But a lot of sprites you find on the internet are drawn differently, facing up or down instead of to the right.

Our /sys/pics directory does contain one such image: the tank. Let's see what happens if you the path in the file.loadImage call to "/sys/pics/Fighter.png". (I also changed the sprite scale to 1, as the tank image is smaller than the fighter.)

Tank animation, with tank moving sideways

Oof! Our tank moves sideways. In such a case, you just need to add an offset to the sprite rotation.

    spr.rotation = atan(dy, dx) * 180/pi - 90
Enter fullscreen mode Exit fullscreen mode

And now your tank should properly face where it's going.

Tank driving properly

Using /sys/demo/inputCheck

One last tip before we wrap it up for today: make use of the demo at /sys/demo/inputCheck.

This demo clears the screen, and then constantly checks all the inputs of the machine. Any that are not in their null/zero/off position, it draws to the screen, along with the name of the key, axis, or mouse input it is.

This is a great way to discover the exact name of the key or axis you're looking for, discover what mouse button activates when you press down on the scroll wheel, see whether your gamepad or joystick is working at all, etc. It's not just a demo; it's a valuable tool in any game dev's toolbox.

Conclusion

Directional inputs are an important part of almost any action game. Now you know how to read them, how to normalize them so you don't move faster diagonally than orthogonally, and how to move a sprite accordingly. Go forth and create something fun!

Top comments (0)