02

Featured project · My own game

Backyard Volley: From Blender modelsto a playablevolleyball game.

Backyard Volley. Arcade volleyball where you move by hand and aim every hit with the mouse. 1v1 up to 6v6 on three courts, bots that run real plays, and online matches with friends. I built the characters, poses, balls, gameplay systems, netcode, and the music generator.

Role
Solo: design, art, code, music
When
Sep – Oct 2026
Platform
Windows build
Stack
Godot 4.7GDScriptBlender 5.2Blender PythonENet (UDP)
Backyard Volley gameplay in the park court: a CPU player spikes over the net with a burst at the contact point while Anthony's player (dark hair, red jersey) waits on the cyan ring.
A real frame from the game, captured with Godot’s Movie Maker mode. Tap the numbers.
  1. 1Contact readoutTraining mode prints every hit’s charge, timing, alignment, and contact score, the same model you can try in the Spike Lab below.
  2. 2SOLID burstA warm comic burst marks a solid spike right where the hand met the ball. Blocks get cool blues; weak hits only spark.
  3. 3Ball trailThe trail encodes the last touch: hits are a thick streak in the hitter’s team colour, sets a gold ribbon, passes blue bubbles, blocks a red zig-zag.
  4. 4CrosshairYour character always faces the cursor. Moving and facing are separate, so you can shuffle sideways while aiming.
  5. 5Landing markerShows where the ball will land; its outer ring shrinks as the ball comes down, which is how you time your jump.
  6. 6You (cyan ring)My player, with a CPU teammate. Bots fill every empty spot, from 1v1 to 6v6.

Stage 1

Modeling. Characters as a kit, not a sculpture.

Every body piece is generated by a Blender Python script (build_character_kit.py, build_hair.py, build_clothing.py, build_apparel.py), so a change is an edit and a re-run, not hours of hand work.

Each piece’s origin sits on its joint in the game’s rig (hips, knees, head), so the game can attach and recolor it. A new hairstyle, shoe, or top is one new mesh in the kit, not a new character.

The face is not modeled at all. A shader projects decal textures onto the front of whatever head shape you pick, so every eye, brow, and mouth works on every head. Skin under clothing is hidden per-vertex with color-coded regions, so tops and shorts never z-fight with the body.

Hairstyles
16 Blender-made styles + bald
Garments
5 tops × 5 bottoms, any combination
Face options
8 eyes · 7 brows · 8 mouths · 3 head shapes
Debug rig view in the game's menu: the skeleton's labelled bones drawn over the player.
The rig view (R in the menu): the 19-bone skeleton the Blender poses drive
  • Hairstyle option 1
  • Hairstyle option 2
  • Hairstyle option 3
  • Hairstyle option 4
  • Hairstyle option 5
  • Hairstyle option 6
  • Hairstyle option 7
  • Hairstyle option 8
8 of the 16 Blender-built hairstyles, on the same head and face

Stage 2

Animation. Poses from Blender, motion from code.

I author key poses in a Blender pose library (the jump load, spike load and finish, setting, platform receive, block, dive), and a build script exports them as one-frame clips on a 19-bone rig.

The game blends them at runtime. Locomotion picks a pose from the direction you move relative to where you aim: forward run, two diagonal runs, a volleyball side shuffle, and a backpedal that is never just the run played backwards. Neighbouring directions blend so turning stays smooth.

On top of the pose, the body twists in layers: legs follow travel, hips mostly travel, chest splits the difference, and the head stays on the cursor.

Loading the rig…

Fig. 02 · live pose blend

19 bones · 20 clips

50% Jump load · 50% Spike load

Each bone’s rotation is slerped between the two clips, the same incremental blend the game uses in _blend_poses(). In play, the target pose is then eased toward every frame so changes never snap.

  • In-game capture: Ready pose

    Ready

  • In-game capture: Jump load pose

    Jump load

  • In-game capture: Spike pose

    Spike

  • In-game capture: Set pose

    Set

  • In-game capture: Platform pose

    Platform

  • In-game capture: Side dive pose

    Side dive

In-game captures from the menu’s pose preview (Godot Movie Maker mode).

Stage 3

Gameplay engineering. Every touch is physical, aimed, and judged by the host.

Nothing connects automatically. Your hitting hand sweeps a real arc; if it passes near the ball you hit it, and it goes where your cursor aims.

Setters read the hitter: where they are heading and how fast decides a quick, slide, shoot, back-row, or high ball. There is no attack menu.

Online, the host decides every hit and collision. Your PC sends intent (“I swung, aimed here, I’m standing here”), and the host checks it against where the ball was, adjusted for your ping.

Demo · Spike contact lab

side view · 1 square = 10 cm
100% timing window full power ≤ 35 cm still connects ≤ 85 cm

Drag the ball (or focus it and use arrow keys). Then hold the button to charge and release to swing, using the same rules as the game.

Alignment

100%

hand passes 7 cm from centre

Timing

100%

arm angle 2.60 rad (ideal 2.75)

Charge

0%

hold 0.55 s for 100%

Contact

—

alignment × timing

Grades: SOLID ≥ 0.85 contact, OK ≥ 0.65, otherwise WEAK. There’s no “perfect” tier: a good swing is a good spike.

Ported from spike_tuning.gd with the shipped values from data/spike.tres. In the game the ball is moving and you choose when to release; here it holds still so you can see how position along the arc becomes timing.

Under the hood

  • Spike contact

    Spike power = charge × contact score, and contact = alignment × timing. Both are forgiving: within 35 cm of the ball’s centre is full alignment, and it still connects out to 85 cm. Grades are SOLID, OK, or WEAK; there is deliberately no “perfect” tier.

  • The setter reads you

    The set is chosen from the hitter’s position and velocity, then thrown to where they will be. For fast sets the landing point is refined four times against the ball’s real flight time.

  • Host-authoritative netcode

    One player hosts over ENet. Clients send intent; the host measures hold time, validates position, rewinds the ball by half your round-trip time (capped at 150 ms), and computes contact itself.

  • Real block hitboxes

    Two arms, two hands, and the wall between them are checked against the ball’s path every frame. The result is a clean block, deflection, touch, or a miss with a reason: early, late, position, or low.

  • Instant replay

    A rolling 12-second buffer of ball, players, and contacts. After each point a broadcast camera replays the build-up with slow motion on the deciding touch. It’s presentation only: gameplay is frozen and restored exactly.

  • Generated, layered music

    The match theme is generated by a GDScript composer and split into four layers that build with the rally: bass at the serve, melody once a rally starts, brass in long rallies, choir at set point.

  • Scripted AI tests

    Run with --test-ai, nine scenarios drive the real game code (bots, ball physics, and the host’s hit logic), covering block coverage, tips, quick reads, bad passes, and set quality, then print a report.

Debug view on the beach: overlay text with hand-to-ball distance, arm angle, timing, contact, and block hitbox data, and a CLEAN BLOCK callout.
The F3 debug view during a match: live hand-to-ball distance, arm angle, contact grade, and the block-volume result: the instrumentation I tuned the game with.