About
This was a group submission for the November 2025 Three Thing Game jam, an internal game jam hosted by the University of Hull, and came second place. Participants are given three words and must make a game around that theme, with our team being given the words: Underwater, Bus, and Blossom. The player controls a beluga "bus" that drives around picking up passengers and dropping them off at their destination. At the end of each day a player receives a score, and are able to progress to the next day. This repeats until they fail to reach the ever increasing target score. There is an online leaderboard for players to compare and compete for the highest score. The project was made in Unity with C#, and can be played on itch.io on both mobile and desktop or downloaded from the links above.
Core Gameplay Loop
- A
Fishspawns at aBusStop - It is assigned a random valid destination
BusStop - The player must pick it up before its pick-up timer expires
- Once picked up, a second delivery timer begins
- The fish must be delivered to the correct drop-off
BusStop - Failure occurs if either timer expires, success if delivered correctly
Fish Spawning
At its core, Fish objects are state-driven timers between two locations: the pick-up and drop-off stops.
State 1: Waiting at pick-up bus stop
- Spawned at pick-up
BusStop - Assigned random drop-off
BusStop - Waiting timer started
Drop-off stop cannot be same as pick-up
do
{
dropOff = random stop;
}
while (dropOff == pickUp);
State 2: Picked up by bus
- Fish becomes child of bus
- UI updates with drop-off location
- Added to
Bus.passengerQueue - Removed from
BusStop - Delivery timer started
- Pick-up counter increments
Timer resets on pick-up
timer = 15f * difficultyScaling;
State 3: Delivered to drop-off bus stop
- Fish removed from bus hierarchy
- UI updates and removes drop-off goal
- Animation coroutine plays before destruction
- Drop-off counter increments
Fish must match assigned drop-off bus stop
if (fish.dropOff == busStop)
Drop-off flow
bus.RemovePassenger(f);
CollectionCounter.SuccessfulDropOffs++;
GameController.instance.TotalScoreForRun += 10;
f.MoveAwayFromCenterAndDestroy();
State 4: Failed pickup
- Missed pick-up counter increments
- Spawn point freed for new fish
Spawn point freed
pickUp.isFish = false;
Destroy(gameObject);
State 5: Failed drop-off
- Fish removed from bus hierarchy
- UI updated
- Missed drop-off counter increments
Bus Stop Spawning System
The BusStopManager controls global spawning behaviour, managing all active stops and spawn timing.
- Holds all active
BusStopreferences - Spawns fish at random intervals
- Scales spawn rate with day progression
- Indirectly controls difficulty
Spawning is managed by a scaling formula
spawnRate -= GameController.instance.dayCounter;
spawnRate = Math.Max(spawnRate, 2);
Difficulty Scaling
Difficulty increases each day via spawn frequency and timer reduction.
Spawn Frequency
Fish spawn rate scaling
spawnRate -= GameController.instance.dayCounter;
spawnRate = Math.Max(spawnRate, 2);
Timer Scaling
Fish timer scaling
timer = baseTimer * Mathf.Pow(dayMultiplier, dayCounter - 1);
timer = Mathf.Max(timer, 0.5f);
| Day | Spawn Interval | Pickup Timer | Delivery Timer |
|---|---|---|---|
| 1 | 9 | 15.00 | 15.00 |
| 2 | 8 | 14.25 | 14.25 |
| 3 | 7 | 13.54 | 13.54 |
| 4 | 6 | 12.86 | 12.86 |
| 5 | 5 | 12.22 | 12.22 |
| 6 | 4 | 11.61 | 11.61 |
| 7 | 3 | 11.03 | 11.03 |
| 8 | 2 | 10.48 | 10.48 |
| 9 | 2 | 9.96 | 9.96 |
| 10 | 2 | 9.46 | 9.46 |
Score Calculation
Player score is based on pickups, successful deliveries, and missed fish.
Score calculation formula
float raw = pickups * pickupWeight + dropOffs * dropOffWeight - missed * missedPenalty;
float potential = (pickups * (pickupWeight + dropOffWeight)) + (missed * dropOffWeight);
float normalized = potential > 0f ? Mathf.Clamp01(raw / potential) : 1f;
Game
Missed fish are heavily penalised, while pickups partially offset losses.
| Normalized Score | Grade |
|---|---|
| ≥ 0.90 | S |
| ≥ 0.75 | A |
| ≥ 0.60 | B |
| ≥ 0.45 | C |
| ≥ 0.30 | D |
| < 0.30 | F |
A leaderboard tracks the top 10 scores globally. Player names must be 3–20 characters, contain valid symbols only, and pass profanity filtering.