Add collectable types and implement level unlocking mechanics; enhance player state management

This commit is contained in:
2025-05-03 07:03:44 +02:00
parent 4e5427e731
commit c58cc7da88
19 changed files with 255 additions and 24 deletions

View File

@@ -45,7 +45,7 @@ texture = ExtResource("4_vq1oq")
position = Vector2(4, -18) position = Vector2(4, -18)
texture = ExtResource("5_m7x6t") texture = ExtResource("5_m7x6t")
[node name="CollectableComponent" type="Node" parent="." node_paths=PackedStringArray("area2d", "sfx")] [node name="Collectable" type="Node" parent="." node_paths=PackedStringArray("area2d", "sfx")]
script = ExtResource("5_wc3ym") script = ExtResource("5_wc3ym")
area2d = NodePath("..") area2d = NodePath("..")
collectable_data = ExtResource("6_vmvuo") collectable_data = ExtResource("6_vmvuo")

43
objects/exit_level.tscn Normal file
View File

@@ -0,0 +1,43 @@
[gd_scene load_steps=6 format=3 uid="uid://12jnkdygpxwc"]
[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_agxwm"]
[ext_resource type="Script" uid="uid://cmh8k0rdsyh7j" path="res://scripts/components/requirement_component.gd" id="2_iysc1"]
[ext_resource type="Script" uid="uid://c8xhgkg8gcqu6" path="res://scripts/components/unlock_on_requirement_component.gd" id="3_5ktpq"]
[ext_resource type="Script" uid="uid://bwamqffvpa452" path="res://scripts/components/exit_door_component.gd" id="4_4jgt0"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_yfu6m"]
size = Vector2(28, 32)
[node name="ExitLevel" type="Area2D"]
collision_layer = 0
collision_mask = 4
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(0, -8)
scale = Vector2(2, 2)
texture = ExtResource("1_agxwm")
hframes = 12
vframes = 12
frame = 54
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -8)
shape = SubResource("RectangleShape2D_yfu6m")
[node name="RequirementComponent" type="Node" parent="."]
script = ExtResource("2_iysc1")
requirement_type = 1
metadata/_custom_type_script = "uid://cmh8k0rdsyh7j"
[node name="UnlockOnRequirementComponent" type="Node" parent="." node_paths=PackedStringArray("requirement_component", "unlock_target")]
script = ExtResource("3_5ktpq")
requirement_component = NodePath("../RequirementComponent")
unlock_target = NodePath("../ExitDoorComponent")
metadata/_custom_type_script = "uid://c8xhgkg8gcqu6"
[node name="ExitDoorComponent" type="Node" parent="." node_paths=PackedStringArray("exit_area", "door_sprite")]
script = ExtResource("4_4jgt0")
exit_area = NodePath("..")
door_sprite = NodePath("../Sprite2D")
opened_door_frame = 88
metadata/_custom_type_script = "uid://bwamqffvpa452"

View File

@@ -1,6 +1,15 @@
[gd_scene load_steps=2 format=3 uid="uid://b4eifkc31jsun"] [gd_scene load_steps=3 format=3 uid="uid://b4eifkc31jsun"]
[ext_resource type="Script" uid="uid://dd30bgqiagi25" path="res://scripts/game_manager.gd" id="1_58t7u"] [ext_resource type="Script" uid="uid://dd30bgqiagi25" path="res://scripts/game_manager.gd" id="1_58t7u"]
[ext_resource type="PackedScene" uid="uid://h60obxmju6mo" path="res://scenes/test.tscn" id="2_bentb"]
[node name="GameManager" type="Node"] [node name="GameManager" type="Node"]
script = ExtResource("1_58t7u") script = ExtResource("1_58t7u")
level_scenes = Array[PackedScene]([ExtResource("2_bentb")])
player_state = {
"coins": 0,
"current_level": 0,
"lives": 3,
"unlocked_levels": [ExtResource("2_bentb")],
"unlocked_skills": []
}

View File

@@ -4,7 +4,7 @@
[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_yy7uq"] [ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_yy7uq"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hkd8b"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_hkd8b"]
size = Vector2(12, 17) size = Vector2(38, 22)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lqw6h"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lqw6h"]
bg_color = Color(0, 0, 0, 1) bg_color = Color(0, 0, 0, 1)
@@ -18,7 +18,7 @@ ui_root = NodePath("Panel")
tooltip_label = NodePath("Panel/PanelContainer/Label") tooltip_label = NodePath("Panel/PanelContainer/Label")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -0.5) position = Vector2(1, -3)
shape = SubResource("RectangleShape2D_hkd8b") shape = SubResource("RectangleShape2D_hkd8b")
[node name="Sprite2D" type="Sprite2D" parent="."] [node name="Sprite2D" type="Sprite2D" parent="."]

View File

