diff --git a/Autoloads/EventBus.cs b/Autoloads/EventBus.cs
index 5647cec..de97ee8 100644
--- a/Autoloads/EventBus.cs
+++ b/Autoloads/EventBus.cs
@@ -82,6 +82,7 @@ public partial class EventBus : Node
[Signal] public delegate void CoinCollectedEventHandler(int amount, Vector2 position);
[Signal] public delegate void ItemCollectedEventHandler(CollectableType itemType, float amount, Vector2 position);
[Signal] public delegate void ChildRescuedEventHandler(Vector2 position);
+ [Signal] public delegate void SkillCollectedEventHandler(SkillData skill, Vector2 position);
public static void EmitCoinCollected(int amount, Vector2 position)
=> Instance?.EmitSignal(SignalName.CoinCollected, amount, position);
@@ -92,6 +93,9 @@ public partial class EventBus : Node
public static void EmitChildRescued(Vector2 position)
=> Instance?.EmitSignal(SignalName.ChildRescued, position);
+ public static void EmitSkillCollected(SkillData skill, Vector2 position)
+ => Instance?.EmitSignal(SignalName.SkillCollected, skill, position);
+
#endregion
#region Skill Events
diff --git a/objects/entities/double_jump_skill_pickup.tscn b/objects/entities/double_jump_skill_pickup.tscn
new file mode 100644
index 0000000..4ad4764
--- /dev/null
+++ b/objects/entities/double_jump_skill_pickup.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=6 format=3 uid="uid://dk2cu8qs7odib"]
+
+[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_214vd"]
+[ext_resource type="Script" uid="uid://r4jybneigfcn" path="res://scripts/components/CollectableComponent.cs" id="2_h7fi3"]
+[ext_resource type="Script" uid="uid://bjln6jb1sigx2" path="res://scripts/components/FadeAwayComponent.cs" id="3_b687r"]
+[ext_resource type="Resource" path="res://resources/collectables/double_jump_pickup.tres" id="3_h7fi3"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_pickup"]
+radius = 12.0
+
+[node name="SkillPickup" type="Area2D" groups=["Collectables"]]
+collision_layer = 2
+collision_mask = 4
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_pickup")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+texture = ExtResource("1_214vd")
+hframes = 12
+vframes = 12
+frame = 24
+
+[node name="CollectableComponent" type="Node" parent="." node_paths=PackedStringArray("Area2D", "CollisionShape")]
+script = ExtResource("2_h7fi3")
+Area2D = NodePath("..")
+CollisionShape = NodePath("../CollisionShape2D")
+Data = ExtResource("3_h7fi3")
+
+[node name="FadeAwayComponent" type="Node" parent="." node_paths=PackedStringArray("Sprite", "Area")]
+script = ExtResource("3_b687r")
+Sprite = NodePath("../Sprite2D")
+FadeDuration = 0.5
+Area = NodePath("..")
diff --git a/objects/entities/skill_pickup.tscn b/objects/entities/skill_pickup.tscn
new file mode 100644
index 0000000..724bbcb
--- /dev/null
+++ b/objects/entities/skill_pickup.tscn
@@ -0,0 +1,32 @@
+[gd_scene load_steps=5 format=3 uid="uid://0idmnkwids1r"]
+
+[ext_resource type="Texture2D" uid="uid://djifxc5x0dyrw" path="res://sprites/ppc_tileset.png" id="1_sprite"]
+[ext_resource type="Script" uid="uid://r4jybneigfcn" path="res://scripts/components/CollectableComponent.cs" id="2_collectable"]
+[ext_resource type="Script" uid="uid://bjln6jb1sigx2" path="res://scripts/components/FadeAwayComponent.cs" id="3_fadeaway"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_pickup"]
+radius = 12.0
+
+[node name="SkillPickup" type="Area2D" groups=["Collectables"]]
+collision_layer = 2
+collision_mask = 4
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_pickup")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+texture = ExtResource("1_sprite")
+hframes = 12
+vframes = 12
+frame = 24
+
+[node name="CollectableComponent" type="Node" parent="." node_paths=PackedStringArray("Area2D", "CollisionShape")]
+script = ExtResource("2_collectable")
+Area2D = NodePath("..")
+CollisionShape = NodePath("../CollisionShape2D")
+
+[node name="FadeAwayComponent" type="Node" parent="." node_paths=PackedStringArray("Sprite", "Area")]
+script = ExtResource("3_fadeaway")
+Sprite = NodePath("../Sprite2D")
+FadeDuration = 0.5
+Area = NodePath("..")
diff --git a/project.godot b/project.godot
index 61af395..7c0a8ca 100644
--- a/project.godot
+++ b/project.godot
@@ -49,6 +49,7 @@ StatisticsEventHandler="*res://scripts/Events/StatisticsEventHandler.cs"
CoinStateHandler="*res://scripts/Events/CoinStateHandler.cs"
LevelStateHandler="*res://scripts/Events/LevelStateHandler.cs"
LivesStateHandler="*res://scripts/Events/LivesStateHandler.cs"
+SkillCollectHandler="*res://scripts/Events/SkillCollectHandler.cs"
GameStateStore="*res://Autoloads/GameStateStore.cs"
GameManager="*res://objects/game_manager.tscn"
diff --git a/resources/collectables/double_jump_pickup.tres b/resources/collectables/double_jump_pickup.tres
new file mode 100644
index 0000000..b2a9b65
--- /dev/null
+++ b/resources/collectables/double_jump_pickup.tres
@@ -0,0 +1,10 @@
+[gd_resource type="Resource" script_class="CollectableResource" load_steps=3 format=3]
+
+[ext_resource type="Script" path="res://scripts/Resources/CollectableResource.cs" id="1_script"]
+[ext_resource type="Resource" uid="uid://bxsgq8703qx4u" path="res://resources/skills/double_jump.tres" id="2_skill"]
+
+[resource]
+script = ExtResource("1_script")
+Amount = 0.0
+Type = 3
+Skill = ExtResource("2_skill")
diff --git a/scenes/level_village_3.tscn b/scenes/level_village_3.tscn
index da67bb4..323c5ad 100644
--- a/scenes/level_village_3.tscn
+++ b/scenes/level_village_3.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=33 format=4 uid="uid://h60obxmju6mo"]
+[gd_scene load_steps=34 format=4 uid="uid://h60obxmju6mo"]
[ext_resource type="PackedScene" uid="uid://dyp4i4ru2j2jh" path="res://objects/fxs/explosion_fx.tscn" id="1_p30ax"]
[ext_resource type="PackedScene" uid="uid://dx80ivlvuuew4" path="res://objects/fxs/fire_fx.tscn" id="2_a7yjf"]
@@ -23,6 +23,7 @@
[ext_resource type="PackedScene" uid="uid://bqom4cm7r18db" path="res://objects/entities/killzone.tscn" id="21_p30ax"]
[ext_resource type="PackedScene" uid="uid://12jnkdygpxwc" path="res://objects/entities/exit_level.tscn" id="22_a7yjf"]
[ext_resource type="PackedScene" uid="uid://t6h2ra7kjyq" path="res://objects/entities/small_heal_potion.tscn" id="23_m6h4x"]
+[ext_resource type="PackedScene" uid="uid://dk2cu8qs7odib" path="res://objects/entities/double_jump_skill_pickup.tscn" id="24_6fdf4"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_j7bvy"]
texture = ExtResource("7_uvxky")
@@ -300,7 +301,7 @@ z_index = 5
position = Vector2(903, -118)
metadata/_edit_group_ = true
-[node name="HitParticles" parent="Brick Player" index="24"]
+[node name="HitParticles" parent="Brick Player" index="23"]
process_material = SubResource("ParticleProcessMaterial_lgb3u")
[node name="Camera2D" parent="." instance=ExtResource("12_qhkyq")]
@@ -364,6 +365,9 @@ position = Vector2(1359, -42)
[node name="Killzone" parent="." instance=ExtResource("21_p30ax")]
position = Vector2(2456, 815)
+[node name="SkillPickup" parent="." instance=ExtResource("24_6fdf4")]
+position = Vector2(1136, -109)
+
[connection signal="Death" from="Brick Player/HealthComponent" to="UI Layer/DeathScreen" method="OnPlayerDeath"]
[connection signal="Death" from="Brick Player/HealthComponent" to="UI Layer/GameOverScreen" method="OnPlayerDeath"]
diff --git a/scripts/Events/SkillCollectHandler.cs b/scripts/Events/SkillCollectHandler.cs
new file mode 100644
index 0000000..e2dd3b3
--- /dev/null
+++ b/scripts/Events/SkillCollectHandler.cs
@@ -0,0 +1,44 @@
+using Godot;
+using Mr.BrickAdventures.Autoloads;
+using Mr.BrickAdventures.scripts.Resources;
+
+namespace Mr.BrickAdventures.scripts.Events;
+
+///
+/// Handles skill collection events and unlocks skills via GameStateStore.
+/// Skills are immediately activated but only persisted on level complete.
+///
+public partial class SkillCollectHandler : Node
+{
+ private SkillManager _skillManager;
+
+ public override void _Ready()
+ {
+ _skillManager = GetNode(Constants.SkillManagerPath);
+ EventBus.Instance.SkillCollected += OnSkillCollected;
+ }
+
+ public override void _ExitTree()
+ {
+ if (EventBus.Instance != null)
+ {
+ EventBus.Instance.SkillCollected -= OnSkillCollected;
+ }
+ }
+
+ private void OnSkillCollected(SkillData skill, Vector2 position)
+ {
+ if (skill == null) return;
+
+ // Unlock in session (will be committed on level complete, lost on death)
+ GameStateStore.Instance?.UnlockSkillInSession(skill);
+
+ // Immediately activate the skill for the player
+ skill.IsActive = true;
+ skill.Level = 1;
+ _skillManager?.AddSkill(skill);
+
+ // Emit skill unlocked event for UI/achievements
+ EventBus.EmitSkillUnlocked(skill.Name, skill.Level);
+ }
+}
diff --git a/scripts/Events/SkillCollectHandler.cs.uid b/scripts/Events/SkillCollectHandler.cs.uid
new file mode 100644
index 0000000..a9cecdb
--- /dev/null
+++ b/scripts/Events/SkillCollectHandler.cs.uid
@@ -0,0 +1 @@
+uid://c1po4hjvqbslm
diff --git a/scripts/Resources/CollectableResource.cs b/scripts/Resources/CollectableResource.cs
index 96cb03a..6f5bad7 100644
--- a/scripts/Resources/CollectableResource.cs
+++ b/scripts/Resources/CollectableResource.cs
@@ -6,4 +6,9 @@ public partial class CollectableResource : Resource
{
[Export] public float Amount { get; set; } = 0.0f;
[Export] public CollectableType Type { get; set; }
+
+ ///
+ /// The skill to unlock when collected. Only used when Type is Skill.
+ ///
+ [Export] public SkillData Skill { get; set; }
}
\ No newline at end of file
diff --git a/scripts/Resources/CollectableType.cs b/scripts/Resources/CollectableType.cs
index 840b8b9..133195e 100644
--- a/scripts/Resources/CollectableType.cs
+++ b/scripts/Resources/CollectableType.cs
@@ -5,4 +5,5 @@ public enum CollectableType
Coin,
Kid,
Health,
+ Skill,
}
\ No newline at end of file
diff --git a/scripts/components/CollectableComponent.cs b/scripts/components/CollectableComponent.cs
index 418b33d..e4d9535 100644
--- a/scripts/components/CollectableComponent.cs
+++ b/scripts/components/CollectableComponent.cs
@@ -64,6 +64,13 @@ public partial class CollectableComponent : Node
_floatingTextManager?.ShowMessage("Rescued!", ownerNode.GlobalPosition);
EventBus.EmitChildRescued(ownerNode.GlobalPosition);
break;
+ case CollectableType.Skill:
+ if (Data.Skill != null)
+ {
+ _floatingTextManager?.ShowMessage($"{Data.Skill.Name} Unlocked!", ownerNode.GlobalPosition);
+ EventBus.EmitSkillCollected(Data.Skill, ownerNode.GlobalPosition);
+ }
+ break;
default:
EventBus.EmitItemCollected(Data.Type, Data.Amount, ownerNode.GlobalPosition);
break;