diff --git a/project.godot b/project.godot index 5a25486..805b1fe 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ GameState="*res://scripts/autoload/GameState.gd" SaveManager="*res://scripts/autoload/SaveManager.gd" AudioManager="*res://scripts/autoload/AudioManager.gd" InputManager="*res://scripts/autoload/InputManager.gd" +RoomNavigator="*res://scripts/systems/room_navigator.gd" [display] diff --git a/scenes/main/Main.tscn b/scenes/main/Main.tscn index cfd0027..5bcbdc1 100644 --- a/scenes/main/Main.tscn +++ b/scenes/main/Main.tscn @@ -1,23 +1,101 @@ -[gd_scene load_steps=4 format=3 uid="uid://cozypaw_main"] +[gd_scene load_steps=8 format=3 uid="uid://cozypaw_main"] -[ext_resource type="PackedScene" path="res://scenes/rooms/floor0/Reception.tscn" id="1_reception"] -[ext_resource type="PackedScene" path="res://scenes/characters/Character.tscn" id="2_char"] -[ext_resource type="PackedScene" path="res://scenes/ui/HUD.tscn" id="3_hud"] +[ext_resource type="Script" path="res://scripts/main/main.gd" id="1_main"] +[ext_resource type="PackedScene" path="res://scenes/rooms/floor0/Reception.tscn" id="2_reception"] +[ext_resource type="PackedScene" path="res://scenes/characters/Character.tscn" id="3_char"] +[ext_resource type="PackedScene" path="res://scenes/ui/HUD.tscn" id="4_hud"] +[ext_resource type="PackedScene" path="res://scenes/ui/SettingsMenu.tscn" id="5_settings"] +[ext_resource type="PackedScene" path="res://scenes/objects/ElevatorButton.tscn" id="6_elevbtn"] + +[sub_resource type="CharacterData" id="CharacterData_bunny1"] +id = "bunny_01" +display_name = "Bunny" +species = 0 +state = 0 +current_floor = 0 +position = Vector2(0, 0) [node name="Main" type="Node2D"] +script = ExtResource("1_main") + +[node name="Camera2D" type="Camera2D" parent="."] +position = Vector2(640, 360) +zoom = Vector2(1, 1) [node name="Hospital" type="Node2D" parent="."] [node name="Floor0" type="Node2D" parent="Hospital"] - -[node name="Reception" parent="Hospital/Floor0" instance=ExtResource("1_reception")] position = Vector2(0, 0) +[node name="Reception" parent="Hospital/Floor0" instance=ExtResource("2_reception")] +position = Vector2(0, 0) + +[node name="ElevatorUp0" parent="Hospital/Floor0" instance=ExtResource("6_elevbtn")] +position = Vector2(1200, 360) +target_floor = 1 + +[node name="Floor1" type="Node2D" parent="Hospital"] +position = Vector2(0, -720) + +[node name="Background" type="ColorRect" parent="Hospital/Floor1"] +color = Color(0.78, 0.88, 0.96, 1) +size = Vector2(1280, 720) +position = Vector2(0, 0) + +[node name="FloorLabel" type="Label" parent="Hospital/Floor1"] +offset_left = 560.0 +offset_top = 300.0 +offset_right = 720.0 +offset_bottom = 360.0 +text = "1. OG" +theme_override_font_sizes/font_size = 32 + +[node name="FloorRect" type="ColorRect" parent="Hospital/Floor1"] +color = Color(0.88, 0.80, 0.68, 1) +size = Vector2(1280, 100) +position = Vector2(0, 620) + +[node name="ElevatorDown1" parent="Hospital/Floor1" instance=ExtResource("6_elevbtn")] +position = Vector2(1200, 540) +target_floor = 0 + +[node name="ElevatorUp1" parent="Hospital/Floor1" instance=ExtResource("6_elevbtn")] +position = Vector2(1200, 180) +target_floor = 2 + +[node name="Floor2" type="Node2D" parent="Hospital"] +position = Vector2(0, -1440) + +[node name="Background" type="ColorRect" parent="Hospital/Floor2"] +color = Color(0.96, 0.88, 0.96, 1) +size = Vector2(1280, 720) +position = Vector2(0, 0) + +[node name="FloorLabel" type="Label" parent="Hospital/Floor2"] +offset_left = 560.0 +offset_top = 300.0 +offset_right = 720.0 +offset_bottom = 360.0 +text = "2. OG" +theme_override_font_sizes/font_size = 32 + +[node name="FloorRect" type="ColorRect" parent="Hospital/Floor2"] +color = Color(0.88, 0.80, 0.68, 1) +size = Vector2(1280, 100) +position = Vector2(0, 620) + +[node name="ElevatorDown2" parent="Hospital/Floor2" instance=ExtResource("6_elevbtn")] +position = Vector2(1200, 360) +target_floor = 1 + [node name="Characters" type="Node2D" parent="."] -[node name="Bunny1" parent="Characters" instance=ExtResource("2_char")] +[node name="Bunny1" parent="Characters" instance=ExtResource("3_char")] position = Vector2(300, 400) +data = SubResource("CharacterData_bunny1") [node name="UI" type="CanvasLayer" parent="."] -[node name="HUD" parent="UI" instance=ExtResource("3_hud")] +[node name="HUD" parent="UI" instance=ExtResource("4_hud")] + +[node name="SettingsMenu" parent="UI" instance=ExtResource("5_settings")] diff --git a/scenes/objects/ElevatorButton.tscn b/scenes/objects/ElevatorButton.tscn new file mode 100644 index 0000000..6800f6f --- /dev/null +++ b/scenes/objects/ElevatorButton.tscn @@ -0,0 +1,29 @@ +[gd_scene load_steps=3 format=3 uid="uid://cozypaw_elevator_btn"] + +[ext_resource type="Script" path="res://scripts/objects/elevator_button.gd" id="1_elevbtn"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_elevbtn"] +size = Vector2(80, 80) + +[node name="ElevatorButton" type="Node2D"] +script = ExtResource("1_elevbtn") + +[node name="Visual" type="ColorRect" parent="."] +color = Color(0.95, 0.75, 0.3, 1) +size = Vector2(80, 80) +position = Vector2(-40, -40) + +[node name="Label" type="Label" parent="Visual"] +offset_left = 0.0 +offset_top = 0.0 +offset_right = 80.0 +offset_bottom = 80.0 +horizontal_alignment = 1 +vertical_alignment = 1 +text = "▲" + +[node name="CollisionArea" type="Area2D" parent="."] +input_pickable = true + +[node name="CollisionShape" type="CollisionShape2D" parent="CollisionArea"] +shape = SubResource("RectangleShape2D_elevbtn") diff --git a/scenes/ui/HUD.tscn b/scenes/ui/HUD.tscn index f301e41..42a6109 100644 --- a/scenes/ui/HUD.tscn +++ b/scenes/ui/HUD.tscn @@ -31,5 +31,19 @@ offset_bottom = 80.0 text = "♪" flat = false +[node name="SettingsButton" type="Button" parent="."] +anchors_preset = 1 +anchor_left = 1.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 0.0 +offset_left = -160.0 +offset_top = 16.0 +offset_right = -96.0 +offset_bottom = 80.0 +text = "⚙" +flat = false + [connection signal="pressed" from="BackButton" to="." method="_on_back_button_pressed"] [connection signal="pressed" from="MusicToggle" to="." method="_on_music_toggle_pressed"] +[connection signal="pressed" from="SettingsButton" to="." method="_on_settings_button_pressed"] diff --git a/scenes/ui/SettingsMenu.tscn b/scenes/ui/SettingsMenu.tscn new file mode 100644 index 0000000..4f4411d --- /dev/null +++ b/scenes/ui/SettingsMenu.tscn @@ -0,0 +1,103 @@ +[gd_scene load_steps=2 format=3 uid="uid://cozypaw_settings"] + +[ext_resource type="Script" path="res://scripts/systems/settings_menu.gd" id="1_settings"] + +[node name="SettingsMenu" type="CanvasLayer"] +script = ExtResource("1_settings") +visible = false + +[node name="Backdrop" type="ColorRect" parent="."] +color = Color(0, 0, 0, 0.7) +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 0.0 +offset_top = 0.0 +offset_right = 0.0 +offset_bottom = 0.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Panel" type="Panel" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -200.0 +offset_top = -150.0 +offset_right = 200.0 +offset_bottom = 150.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Title" type="Label" parent="Panel"] +offset_left = 0.0 +offset_top = 16.0 +offset_right = 400.0 +offset_bottom = 64.0 +horizontal_alignment = 1 +text = "⚙" +theme_override_font_sizes/font_size = 32 + +[node name="MusicLabel" type="Label" parent="Panel"] +offset_left = 24.0 +offset_top = 76.0 +offset_right = 80.0 +offset_bottom = 108.0 +text = "♪" +theme_override_font_sizes/font_size = 24 + +[node name="MusicSlider" type="HSlider" parent="Panel"] +min_value = 0.0 +max_value = 1.0 +step = 0.05 +value = 0.6 +offset_left = 88.0 +offset_top = 76.0 +offset_right = 376.0 +offset_bottom = 108.0 + +[node name="SfxLabel" type="Label" parent="Panel"] +offset_left = 24.0 +offset_top = 120.0 +offset_right = 80.0 +offset_bottom = 152.0 +text = "🔊" +theme_override_font_sizes/font_size = 24 + +[node name="SfxSlider" type="HSlider" parent="Panel"] +min_value = 0.0 +max_value = 1.0 +step = 0.05 +value = 1.0 +offset_left = 88.0 +offset_top = 120.0 +offset_right = 376.0 +offset_bottom = 152.0 + +[node name="ResetButton" type="Button" parent="Panel"] +offset_left = 24.0 +offset_top = 172.0 +offset_right = 88.0 +offset_bottom = 236.0 +text = "↺" +theme_override_font_sizes/font_size = 28 + +[node name="ResetConfirmDialog" type="ConfirmationDialog" parent="Panel"] +title = "Reset?" +dialog_text = "Spielstand löschen?" + +[node name="CloseButton" type="Button" parent="Panel"] +offset_left = 312.0 +offset_top = 172.0 +offset_right = 376.0 +offset_bottom = 236.0 +text = "✕" +theme_override_font_sizes/font_size = 28 + +[connection signal="value_changed" from="Panel/MusicSlider" to="." method="_on_music_slider_value_changed"] +[connection signal="value_changed" from="Panel/SfxSlider" to="." method="_on_sfx_slider_value_changed"] +[connection signal="pressed" from="Panel/ResetButton" to="." method="_on_reset_button_pressed"] +[connection signal="confirmed" from="Panel/ResetConfirmDialog" to="." method="_on_reset_confirmed"] +[connection signal="pressed" from="Panel/CloseButton" to="." method="_on_close_button_pressed"] diff --git a/scripts/characters/character.gd b/scripts/characters/character.gd index 700854e..15d5b71 100644 --- a/scripts/characters/character.gd +++ b/scripts/characters/character.gd @@ -3,18 +3,55 @@ class_name Character extends Node2D signal character_picked_up(character: Character) signal character_placed(character: Character, position: Vector2) +signal state_changed(new_state: CharacterData.State) @export var character_id: String = "" @export var display_name: String = "" +@export var data: CharacterData var _is_held: bool = false +const _STATE_COLORS: Dictionary = { + CharacterData.State.HEALTHY: Color(0.6, 0.8, 1.0), + CharacterData.State.SICK: Color(0.7, 0.9, 0.7), + CharacterData.State.SLEEPING: Color(0.8, 0.7, 0.95), + CharacterData.State.TIRED: Color(1.0, 0.95, 0.6), + CharacterData.State.PREGNANT: Color(1.0, 0.85, 0.9), + CharacterData.State.BABY: Color(0.9, 0.95, 1.0), +} + func _ready() -> void: var drag: DragDropComponent = get_node_or_null("DragDropComponent") as DragDropComponent if drag != null: drag.drag_picked_up.connect(_on_drag_picked_up) drag.drag_released.connect(_on_drag_released) + if data != null: + _update_visual_state() + + +func set_state(new_state: CharacterData.State) -> void: + if data == null: + return + data.state = new_state + _update_visual_state() + state_changed.emit(new_state) + + +func _update_visual_state() -> void: + if data == null: + return + var visual: ColorRect = get_node_or_null("Visual") as ColorRect + if visual == null: + return + var color: Color = _STATE_COLORS.get(data.state, Color(0.6, 0.8, 1.0)) + visual.color = color + var ear_left: ColorRect = get_node_or_null("Ears/EarLeft") as ColorRect + var ear_right: ColorRect = get_node_or_null("Ears/EarRight") as ColorRect + if ear_left != null: + ear_left.color = color + if ear_right != null: + ear_right.color = color func _on_drag_picked_up(_pos: Vector2) -> void: diff --git a/scripts/characters/character_data.gd b/scripts/characters/character_data.gd new file mode 100644 index 0000000..6b5dba4 --- /dev/null +++ b/scripts/characters/character_data.gd @@ -0,0 +1,12 @@ +## CharacterData — Resource holding all persistent state for one character. +class_name CharacterData extends Resource + +enum State { HEALTHY, SICK, SLEEPING, TIRED, PREGNANT, BABY } +enum Species { BUNNY, KITTEN } + +@export var id: String = "" +@export var display_name: String = "" +@export var species: Species = Species.BUNNY +@export var state: State = State.HEALTHY +@export var current_floor: int = 0 +@export var position: Vector2 = Vector2.ZERO diff --git a/scripts/main/main.gd b/scripts/main/main.gd new file mode 100644 index 0000000..6bd5426 --- /dev/null +++ b/scripts/main/main.gd @@ -0,0 +1,16 @@ +## Main — scene root: wires up RoomNavigator and restores saved character positions. +extends Node2D + + +func _ready() -> void: + RoomNavigator.initialize($Camera2D) + SaveManager.load_game() + _apply_saved_state() + + +func _apply_saved_state() -> void: + for character in $Characters.get_children(): + if character is Character and character.data != null: + var pos: Vector2 = GameState.get_character_position(character.data.id) + if pos != Vector2.ZERO: + character.global_position = pos diff --git a/scripts/objects/elevator_button.gd b/scripts/objects/elevator_button.gd new file mode 100644 index 0000000..dde7cf7 --- /dev/null +++ b/scripts/objects/elevator_button.gd @@ -0,0 +1,30 @@ +## ElevatorButton — tappable button that navigates the camera to a target floor. +class_name ElevatorButton extends Node2D + +@export var target_floor: int = 0 + +var _area: Area2D + + +func _ready() -> void: + _area = get_node_or_null("CollisionArea") as Area2D + if _area != null: + _area.input_event.connect(_on_input_event) + + +func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void: + if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: + _on_pressed() + elif event is InputEventScreenTouch and event.pressed: + _on_pressed() + + +func _on_pressed() -> void: + RoomNavigator.go_to_floor(target_floor) + _play_bounce() + + +func _play_bounce() -> void: + var tween: Tween = create_tween() + tween.tween_property(self, "scale", Vector2(1.15, 1.15), 0.08) + tween.tween_property(self, "scale", Vector2(1.0, 1.0), 0.08) diff --git a/scripts/systems/hud.gd b/scripts/systems/hud.gd index d71d0a9..72f063c 100644 --- a/scripts/systems/hud.gd +++ b/scripts/systems/hud.gd @@ -1,10 +1,11 @@ -## HUD — heads-up display with back button and music toggle. +## HUD — heads-up display with back button, music toggle, and settings access. extends CanvasLayer const MUSIC_ON_SYMBOL: String = "♪" const MUSIC_OFF_SYMBOL: String = "✕" var _music_enabled: bool = true +@onready var _settings_menu: SettingsMenu = get_node_or_null("/root/Main/UI/SettingsMenu") as SettingsMenu func _on_back_button_pressed() -> void: @@ -18,3 +19,8 @@ func _on_music_toggle_pressed() -> void: var btn: Button = get_node_or_null("MusicToggle") as Button if btn != null: btn.text = MUSIC_ON_SYMBOL if _music_enabled else MUSIC_OFF_SYMBOL + + +func _on_settings_button_pressed() -> void: + if _settings_menu != null: + _settings_menu.show_menu() diff --git a/scripts/systems/room_navigator.gd b/scripts/systems/room_navigator.gd new file mode 100644 index 0000000..6f40296 --- /dev/null +++ b/scripts/systems/room_navigator.gd @@ -0,0 +1,30 @@ +## RoomNavigator — autoload that moves the Camera2D smoothly between hospital floors. +extends Node + +signal room_changed(floor_index: int) + +const FLOOR_HEIGHT: float = 720.0 +const CAMERA_TWEEN_DURATION: float = 0.6 + +var _current_floor: int = 0 +var _camera: Camera2D + + +func initialize(camera: Camera2D) -> void: + _camera = camera + + +func go_to_floor(floor_index: int) -> void: + if _camera == null or floor_index == _current_floor: + return + _current_floor = floor_index + var target_y: float = floor_index * -FLOOR_HEIGHT + var tween: Tween = _camera.create_tween() + tween.set_ease(Tween.EASE_IN_OUT) + tween.set_trans(Tween.TRANS_SINE) + tween.tween_property(_camera, "position:y", target_y, CAMERA_TWEEN_DURATION) + room_changed.emit(floor_index) + + +func get_current_floor() -> int: + return _current_floor diff --git a/scripts/systems/settings_menu.gd b/scripts/systems/settings_menu.gd new file mode 100644 index 0000000..749e638 --- /dev/null +++ b/scripts/systems/settings_menu.gd @@ -0,0 +1,37 @@ +## SettingsMenu — overlay panel for audio volume and game reset. +class_name SettingsMenu extends CanvasLayer + + +func _ready() -> void: + visible = false + + +func show_menu() -> void: + visible = true + + +func hide_menu() -> void: + visible = false + + +func _on_music_slider_value_changed(value: float) -> void: + AudioManager.set_music_volume(value) + + +func _on_sfx_slider_value_changed(value: float) -> void: + AudioManager.set_sfx_volume(value) + + +func _on_reset_button_pressed() -> void: + var dialog: ConfirmationDialog = get_node_or_null("Panel/ResetConfirmDialog") as ConfirmationDialog + if dialog != null: + dialog.popup_centered() + + +func _on_reset_confirmed() -> void: + SaveManager.reset_game() + get_tree().reload_current_scene() + + +func _on_close_button_pressed() -> void: + hide_menu()