@@ -5,3 +5,4 @@
[resource] [resource]
script = ExtResource("1_2d5tb") script = ExtResource("1_2d5tb")
amount = 1 amount = 1
type = 1

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,7 @@ var has_fade_away: bool = false
@export var area2d: Area2D @export var area2d: Area2D
@export var collectable_data: CollectableResource @export var collectable_data: CollectableResource
@export var sfx: AudioStreamPlayer2D @export var sfx: AudioStreamPlayer2D
signal collected(amount: int) signal collected(amount: int, type: CollectableResource.CollectableType)
func _ready() -> void: func _ready() -> void:
@@ -24,7 +24,7 @@ func _ready() -> void:
func _on_area2d_body_entered(body: Node2D) -> void: func _on_area2d_body_entered(body: Node2D) -> void:
if body.has_node("CanPickUpComponent"): if body.has_node("CanPickUpComponent"):
collected.emit(collectable_data.amount) collected.emit(collectable_data.amount, collectable_data.type)
if sfx: if sfx:
sfx.play() sfx.play()
if not has_fade_away: if not has_fade_away:

View File

@@ -0,0 +1,34 @@
class_name ExitDoorComponent
extends Node
@export var locked: bool = true
@export var exit_area: Area2D
@export var door_sprite: Sprite2D
@export var opened_door_sfx: AudioStreamPlayer2D
@export var opened_door_frame: int = 0
signal exit_triggered
@onready var gm: GM = $"/root/GameManager"
func _ready() -> void:
if not exit_area:
printerr("ExitDoorComponent: exit_area is not set.")
return
exit_area.body_entered.connect(on_exit_area_body_entered)
func unlock() -> void:
locked = false
if door_sprite:
door_sprite.frame = opened_door_frame
if opened_door_sfx:
opened_door_sfx.play()
func on_exit_area_body_entered(_body: Node2D) -> void:
if locked:
return
exit_triggered.emit()
gm.try_to_go_to_next_level()

View File

@@ -0,0 +1 @@
uid://bwamqffvpa452

View File

@@ -24,5 +24,6 @@ func _on_health_component_on_death() -> void:
gm.reset_player_state() gm.reset_player_state()
else: else:
gm.remove_lives(1) gm.remove_lives(1)
gm.set_coins(0)
call_deferred("reset_scene") call_deferred("reset_scene")

View File

@@ -0,0 +1,32 @@
class_name RequirementComponent
extends Node
@export var requirement_type: CollectableResource.CollectableType
@export var required_amount: int = 1
var current_amount: int = 0
signal requirement_met(requirement_type: CollectableResource.CollectableType)
@onready var gm: GM = $"/root/GameManager"
func _ready() -> void:
if not gm:
printerr("RequirementComponent: GameManager not found.")
return
var collectables: Array[CollectableComponent] = gm.get_colllectable_nodes()
for collectable in collectables:
collectable.collected.connect(on_collected)
func on_collected(amount: int, type: CollectableResource.CollectableType) -> void:
print("Collected: ", amount, " of type: ", str(type))
if type != requirement_type:
return
add_progress(amount)
func add_progress(amount: int = 1) -> void:
current_amount += amount
if current_amount >= required_amount:
requirement_met.emit(requirement_type)

View File

@@ -0,0 +1 @@
uid://cmh8k0rdsyh7j

View File

@@ -3,6 +3,7 @@ extends Node
@onready var game_manager: GM = $"/root/GameManager" @onready var game_manager: GM = $"/root/GameManager"
func _ready(): func _ready():
await get_tree().process_frame await get_tree().process_frame
var coins = get_tree().get_nodes_in_group("coins") var coins = get_tree().get_nodes_in_group("coins")
@@ -10,10 +11,10 @@ func _ready():
for coin in coins: for coin in coins:
coin.connect("collected", on_collected) coin.connect("collected", on_collected)
func on_collected(amount: int) -> void:
func on_collected(amount: int, type: CollectableResource.CollectableType) -> void:
if not game_manager: if not game_manager:
return return
if type != CollectableResource.CollectableType.COIN:
return
game_manager.add_coins(amount) game_manager.add_coins(amount)
# todo: play sound
# todo: update ui

View File

@@ -0,0 +1,26 @@
class_name UnlockOnRequirementComponent
extends Node
@export var requirement_component: RequirementComponent
@export var unlock_target: Node
func _ready() -> void:
if not requirement_component:
printerr("UnlockOnRequirementComponent: requirement_component is not set.")
return
if not unlock_target:
printerr("UnlockOnRequirementComponent: unlock_target is not set.")
return
requirement_component.requirement_met.connect(on_requirement_met)
func on_requirement_met(requirement_type: CollectableResource.CollectableType) -> void:
if requirement_type == requirement_component.requirement_type:
if unlock_target.has_method("unlock"):
unlock_target.unlock()
else:
printerr("UnlockOnRequirementComponent: unlock_target does not have an unlock method.")

View File

@@ -0,0 +1 @@
uid://c8xhgkg8gcqu6

View File

