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
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"
...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
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.)
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)
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
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.
Try this: What happens if you pass
false
as the second parameter on each of thekey.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
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.
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
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.
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.)
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
And now your tank should properly face where it's going.
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!