Directional Inputs in Mini Micro
Tags: miniscript, minimicro, programming, gamedev
Published: 2024-04-25 15:07:08

This week we're going to look at several ways to get _directional inputs_ in [Mini Micro](https://miniscript.org/MiniMicro), 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](https://miniscript.org/MiniMicro/#download) — or at least open the [web version](https://miniscript.org/MiniMicro/#playnow) 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.)

![Output of key.axis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4n9a77x9clrgkozqnkb.gif)

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.

![Ball and stick display of smoothed, unnormalized inputs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ael8woxy4zz91i8l1jjg.gif)

> **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
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25e0kr3al8v3a7pymah7.gif)

## 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.

![Fighter movement demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8256ekjtp7157iaid1j.gif)

> **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/box54kean16tougght4v.gif)

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.

![Tank driving properly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8fnmbh3igqx4j7u9jvvj.gif)

## 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!