Last week I covered the theory of a clean Git workflow: branches, commits, and pull requests. If you haven’t read that one, start there. This article is the practical follow-up. We’re going to apply that exact git workflow to a complete project.
I’m using Pong because everyone knows what it is. The systems are small and the scope is contained. That makes it a good vehicle for demonstrating the workflow without the game design getting in the way.
This is a demonstration of the workflow, not a complete Pong tutorial. Some implementation details are simplified or skipped. The goal is to show how branches, commits, and pull requests map to real development decisions, not to cover every line of code.
GitHub setup (before any branches)
Before you open Unity, set up your repository.
- Create a new repo on GitHub.
- Check the box to initialize with a README.
- Add aÂ
.gitignore using the Unity template GitHub provides. This keeps Unity’s auto-generated files out of your commit history. - Clone the repo locally.
- Connect your local project toÂ
origin and pull the README andÂ.gitignore down.
You’re now on main with two files. That’s your baseline. Nothing has been built yet, and that’s correct.
This walkthrough uses Unity, but nothing in the workflow is engine-specific. The branching structure, commit format, and PR process work the same regardless of what you’re building in.
Setting up the branch structure
With the repo initialized, create your development branch directly from main.
git checkout -b development git push -u origin development
From this point forward, main only changes when something is ready to ship. development is where finished feature branches land. All active work happens on feature branches off of development.
feature/project-setup
Branch from development.
chore: initialize Unity project with 2D template
chore: configure folder structure (Scripts, Prefabs, Scenes, Materials, Fonts, Art)
fix: correct default physics settings for 2D project
chore: create and configure main game scene
Open a pull request from feature/project-setup into development. Review the diff. Confirm the folder structure looks right and there are no merge conflicts. Merge.
feature/input
Branch from development.
feat: add InputManager to handle player one and player two keyboard input feat: expose input axes as readable movement direction for other systems refactor: rename input axis variables to match Unity input axis naming convention
The refactor: this is the first time you’ll see a non-feat commit inside an active feature branch. My rule is simple: if the branch hasn’t merged yet, the fix stays on the branch. No need to open a separate branch for a correction made during active development on the same system.
Open a PR from feature/input into development. Review. Merge.
feature/paddle
Branch from development.
feat: add Paddle script with movement speed and clamped vertical bounds feat: create paddle prefab with BoxCollider2D and Rigidbody2D fix: correct paddle bounds clamp to account for sprite height offset feat: place player one and player two paddles in scene with input bindings
Open a PR from feature/paddle into development. Review. Merge.
art/paddle
Branch from development.
art: add custom paddle sprites to replace default white rectangles art: update paddle prefab to use new sprites fix: adjust paddle collider size to match new sprite dimensions
Art branches live separately from feature branches by design. A dedicated artist can work on art/paddle at the same time a programmer is working on feature/ball. Nothing collides. Both branches merge into development independently when they’re done.
Open a PR from art/paddle into development. Review. Merge.
feature/ball
Branch from development.
feat: add Ball script with initial velocity and serve direction
feat: add physics-based bounce on collision with paddles and walls
refactor: extract serve direction logic into separate method for clarity
feat: add speed increase on each paddle hit to escalate difficulty
feat: create ball prefab with CircleCollider2D and Rigidbody2D
fix: cap maximum ball speed to prevent tunneling at high velocities
feat: configure physics material on ball for zero friction and full bounce
feat: place ball in scene
Open a PR from feature/ball into development. Review. Merge.
art/ball
Branch from development.
art: add custom ball sprite to replace default white square art: update ball prefab to use new sprite
Open a PR from art/ball into development. Review. Merge.
feature/scoring
Branch from development.
feat: add goal trigger colliders on left and right walls feat: add ScoreManager to track and update player scores fix: correct goal trigger layer mask to exclude paddle collisions feat: add ball reset and re-serve logic on score
Open a PR from feature/scoring into development. Review. Merge.
bugfix/ball-clip-on-score
Branch from development.
fix: prevent ball from clipping through goal collider at high speed by switching to continuous collision detection
This is the first time a dedicated bugfix/ branch appears. The reason is timing. The ball clipping bug wasn’t discovered until after feature/ball had already merged into development. At that point, the branch is closed. The bug lives in development now, not in any active feature branch. The correct move is a new branch, scoped to just the fix, merged back into development when it’s done.
Open a PR from bugfix/ball-clip-on-score into development. Review. Merge.
feature/ui
Branch from development.
feat: add score display UI for player one and player two feat: add win condition screen with player name and final score refactor: move score update calls into ScoreManager events instead of direct UI references feat: connect ScoreManager events to UI score display and win screen
Open a PR from feature/ui into development. Review. Merge.
art/ui-font
Branch from development.
art: import custom font for score and win screen display art: apply custom font to all UI text elements fix: adjust font size and line height on win screen to prevent text overflow
Open a PR from art/ui-font into development. Review. Merge.
feature/game-loop
Branch from development.
feat: add GameManager to control match state (waiting, playing, game over)
feat: add serve state that holds ball until player input
fix: prevent serve from triggering before game state is fully initialized
feat: add win condition check and transition to game over state
refactor: consolidate state transition logic into single GameManager method
feat: add restart flow from game over back to serve state
Open a PR from feature/game-loop into development. Review. Merge.
Shipping: development into main
The game is complete. Every feature branch is merged. development represents a full, working build of Pong.
Before opening the final PR, bump the version number. This is a chore: commit directly on development because it isn’t a feature and it isn’t a fix. It’s bookkeeping. The version number tells anyone who picks up the project exactly what state it’s in.
chore: bump version to 1.0.0
Now open a PR from development into main. This is the last review before the build is in the hands of players. The diff covers every change made across the entire project. Review it thoroughly. This is the last chance to catch anything before it ships. Merge.
main holds a shipped game. Everything is completely separate. The entire development history lives in development and its branches, exactly where it belongs.
The full branch list
Here’s what the complete branch history looks like from the outside:
main development feature/project-setup feature/input feature/paddle art/paddle feature/ball art/ball feature/scoring bugfix/ball-clip-on-score feature/ui art/ui-font feature/game-loop
You can read the entire development arc of the project from that list. Setup, input, the two game objects, scoring, a bug that came in after the fact, UI, art, the game loop. No branch name requires explanation. That’s the goal.
The Git Workflow in Practice
The workflow you just walked through is the same one I use on every project. Branches keep systems separated. Commits make it easy to see exactly what changed and when. Pull requests give you a review step before anything becomes permanent.
That separation forces you to think about where one system ends and another begins before you write a single line. That habit builds better software architecture over time.
It also sets you up for collaboration. If you bring on an artist or another developer mid-project, the structure is already there. Each person works in their own branch. Nothing interferes until it’s ready to merge.
The Pong project is small. But the workflow scales. The same structure that organized twelve branches here will organize a hundred on a larger project.
0 Comments