Motion controls for Ballistic Zen

A close friend of mine just released a game! You should probably check it out if you like games.

Ballistic Zen is a first person open world platformer with a unique movement system based on old-school air-strafing, updated for the modern age. Simple to pick up but difficult to master, Ballistic Zen celebrates the joy and meditation of movement in games.

Of course, I decided to play it with the Wii Balance Board.


Controls and controllers have always been a passion of mine, so when a colleague decided to sell his Balance Board, I immediately seized the opportunity. My glee was then almost-as-immediately shattered when I realised that opportunities to actually use the thing would be few and far between. (Playing Wii Fit in 2022, I found it…disappointing.)

Anyway, when Alex finally published Ballistic Zen, I knew that the time had come.

The final control scheme I ended up with uses the Balance Board and Wiimote/Nunchuk in concert. At first, I wanted to use the Balance Board on its own, but the game requires too many fast direction changes, and I couldn’t keep up without falling over. Using multiple sensitivity zones (that is, making the edges of the board more sensitive than the centre) helped – but in the end I had to couple it with the Wiimote.

Watching the video, you’ll notice that you turn much faster using the Wiimote and Nunchuk than you do using the Balance Board, and might reasonably ask if the Balance Board is really necessary. Well, it isn’t. But it isn’t completely useless, either. I did try a control scheme without the Balance Board, and though it worked, it lacked some of the fine control afforded by the full scheme. In short, it was more awkward. Moreover, with any motion control scheme, the more sensors you use, the more stable and reliable the signal, and in this case using the Wiimote, Nunchuk and Balance Board together gave the best result.

The B and Z (trigger) buttons are used for tight turns, inspired by Ballistic Zen’s actual gamepad control scheme.

Here’s the button mapping:

Button mapping. Start and stop by raising both the Wiimote and the Nunchuk. Turn right by raising the Nunchuk and/or leaning right. Turn left by raising the Wiimote and/or leaning left.

And here’s the FreePIE script:

def armsUp():
    return wm.acceleration.y < lift_arms_threshold and wm.nunchuck.acceleration.y < lift_arms_threshold

def shouldClick():
    global arms_currently_up
    global last_click_time
    if (wm.buttons.button_down(WiimoteButtons.DPadDown)):
        return True
    if armsUp():
        if arms_currently_up and time.clock() - last_click_time > click_debounce:
            return True
        elif not arms_currently_up:
            arms_currently_up = True
            last_click_time = time.clock()
    else:
        arms_currently_up = False
    return False

def shouldJump():
    return wm.buttons.button_down(WiimoteButtons.A)

def maprange(input, imin, imax, omin, omax):
    return (omax-omin)*((input-imin)/(imax-imin)) + omin

def deltaX(centre_of_gravity):
    turn = 0
    if (wm.buttons.button_down(WiimoteButtons.B)):
        turn += turn_enhancer_strength
        return turn
    if (wm.nunchuck.buttons.button_down(NunchuckButtons.Z)):
        turn -= turn_enhancer_strength
        return turn
    turn += maprange(wm.nunchuck.acceleration.y, 0, lift_arms_threshold, 0, arm_steering_sensitivity)
    turn -= maprange(wm.acceleration.y, 0, lift_arms_threshold, 0, arm_steering_sensitivity)
    if (abs(centre_of_gravity) > bb_deadzone):
        turn += centre_of_gravity * bb_sensitivity
    if (abs(wm.nunchuck.stick.x) > nunchuck_deadzone):
        turn += wm.nunchuck.stick.x * nunchuck_sensitivity
    return turn

def deltaY(stick):
    if (abs(stick) < nunchuck_deadzone):
        return 0
    return -(stick * nunchuck_sensitivity)

if starting:
    import time
    bb = wiimote[1].balanceBoard
    wm = wiimote[0]
    nunchuck_sensitivity = 0.1
    nunchuck_deadzone = 10
    bb_deadzone = 2
    bb_sensitivity = 0.1
    bb_deadzone_2 = 6
    bb_sensitivity_2 = 0.4
    lift_arms_threshold = -6.5
    turn_enhancer_strength = 10
    arm_steering_sensitivity = 3
    click_debounce = 0.25
    last_click_time = 0
    arms_currently_up = False

mouse.deltaX = deltaX(bb.centerOfGravity.x)
mouse.deltaY = deltaY(wm.nunchuck.stick.y)
mouse.setButton(0, shouldClick())
mouse.setButton(1, shouldJump())
keyboard.setPressed(Key.E, wm.nunchuck.buttons.button_down(NunchuckButtons.C))
keyboard.setPressed(Key.Escape, wm.buttons.button_down(WiimoteButtons.Plus))
keyboard.setPressed(Key.Tab, wm.buttons.button_down(WiimoteButtons.Minus))
keyboard.setPressed(Key.Q, wm.buttons.button_down(WiimoteButtons.One))
keyboard.setPressed(Key.A, wm.buttons.button_down(WiimoteButtons.Two))
keyboard.setPressed(Key.R, wm.buttons.button_down(WiimoteButtons.Home))

Ballistic Zen is available on Steam and on itch.io.

Oh, and if you like this kind of thing, check out BadRAM’s custom Ballistic Zen controller.