Split Screen.
Lecture W6R1: Carving Code, Git for Two, Lab 4
▶ PRESS START
Lucas P. Cordova, Ph.D.
Willamette University | Thursday, October 1, 2026
Outline
- One reason to change: Tuesday's parts get a name and a test.
- Boss fight on paper: carve a god script into parts and a tuning sheet.
- Git for two: how a pair ships one game without collisions.
- Lab 4 launch: the Component Kit, then studio time.
Lab 3 is due tonight, 9:59 PM. The proposal is due Thursday, October 8, 9:59 PM. Lab 4 goes out today and is due Tuesday, October 20, 9:59 PM.

One Reason to Change
Tuesday's parts get a name and a test
Tuesday, in four rules
- One job per part. If you cannot name the job in three words, split it.
- The part owns its data. Health lives in HealthComponent, nowhere else.
- Signals up, calls down. Parts announce; owners decide.
- Numbers live in data. @export for one entity, a Resource file for shared tuning.
Rule one carries the most weight, and software engineering has a formal name for it.

The Single Responsibility Principle
Gather the things that change for the same reason. Separate the things that change for different reasons.
A module that follows the principle has one reason to change, and it answers to one kind of person asking for that change.
Robert C. Martin's formulation [1]. It is the S in SOLID, and it is the rule that tells you where to cut a god script into parts.
Component is the mechanism (a part is a child node). Single Responsibility is the judgment (which lines belong in which part).

Three tests for one job
| Test | Ask | Fails when |
|---|---|---|
| Three words | Can you name the job in three words or fewer? | The name needs an "and". |
| Who asks | Who would request a change to this code? | Designer, artist, and sound all would. |
| Lift out | Could this code move to another entity untouched? | It reaches into its owner or its neighbors. |
"Tracks hit points" passes all three. "Chases the player, flashes red, and updates the score" fails the first one out loud.

Meet the Berry Bandit
An enemy from a Blitz side game. It chases Blitz, hurts them on contact, takes three marionberries to defeat, and sometimes drops a fig. It works. It is also one file doing eight jobs.
1 # berry_bandit.gd (the whole enemy, one file) 2 extends CharacterBody2D 3 4 var hp: int = 3 5 6 func _physics_process(_delta: float) -> void: 7 var player = get_tree().get_first_node_in_group("player") 8 var to_player: Vector2 = player.global_position - global_position 9 if to_player.length() < 300.0: 10 velocity = to_player.normalized() * 180.0 11 else: 12 velocity = Vector2.ZERO 13 move_and_slide() 14 if to_player.length() < 24.0: 15 player.hp -= 1 16 player.get_node("Sprite2D").modulate = Color.RED 17 if player.hp <= 0: 18 get_tree().reload_current_scene()

The Berry Bandit, continued
20 func hit_by_berry() -> void: 21 hp -= 1 22 $Sprite2D.modulate = Color.RED 23 $HurtSound.pitch_scale = randf_range(0.9, 1.1) 24 $HurtSound.play() 25 await get_tree().create_timer(0.15).timeout 26 $Sprite2D.modulate = Color.WHITE 27 if hp <= 0: 28 Game.score += 50 29 get_node("/root/Main/HUD/ScoreLabel").text = "Score: " + str(Game.score) 30 if randf() < 0.25: 31 var fig := preload("res://fig.tscn").instantiate() 32 fig.global_position = global_position 33 get_parent().add_child(fig) 34 queue_free()
Count the reasons this file could change: the chase feels unfair, the flash is too long, the sound designer has a new clip, the HUD moves, figs drop too often. Five reasons, one file.

Boss fight: carve the bandit
12 minutes, in pairs, on the handout:
- Tag every line with its job in the margin (chase, health, flash, ...).
- Group the jobs into parts. Name each part in three words or fewer.
- Draw the new scene tree: the bandit, its parts, and what stays in the brain.
- Wire it: one call down or one signal up for each part.
- Fill the tuning sheet: every magic number gets a name and moves into
BanditStats. - Fast pair? Bugs are hiding in this file. Find two, and say which part would have prevented each.

Trade and defend
4 minutes, two pairs together:
- Swap sheets. Find one border the other pair drew differently.
- Each side defends its cut with one of the three tests.
- Agree, or agree on what you would need to know to decide.
Designs differ. A cut you can defend with a test is a good cut.

Git for Two
One game, two keyboards, zero collisions
Small parts merge clean
- Git merges by file, line by line. Two people editing different files never collide.
- A 900-line
player.gdguarantees you and your teammate are in the same file all week. - Parts change that: you take
dash_component.gd, your teammate takeshealth_bar.gd. - The same is true for scenes. A small scene that is instanced into the main scene is a file one person can own.
Single Responsibility is also a teamwork tool: one job per file means one owner per change.

What goes in the repo
| File | Commit it? | Why |
|---|---|---|
| *.gd, *.tscn, *.tres | Yes | Your game. All three are plain text, so git can diff them. |
| project.godot | Yes | Input map, layer names, main scene. |
| *.import, *.uid | Yes | Import settings and stable IDs. Without them your teammate's scenes lose their references. |
| art, audio | Yes | Keep sources small. Binary files cannot merge, so one owner per asset. |
| .godot/ | Never | The editor's cache, rebuilt on every machine. The lab .gitignore already excludes it. |
Starting your team repo from a lab template gives you the correct .gitignore for free [2].

