Year 2, Term 3, 2025

Zibbs

An Android game that turns real walking into XP, and the sensor bugs that only appear on a real phone.

  • Unity
  • C#
  • Team of 6
  • 3 weeks
  • Programmer, sensor and input systems
Zibbs, Unity

What it was, and what I owned

A mobile game where the player's actual step count feeds gameplay progression. The design is simple to state and the implementation is where it gets interesting, because the Android step sensor does not behave the way a tutorial suggests.

The sensor resets to zero whenever the OS backgrounds the app. Read it naively and a player who checks a message loses their session progress, or gains a nonsense amount when the counter restarts below the stored offset. The fix is an OnApplicationPause handler that rolls the session's steps into a running total and re-baselines the offset on resume, so the sensor restarting is expected rather than corrupting.

There is also a guard that early-returns in the editor, because StepCounter.current does not exist on desktop and every play-mode test would otherwise throw before reaching the logic under it. Permission is requested asynchronously at runtime: ACTIVITY_RECOGNITION is a runtime permission on modern Android, and the device is enabled through the Input System only once it is granted.

Separately, room navigation is driven by touch. A tap and a swipe are the same gesture until they are not, so Tap.canceled pairs with Swipe.performed, and a swipe only counts if it clears a minimum horizontal distance while staying inside a vertical deviation limit, behind a cooldown. Room offsets scale by RectTransform.lossyScale, which is what keeps the movement correct across screen densities rather than only on the device it was tuned on.

Attribution

Team of 6. git blame attributes 92 of 111 lines in Walking.cs and 103 of 198 in SwitchRooms.cs to me, out of the people who committed to the repo. No AI involvement; this predates my year 3 work.

The code that makes the claim checkable

Walking.cs92/111 lines
void OnApplicationPause(bool pause)
{
    if (!pause && permissionGranted)
    {
        // Once the app pauses the stepCounter gets reset to 0 so the session XP is moved to the total XP pool.
        totalWalkingXP += sessionXP;
        // Reinitialize the step counter when the app is resumed
        InitializeStepCounter();
    }
}

The bug that only exists on a real phone. Android zeroes the step counter when the app is backgrounded, so on resume the session total is banked and the offset re-read. Without this, alt-tabbing either erases progress or invents it.

SwitchRooms.cs103/198 lines
void ProcessSwipe(InputAction.CallbackContext context)
{
    // If there is no swipe input or it is too small, it doesn't have to be processed.
    // Only counting swipes in the X direction, so if there is too much vertical change in the swipe it is invalidated.
    if (Mathf.Abs(swipeDirection.x) > minimumSwipeDistance && Mathf.Abs(swipeDirection.y) < verticalSwipeLimit)
    {
        Debug.LogFormat("Swiped {0} on the X-axis. with {1} on the Y-axis", Mathf.Abs(swipeDirection.x), Mathf.Abs(swipeDirection.y));
        if (swipeDirection.x > 0 && (gameManager == null || !gameManager.MinigameOngoing()))
        {
            RoomSwitch(-1);
            swipeDirection.x = 0;
            lastSwipeTime = Time.time;
        }
        if (swipeDirection.x < 0 && (gameManager == null || !gameManager.MinigameOngoing()))
        {
            RoomSwitch(1);
            swipeDirection.x = 0;
            lastSwipeTime = Time.time;
        }
    }
    swipeDirection = Vector2.zero;
}

A swipe has to clear a minimum horizontal distance while staying within a vertical deviation limit, so a diagonal drag does not register as a room change. The cooldown stops a fast double-swipe skipping a room.

Systems deep-dive

ACTIVITY_RECOGNITION is a runtime permission on Android 10 and up, so it cannot be declared in the manifest and assumed. The request is awaited asynchronously, and the step counter device is only enabled through InputSystem.EnableDevice once the result comes back granted. A denial leaves the game playable without the walking mechanic rather than throwing.

The counter itself is cumulative since boot, not since app start, so every reading is taken relative to a stored offset captured when tracking begins. stepsTaken = currentSteps - stepOffset.

The editor guard matters more than it looks. StepCounter.current is null on desktop, so without an early return every editor play session throws on the first frame of Update, and the rest of the game becomes untestable outside a device build.