@@ -1,12 +1,69 @@
class_name GM class_name GM
extends Node extends Node
var player_state = { @export var level_scenes: Array[PackedScene]
@export var player_state := {
"coins": 0, "coins": 0,
"lives": 3, "lives": 3,
"unlocked_skills": [], "unlocked_skills": [],
"current_level": 0,
"unlocked_levels": [],
} }
var nodes_in_scene := []
func _enter_tree() -> void:
get_tree().node_added.connect(on_node_added)
get_tree().node_removed.connect(on_node_removed)
func on_node_added(node: Node) -> void:
nodes_in_scene.append(node)
func on_node_removed(node: Node) -> void:
if node in nodes_in_scene:
nodes_in_scene.erase(node)
func get_colllectable_nodes() -> Array[CollectableComponent]:
var collectable_nodes: Array[CollectableComponent] = []
for node in nodes_in_scene:
var collectable_component: CollectableComponent = node.get_node_or_null("Collectable")
if not collectable_component:
collectable_component = node.get_node_or_null("CollectableComponent")
if not collectable_component:
continue
if collectable_component.collectable_data.type == CollectableResource.CollectableType.KID:
print("Kid collectable found: ", collectable_component.get_parent().name)
collectable_nodes.append(collectable_component)
return collectable_nodes
func get_coin_nodes() -> Array[CollectableComponent]:
var coin_nodes := []
for node in nodes_in_scene:
var collectable_component: CollectableComponent = node.get_node_or_null("Collectable")
if not collectable_component:
continue
if collectable_component.collectable_data.type == CollectableResource.CollectableType.COIN:
coin_nodes.append(collectable_component)
return coin_nodes
func get_kid_nodes() -> Array[CollectableComponent]:
var kid_nodes := []
for node in nodes_in_scene:
var collectable_component: CollectableComponent = node.get_node_or_null("Collectable")
if not collectable_component:
continue
if collectable_component.collectable_data.type == CollectableResource.CollectableType.KID:
kid_nodes.append(collectable_component)
return kid_nodes
func add_coins(amount: int) -> void: func add_coins(amount: int) -> void:
player_state["coins"] += amount player_state["coins"] += amount
@@ -60,3 +117,15 @@ func reset_player_state() -> void:
"lives": 3, "lives": 3,
"unlocked_skills": [], "unlocked_skills": [],
} }
func unlock_level(level_index: int) -> void:
if level_index not in player_state["unlocked_levels"]: player_state["unlocked_levels"].append(level_index)
func try_to_go_to_next_level() -> void:
if player_state["current_level"] + 1 < level_scenes.size() and player_state["current_level"] + 1 in player_state["unlocked_levels"]:
player_state["current_level"] += 1
print("Going to next level: ", player_state["current_level"])
else:
print("No more levels to go to.")

View File

@@ -1,4 +1,10 @@
class_name CollectableResource class_name CollectableResource
extends Resource extends Resource
enum CollectableType {
COIN,
KID,
HEALTH,
}
@export var amount: int = 0 @export var amount: int = 0
@export var type: CollectableType = CollectableType.COIN

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -785,6 +785,7 @@ texture = ExtResource("2_k3fip")
0:11/0 = 0 0:11/0 = 0
0:11/0/terrain_set = 0 0:11/0/terrain_set = 0
0:11/0/terrain = 1 0:11/0/terrain = 1
0:11/0/probability = 0.2
0:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 0:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
0:11/0/terrains_peering_bit/right_side = 1 0:11/0/terrains_peering_bit/right_side = 1
0:11/0/terrains_peering_bit/bottom_right_corner = 1 0:11/0/terrains_peering_bit/bottom_right_corner = 1
@@ -797,6 +798,7 @@ texture = ExtResource("2_k3fip")
1:11/0 = 0 1:11/0 = 0
1:11/0/terrain_set = 0 1:11/0/terrain_set = 0
1:11/0/terrain = 1 1:11/0/terrain = 1
1:11/0/probability = 0.2
1:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 1:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
1:11/0/terrains_peering_bit/right_side = 1 1:11/0/terrains_peering_bit/right_side = 1
1:11/0/terrains_peering_bit/bottom_right_corner = 1 1:11/0/terrains_peering_bit/bottom_right_corner = 1
@@ -809,6 +811,7 @@ texture = ExtResource("2_k3fip")
2:11/0 = 0 2:11/0 = 0
2:11/0/terrain_set = 0 2:11/0/terrain_set = 0
2:11/0/terrain = 1 2:11/0/terrain = 1
2:11/0/probability = 0.2
2:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 2:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
2:11/0/terrains_peering_bit/right_side = 1 2:11/0/terrains_peering_bit/right_side = 1
2:11/0/terrains_peering_bit/bottom_right_corner = 1 2:11/0/terrains_peering_bit/bottom_right_corner = 1
@@ -821,6 +824,7 @@ texture = ExtResource("2_k3fip")
3:11/0 = 0 3:11/0 = 0
3:11/0/terrain_set = 0 3:11/0/terrain_set = 0
3:11/0/terrain = 1 3:11/0/terrain = 1
3:11/0/probability = 0.2
3:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8) 3:11/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
3:11/0/terrains_peering_bit/right_side = 1 3:11/0/terrains_peering_bit/right_side = 1
3:11/0/terrains_peering_bit/bottom_right_corner = 1 3:11/0/terrains_peering_bit/bottom_right_corner = 1