Scenes are text
[node name="Rival" type="CharacterBody2D"] collision_layer = 4 script = ExtResource("1_rival") [node name="HealthComponent" type="Node" parent="."] script = ExtResource("2_health") max_health = 3
- A
.tscnis a list of nodes and their changed properties. - Git can merge two edits to it, as long as they touch different nodes.
- Two people rearranging the same scene on the same day produce conflicts nobody enjoys reading.
One editor per scene at a time. Say it in your team chat: "I have main.tscn until tonight." Split big scenes into small instanced ones so there is more to go around.

The feature-branch loop
git switch main git pull # start from the latest main git switch -c dash-component # one branch per feature # ...build, run, commit small... git add . git commit -m "Add dash component with cooldown" git push -u origin dash-component # on GitHub: open a pull request, teammate reviews, merge git switch main git pull # both of you, after every merge
- main always runs. Stakeholders clone main at every gate.
- A branch lives a day or two. Long branches drift.
- The pull request is where your teammate reads your code before it lands.
After pulling a change to project.godot: Project menu, then Reload Current Project.

When a conflict hits
| Conflict in | What to do |
|---|---|
| A script (.gd) | Open the file, read both versions between the markers, keep what is right, delete the markers, run the game, commit. |
| A scene or resource (.tscn, .tres) | Pick one side whole (git checkout --theirs path or --ours), then redo the smaller change in the editor. |
| A binary (art, audio) | Pick one side whole. Ask the owner which one. |
Conflicts are a scheduling problem more than a git problem. Pull before you start, commit small, merge daily, and most weeks you will never see one.

Co-op: the team git pact
4 minutes, with your teammate, straight into your proposal's branching plan:
- Branch names: pick a pattern (
feature/dash,fix/spawn). - Who merges: every pull request gets the other teammate's eyes first.
- Scene owners this sprint: list your first three scenes and put a name on each.
- Sync habit: when do you both pull? (Start of every work session is a fine answer.)

Lab 4: Component Kit
Build the parts once, bolt them onto two games
Lab 4: the Component Kit
One Godot project, two tiny games (the lab calls them cabinets), and one shared folder of parts. The Berry Bandit you carved on paper is the first thing you build.
THE SHAPE OF THE LABlab4-component-kit/ ├─ components/ the kit: one copy of every part ├─ cabinet_a/ Bandit Brawl (Blitz, berries, bandits) ├─ cabinet_b/ a second game, different entities └─ stats/ Resource files: the tuning sheets
The kit rule: both cabinets use the same files in components/. A part that needs editing to work in the second cabinet has two jobs.
Solo lab. Due Tuesday, October 20, 9:59 PM.

Milestones
| You build | The idea | |
|---|---|---|
| M0 | Accept, clone, run both cabinets. Things move; nothing can be hurt. | Starting line |
| M1 | HealthComponent, bolted onto Blitz and the bandit. | A part is a child node |
| M2 | HitboxComponent and HurtboxComponent: damage travels between parts. | Signals up, calls down |
| M3 | The second cabinet, using the kit with zero edits in components/. | Reuse |
| M4 | A stats Resource and at least three .tres files. Magic numbers leave the code. | Numbers live in data |
| M5 | Your own part, used by two different entities. | One job, yours |
M5 ideas: flash on hit, drop on death, knockback, a health bar, a lifetime timer. GUIDE.md has the full menu.

How it is graded
| Criterion | Points |
|---|---|
| The kit works in cabinet A: health, hitbox, hurtbox, defeat | 25 |
Reuse: cabinet B runs on the same, unedited components/ files | 20 |
| Rules of parts: one job each, parts own their data, signals up and calls down, no reaching into owners | 15 |
Numbers in data: a stats Resource, at least three .tres files, no magic numbers in scripts | 15 |
| Your own part (M5), used by two different entities | 10 |
| Your Submission in the README: parts list, wiring sketch, tuning sheet, reflection | 15 |
Your handout from today is a first draft of that README section, for a different enemy.

Studio time
Until class ends, pick your lane:
- Lab 3 still open? That comes first. It is due tonight, 9:59 PM.
- Lab 3 pushed? Accept Lab 4, run both cabinets, and start M1 with your handout beside you.
- Team time: set up the git pact in your team repo and make your first feature branch.
Tonight: Lab 3, 9:59 PM. Thursday, October 8: proposal (PDF in Canvas and in your repo), 9:59 PM. Tuesday, October 20: Lab 4, 9:59 PM. Next week: UI systems on Tuesday, agile for game teams on Thursday.

Game Over Screen
What you know now
Checkpoint reached
- Single Responsibility: gather what changes for the same reason, separate what changes for different reasons.
- Three tests find the cut: three words, who asks, lift out.
- Bugs cluster where a script reaches into someone else's job.
- Small files and small scenes are what let two people work on one game. Branch per feature, pull often, main always runs.

Sources
- [1] Robert C. Martin, The Single Responsibility Principle (blog.cleancoder.com, 2014), and Clean Architecture, chapter 7.
- [2] Godot docs: Version control systems (docs.godotengine.org, Best Practices section), today's reading.
- [3] Nystrom, Game Programming Patterns: Component (gameprogrammingpatterns.com/component.html).
- [4] The 20 Games Challenge (20gameschallenge.com), the anchor list for your pitches.

CS 276: Single Responsibility, Git for Teams, and Lab 4
By Lucas Cordova
CS 276: Single Responsibility, Git for Teams, and Lab 4
CS 276 W6R1 "Split Screen": lightning pitches, the Single Responsibility Principle with a paper carving activity, git for teams, and the Lab 4 Component Kit launch.