Files
Cozypaw-Hospital/docs/superpowers/plans/2026-04-17-sprint-14-garten.md
2026-04-17 21:15:07 +02:00

595 lines
16 KiB
Markdown

# Sprint 14 — Zuhause & Garten Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add the garden/home area below the hospital floors, with interactive gift boxes, a tea pot, and camera navigation buttons in both directions.
**Architecture:** The Home area lives at world position `(0, 720)` — directly below Floor 0. `RoomNavigator` gains `go_to_home()` / `go_to_hospital()` which tween the camera to `Vector2(640, 1080)`. A `HomeButton` component with `@export var go_to_garden: bool` drives navigation in both directions. No new autoloads; everything hooks into the existing `RoomNavigator` autoload.
**Tech Stack:** Godot 4.6.2, GDScript (static typing), ColorRect placeholder visuals, Tween animations
---
### Task 1: RoomNavigator — go_to_home / go_to_hospital
**Files:**
- Modify: `scripts/systems/room_navigator.gd`
- [ ] **Step 1: Replace the entire file**
`scripts/systems/room_navigator.gd`:
```gdscript
## RoomNavigator — autoload that moves the Camera2D smoothly between hospital floors, rooms, and the home/garden area.
extends Node
signal room_changed(floor_index: int, room_index: int)
signal home_entered()
signal hospital_entered()
const FLOOR_HEIGHT: float = 720.0
const ROOM_WIDTH: float = 1280.0
const HOME_CAMERA_X: float = 640.0
const HOME_CAMERA_Y: float = 1080.0
const CAMERA_TWEEN_DURATION: float = 0.6
var _current_floor: int = 0
var _current_room: int = 0
var _is_at_home: bool = false
var _camera: Camera2D
func initialize(camera: Camera2D) -> void:
_camera = camera
func go_to_floor(floor_index: int) -> void:
go_to_room(floor_index, 0)
func go_to_room(floor_index: int, room_index: int) -> void:
if _camera == null:
return
if not _is_at_home and floor_index == _current_floor and room_index == _current_room:
return
_is_at_home = false
_current_floor = floor_index
_current_room = room_index
var target_x: float = room_index * ROOM_WIDTH + ROOM_WIDTH * 0.5
var target_y: float = floor_index * -FLOOR_HEIGHT + FLOOR_HEIGHT * 0.5
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.tween_property(_camera, "position", Vector2(target_x, target_y), CAMERA_TWEEN_DURATION)
tween.finished.connect(func() -> void: room_changed.emit(floor_index, room_index))
func go_to_home() -> void:
if _camera == null:
return
if _is_at_home:
return
_is_at_home = true
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.tween_property(_camera, "position", Vector2(HOME_CAMERA_X, HOME_CAMERA_Y), CAMERA_TWEEN_DURATION)
tween.finished.connect(func() -> void: home_entered.emit())
func go_to_hospital() -> void:
if _camera == null:
return
if not _is_at_home:
return
_is_at_home = false
var target_x: float = _current_room * ROOM_WIDTH + ROOM_WIDTH * 0.5
var target_y: float = _current_floor * -FLOOR_HEIGHT + FLOOR_HEIGHT * 0.5
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.tween_property(_camera, "position", Vector2(target_x, target_y), CAMERA_TWEEN_DURATION)
tween.finished.connect(func() -> void: hospital_entered.emit())
func get_current_floor() -> int:
return _current_floor
func get_current_room() -> int:
return _current_room
func is_at_home() -> bool:
return _is_at_home
```
- [ ] **Step 2: Verify no parse errors**
Open Godot, check the Output panel — no errors for `room_navigator.gd`. The autoload entry `RoomNavigator` should still appear in Project → Autoload.
- [ ] **Step 3: Commit**
```bash
git add scripts/systems/room_navigator.gd
git commit -m "feat(navigator): add go_to_home / go_to_hospital with home_entered / hospital_entered signals"
```
---
### Task 2: HomeButton component
**Files:**
- Create: `scripts/objects/home_button.gd`
- Create: `scenes/objects/HomeButton.tscn`
- [ ] **Step 1: Create the script**
`scripts/objects/home_button.gd`:
```gdscript
## HomeButton — tappable button that navigates between the hospital and the garden area.
class_name HomeButton extends Node2D
const BUTTON_HALF_SIZE: float = 32.0
@export var go_to_garden: bool = true
func _input(event: InputEvent) -> void:
if not event is InputEventScreenTouch:
return
if not (event as InputEventScreenTouch).pressed:
return
var touch_pos: Vector2 = (event as InputEventScreenTouch).position
var local: Vector2 = to_local(touch_pos)
if abs(local.x) > BUTTON_HALF_SIZE or abs(local.y) > BUTTON_HALF_SIZE:
return
if go_to_garden:
RoomNavigator.go_to_home()
else:
RoomNavigator.go_to_hospital()
```
- [ ] **Step 2: Create the scene**
`scenes/objects/HomeButton.tscn`:
```
[gd_scene load_steps=2 format=3 uid="uid://cozypaw_homebtn"]
[ext_resource type="Script" path="res://scripts/objects/home_button.gd" id="1_homebtn"]
[node name="HomeButton" type="Node2D"]
script = ExtResource("1_homebtn")
[node name="ButtonBody" type="ColorRect" parent="."]
offset_left = -32.0
offset_top = -32.0
offset_right = 32.0
offset_bottom = 32.0
color = Color(0.36, 0.70, 0.44, 1)
[node name="ButtonIcon" type="ColorRect" parent="."]
offset_left = -12.0
offset_top = -18.0
offset_right = 12.0
offset_bottom = 6.0
color = Color(0.90, 0.88, 0.70, 1)
```
- [ ] **Step 3: Verify in Godot**
Open `HomeButton.tscn` — green square with a cream-coloured rectangle visible. Check Inspector: `go_to_garden` export shows as `true`.
- [ ] **Step 4: Commit**
```bash
git add scripts/objects/home_button.gd scenes/objects/HomeButton.tscn
git commit -m "feat(home-button): add HomeButton component for hospital/garden navigation"
```
---
### Task 3: GiftBox component
**Files:**
- Create: `scripts/objects/gift_box.gd`
- Create: `scenes/objects/GiftBox.tscn`
- [ ] **Step 1: Create the script**
`scripts/objects/gift_box.gd`:
```gdscript
## GiftBox — tap while closed to open: lid rises and fades out, gift inside fades in.
class_name GiftBox extends Node2D
enum State { CLOSED, OPENING, OPEN }
const LID_OPEN_Y: float = -120.0
const OPEN_DURATION: float = 0.5
const GIFT_FADE_DURATION: float = 0.4
const BUTTON_HALF_WIDTH: float = 40.0
const BUTTON_HALF_HEIGHT: float = 50.0
var _state: State = State.CLOSED
func _ready() -> void:
var gift: Node2D = get_node_or_null("Gift") as Node2D
if gift != null:
gift.modulate.a = 0.0
func _input(event: InputEvent) -> void:
if _state != State.CLOSED:
return
if not event is InputEventScreenTouch:
return
if not (event as InputEventScreenTouch).pressed:
return
var touch_pos: Vector2 = (event as InputEventScreenTouch).position
var local: Vector2 = to_local(touch_pos)
if abs(local.x) > BUTTON_HALF_WIDTH or abs(local.y) > BUTTON_HALF_HEIGHT:
return
_start_opening()
func _start_opening() -> void:
_state = State.OPENING
var lid: Node2D = get_node_or_null("Lid") as Node2D
if lid == null:
_state = State.OPEN
return
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_BACK)
tween.tween_property(lid, "position:y", LID_OPEN_Y, OPEN_DURATION)
tween.parallel().tween_property(lid, "modulate:a", 0.0, OPEN_DURATION)
tween.finished.connect(_on_lid_opened)
func _on_lid_opened() -> void:
_state = State.OPEN
var gift: Node2D = get_node_or_null("Gift") as Node2D
if gift == null:
return
var tween: Tween = create_tween()
tween.tween_property(gift, "modulate:a", 1.0, GIFT_FADE_DURATION)
```
- [ ] **Step 2: Create the scene**
`scenes/objects/GiftBox.tscn`:
```
[gd_scene load_steps=2 format=3 uid="uid://cozypaw_giftbox"]
[ext_resource type="Script" path="res://scripts/objects/gift_box.gd" id="1_giftbox"]
[node name="GiftBox" type="Node2D"]
script = ExtResource("1_giftbox")
[node name="BoxBase" type="ColorRect" parent="."]
offset_left = -40.0
offset_top = -60.0
offset_right = 40.0
offset_bottom = 0.0
color = Color(0.90, 0.28, 0.34, 1)
[node name="BoxStripe" type="ColorRect" parent="."]
offset_left = -6.0
offset_top = -60.0
offset_right = 6.0
offset_bottom = 0.0
color = Color(0.98, 0.82, 0.22, 1)
[node name="Lid" type="Node2D" parent="."]
position = Vector2(0, -60)
[node name="LidShape" type="ColorRect" parent="Lid"]
offset_left = -44.0
offset_top = -18.0
offset_right = 44.0
offset_bottom = 0.0
color = Color(0.98, 0.38, 0.42, 1)
[node name="LidBow" type="ColorRect" parent="Lid"]
offset_left = -10.0
offset_top = -28.0
offset_right = 10.0
offset_bottom = -18.0
color = Color(0.98, 0.82, 0.22, 1)
[node name="Gift" type="Node2D" parent="."]
position = Vector2(0, -90)
[node name="GiftShape" type="ColorRect" parent="Gift"]
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 20.0
color = Color(0.96, 0.76, 0.22, 1)
```
- [ ] **Step 3: Verify in Godot**
Open `GiftBox.tscn` — red box body, yellow vertical stripe, red lid above it with yellow bow, yellow star-shaped gift node above lid. The `Gift` node is invisible at runtime (`_ready` sets alpha to 0).
- [ ] **Step 4: Commit**
```bash
git add scripts/objects/gift_box.gd scenes/objects/GiftBox.tscn
git commit -m "feat(gift-box): add GiftBox with tap-to-open lid animation and gift reveal"
```
---
### Task 4: TeaPot component
**Files:**
- Create: `scripts/objects/tea_pot.gd`
- Create: `scenes/objects/TeaPot.tscn`
- [ ] **Step 1: Create the script**
`scripts/objects/tea_pot.gd`:
```gdscript
## TeaPot — tap to pour: tilts 45 degrees, holds briefly, then returns to upright.
class_name TeaPot extends Node2D
enum State { IDLE, POURING }
const TILT_ANGLE: float = 45.0
const TILT_DURATION: float = 0.4
const POUR_HOLD: float = 0.8
const RETURN_DURATION: float = 0.4
const BUTTON_HALF_SIZE: float = 40.0
var _state: State = State.IDLE
func _input(event: InputEvent) -> void:
if _state != State.IDLE:
return
if not event is InputEventScreenTouch:
return
if not (event as InputEventScreenTouch).pressed:
return
var touch_pos: Vector2 = (event as InputEventScreenTouch).position
var local: Vector2 = to_local(touch_pos)
if abs(local.x) > BUTTON_HALF_SIZE or abs(local.y) > BUTTON_HALF_SIZE:
return
_start_pouring()
func _start_pouring() -> void:
_state = State.POURING
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.tween_property(self, "rotation_degrees", TILT_ANGLE, TILT_DURATION)
tween.tween_interval(POUR_HOLD)
tween.tween_property(self, "rotation_degrees", 0.0, RETURN_DURATION)
tween.finished.connect(func() -> void: _state = State.IDLE)
```
- [ ] **Step 2: Create the scene**
`scenes/objects/TeaPot.tscn`:
```
[gd_scene load_steps=2 format=3 uid="uid://cozypaw_teapot"]
[ext_resource type="Script" path="res://scripts/objects/tea_pot.gd" id="1_teapot"]
[node name="TeaPot" type="Node2D"]
script = ExtResource("1_teapot")
[node name="PotBody" type="ColorRect" parent="."]
offset_left = -30.0
offset_top = -50.0
offset_right = 30.0
offset_bottom = 0.0
color = Color(0.96, 0.92, 0.84, 1)
[node name="PotLid" type="ColorRect" parent="."]
offset_left = -28.0
offset_top = -58.0
offset_right = 28.0
offset_bottom = -48.0
color = Color(0.74, 0.62, 0.50, 1)
[node name="PotSpout" type="ColorRect" parent="."]
offset_left = 26.0
offset_top = -38.0
offset_right = 46.0
offset_bottom = -22.0
color = Color(0.96, 0.92, 0.84, 1)
[node name="PotHandle" type="ColorRect" parent="."]
offset_left = -48.0
offset_top = -36.0
offset_right = -36.0
offset_bottom = -14.0
color = Color(0.96, 0.92, 0.84, 1)
```
- [ ] **Step 3: Verify in Godot**
Open `TeaPot.tscn` — cream-coloured pot body, brown lid on top, spout on right, handle on left all visible.
- [ ] **Step 4: Commit**
```bash
git add scripts/objects/tea_pot.gd scenes/objects/TeaPot.tscn
git commit -m "feat(teapot): add TeaPot with tap-to-pour tilt animation"
```
---
### Task 5: GardenParty scene
**Files:**
- Create: `scenes/rooms/home/GardenParty.tscn`
- [ ] **Step 1: Create the scene file**
`scenes/rooms/home/GardenParty.tscn`:
```
[gd_scene load_steps=4 format=3 uid="uid://cozypaw_gardenparty"]
[ext_resource type="PackedScene" path="res://scenes/objects/GiftBox.tscn" id="1_giftbox"]
[ext_resource type="PackedScene" path="res://scenes/objects/TeaPot.tscn" id="2_teapot"]
[ext_resource type="PackedScene" path="res://scenes/objects/HomeButton.tscn" id="3_homebtn"]
[node name="GardenParty" type="Node2D"]
[node name="Sky" type="ColorRect" parent="."]
offset_left = 0.0
offset_top = 0.0
offset_right = 1280.0
offset_bottom = 400.0
color = Color(0.53, 0.81, 0.98, 1)
[node name="Grass" type="ColorRect" parent="."]
offset_left = 0.0
offset_top = 400.0
offset_right = 1280.0
offset_bottom = 720.0
color = Color(0.55, 0.76, 0.46, 1)
[node name="GroundEdge" type="ColorRect" parent="."]
offset_left = 0.0
offset_top = 392.0
offset_right = 1280.0
offset_bottom = 404.0
color = Color(0.38, 0.62, 0.32, 1)
[node name="TableTop" type="ColorRect" parent="."]
offset_left = 440.0
offset_top = 464.0
offset_right = 840.0
offset_bottom = 480.0
color = Color(0.82, 0.66, 0.46, 1)
[node name="TableLeg1" type="ColorRect" parent="."]
offset_left = 456.0
offset_top = 480.0
offset_right = 472.0
offset_bottom = 580.0
color = Color(0.70, 0.52, 0.34, 1)
[node name="TableLeg2" type="ColorRect" parent="."]
offset_left = 808.0
offset_top = 480.0
offset_right = 824.0
offset_bottom = 580.0
color = Color(0.70, 0.52, 0.34, 1)
[node name="TeaPot" parent="." instance=ExtResource("2_teapot")]
position = Vector2(510, 464)
[node name="GiftBox1" parent="." instance=ExtResource("1_giftbox")]
position = Vector2(640, 464)
[node name="GiftBox2" parent="." instance=ExtResource("1_giftbox")]
position = Vector2(730, 464)
[node name="GiftBox3" parent="." instance=ExtResource("1_giftbox")]
position = Vector2(820, 464)
[node name="TeaCup" type="ColorRect" parent="."]
offset_left = 558.0
offset_top = 440.0
offset_right = 590.0
offset_bottom = 464.0
color = Color(0.96, 0.92, 0.84, 1)
[node name="Balloon1" type="ColorRect" parent="."]
offset_left = 180.0
offset_top = 120.0
offset_right = 220.0
offset_bottom = 180.0
color = Color(0.96, 0.44, 0.44, 1)
[node name="Balloon2" type="ColorRect" parent="."]
offset_left = 1020.0
offset_top = 100.0
offset_right = 1060.0
offset_bottom = 160.0
color = Color(0.56, 0.76, 0.96, 1)
[node name="HomeButtonReturn" parent="." instance=ExtResource("3_homebtn")]
position = Vector2(100, 620)
go_to_garden = false
```
- [ ] **Step 2: Verify in Godot**
Open `GardenParty.tscn` — blue sky, green grass, dark ground edge, brown table with two legs. TeaPot and 3 GiftBoxes sitting on the table (top at y=464). TeaCup to the left of the pots. Two balloons (red, blue). Green HomeButton bottom-left with `go_to_garden = false` in inspector.
- [ ] **Step 3: Commit**
```bash
git add scenes/rooms/home/GardenParty.tscn
git commit -m "feat(garden): add GardenParty scene with table, gifts, teapot, balloons, and return button"
```
---
### Task 6: Main.tscn — Home integration
**Files:**
- Modify: `scenes/main/Main.tscn`
- [ ] **Step 1: Update load_steps**
Replace:
```
[gd_scene load_steps=20 format=3 uid="uid://cozypaw_main"]
```
With:
```
[gd_scene load_steps=22 format=3 uid="uid://cozypaw_main"]
```
- [ ] **Step 2: Add ext_resources after the last existing one (id="18_nursery")**
```
[ext_resource type="PackedScene" path="res://scenes/rooms/home/GardenParty.tscn" id="19_gardenparty"]
[ext_resource type="PackedScene" path="res://scenes/objects/HomeButton.tscn" id="20_homebtn"]
```
- [ ] **Step 3: Add HomeButtonToGarden on Floor0**
After the last Floor0 nav arrow node (`NavLeft3to2`), add:
```
[node name="HomeButtonToGarden" parent="Hospital/Floor0" instance=ExtResource("20_homebtn")]
position = Vector2(640, 620)
go_to_garden = true
```
- [ ] **Step 4: Add Home node and GardenParty**
After the last Floor2 node (`NavLeft2to1_F2`) and before `[node name="Characters" ...`, add:
```
[node name="Home" type="Node2D" parent="."]
position = Vector2(0, 720)
[node name="GardenParty" parent="Home" instance=ExtResource("19_gardenparty")]
position = Vector2(0, 0)
```
- [ ] **Step 5: Verify in Godot**
Run the main scene:
1. Scene loads without errors
2. Floor0 shows a green HomeButton square at the bottom centre (x=640, y=620)
3. Tap the HomeButtonToGarden → camera slides smoothly down to garden (blue sky + green grass)
4. Tap HomeButtonReturn in the garden → camera slides back up to Floor0/Reception
5. Elevator buttons between floors 0/1/2 still work correctly
6. Nav arrows between rooms still work on all three floors
- [ ] **Step 6: Commit**
```bash
git add scenes/main/Main.tscn
git commit -m "feat(main): integrate Home/GardenParty below Floor0 with bidirectional navigation"
```