diff --git a/Mr. Brick Adventures.sln.DotSettings.user b/Mr. Brick Adventures.sln.DotSettings.user
index 2958c8e..4a83f05 100644
--- a/Mr. Brick Adventures.sln.DotSettings.user
+++ b/Mr. Brick Adventures.sln.DotSettings.user
@@ -2,6 +2,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
diff --git a/objects/entities/brick_player.tscn b/objects/entities/brick_player.tscn
index 763270d..fca6b63 100644
--- a/objects/entities/brick_player.tscn
+++ b/objects/entities/brick_player.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=51 format=3 uid="uid://bqi5s710xb1ju"]
+[gd_scene load_steps=54 format=3 uid="uid://bqi5s710xb1ju"]
[ext_resource type="Script" uid="uid://csel4s0e4g5uf" path="res://scripts/components/PlayerController.cs" id="1_yysbb"]
[ext_resource type="Shader" uid="uid://bs4xvm4qkurpr" path="res://shaders/hit_flash.tres" id="2_lgb3u"]
@@ -16,6 +16,7 @@
[ext_resource type="PackedScene" uid="uid://chjbi5mgtwhsh" path="res://objects/movement_abilities/wall_jump_ability.tscn" id="7_bnap0"]
[ext_resource type="Script" uid="uid://ck6kmnbwhsttt" path="res://scripts/components/Movement/OneWayPlatformAbility.cs" id="7_uno3u"]
[ext_resource type="Texture2D" uid="uid://dhkwyv6ayb5qb" path="res://sprites/flying_ship.png" id="8_6lsog"]
+[ext_resource type="PackedScene" uid="uid://dre1vit1m4d2n" path="res://objects/movement_abilities/grid_movement_ability.tscn" id="8_xuhvf"]
[ext_resource type="Script" uid="uid://dy78ak8eykw6e" path="res://scripts/components/FlipComponent.cs" id="9_yysbb"]
[ext_resource type="Script" uid="uid://mnjg3p0aw1ow" path="res://scripts/components/CanPickUpComponent.cs" id="10_yysbb"]
[ext_resource type="Script" uid="uid://ccqb8kd5m0eh7" path="res://scripts/components/ScoreComponent.cs" id="11_o1ihh"]
@@ -38,7 +39,9 @@
[ext_resource type="PackedScene" uid="uid://dtem8jgcyoqar" path="res://objects/entities/green_laser.tscn" id="36_oxudy"]
[ext_resource type="Script" uid="uid://dupnaark1f7gm" path="res://scripts/components/ProgressiveDamageComponent.cs" id="38_dhjci"]
[ext_resource type="Script" uid="uid://dssa2taiwktis" path="res://scripts/components/Movement/PlayerInputHandler.cs" id="42_e5pae"]
+[ext_resource type="Script" uid="uid://c00siqtssccr6" path="res://scripts/components/PacXonGridInteractor.cs" id="42_xuhvf"]
[ext_resource type="Script" uid="uid://ceoxet1nqws8w" path="res://scripts/components/SpriteTilterComponent.cs" id="43_xuhvf"]
+[ext_resource type="Script" uid="uid://cmk4m7mplqnrm" path="res://scripts/components/PacXonTrailComponent.cs" id="44_uno3u"]
[ext_resource type="Script" uid="uid://b1h8r5irryxcx" path="res://scripts/components/PlayerSfxComponent.cs" id="49_qec3q"]
[ext_resource type="Script" uid="uid://b2aanqykvdnev" path="res://scripts/components/PlayerGraphicsComponent.cs" id="50_dhjci"]
@@ -97,6 +100,7 @@ GravityScene = ExtResource("4_qec3q")
OneWayPlatformScene = ExtResource("5_dhjci")
SpaceshipMovementScene = ExtResource("6_721q0")
WallJumpScene = ExtResource("7_bnap0")
+GridMovementScene = ExtResource("8_xuhvf")
metadata/_custom_type_script = "uid://csel4s0e4g5uf"
[node name="Movements" type="Node" parent="."]
@@ -296,3 +300,11 @@ script = ExtResource("38_dhjci")
HealthComponent = NodePath("../HealthComponent")
Sprite = NodePath("../Graphics/Root/Base")
metadata/_custom_type_script = "uid://dupnaark1f7gm"
+
+[node name="PacXonGridInteractor" type="Node" parent="."]
+script = ExtResource("42_xuhvf")
+metadata/_custom_type_script = "uid://c00siqtssccr6"
+
+[node name="PacXonTrailComponent" type="Line2D" parent="."]
+script = ExtResource("44_uno3u")
+metadata/_custom_type_script = "uid://cmk4m7mplqnrm"
diff --git a/objects/entities/ghost.tscn b/objects/entities/ghost.tscn
new file mode 100644
index 0000000..9554146
--- /dev/null
+++ b/objects/entities/ghost.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=5 format=3 uid="uid://b3877xt5upsj2"]
+
+[ext_resource type="Texture2D" uid="uid://dpbpjffbdbovp" path="res://sprites/cap.png" id="1_ksysq"]
+[ext_resource type="Script" uid="uid://7i20oc4cyabl" path="res://scripts/components/GhostMovementComponent.cs" id="2_0qila"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_0xbgb"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_ksysq"]
+
+[node name="Ghost" type="CharacterBody2D"]
+collision_layer = 8
+collision_mask = 5
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("CircleShape2D_0xbgb")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+position = Vector2(2.98023e-08, 0)
+scale = Vector2(0.609375, 0.78125)
+texture = ExtResource("1_ksysq")
+
+[node name="GhostMovementComponent" type="Node2D" parent="."]
+script = ExtResource("2_0qila")
+metadata/_custom_type_script = "uid://7i20oc4cyabl"
+
+[node name="Area2D" type="Area2D" parent="."]
+collision_layer = 0
+collision_mask = 4
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
+shape = SubResource("CircleShape2D_ksysq")
diff --git a/objects/movement_abilities/grid_movement_ability.tscn b/objects/movement_abilities/grid_movement_ability.tscn
new file mode 100644
index 0000000..53264c2
--- /dev/null
+++ b/objects/movement_abilities/grid_movement_ability.tscn
@@ -0,0 +1,7 @@
+[gd_scene load_steps=2 format=3 uid="uid://dre1vit1m4d2n"]
+
+[ext_resource type="Script" uid="uid://ctm5glmeu502l" path="res://scripts/components/Movement/GridMovementAbility.cs" id="1_xeoy8"]
+
+[node name="GridMovementAbility" type="Node"]
+script = ExtResource("1_xeoy8")
+metadata/_custom_type_script = "uid://ctm5glmeu502l"
diff --git a/objects/ui/main_menu.tscn b/objects/ui/main_menu.tscn
index 293bfae..d52cf9a 100644
--- a/objects/ui/main_menu.tscn
+++ b/objects/ui/main_menu.tscn
@@ -12,6 +12,8 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
+focus_neighbor_bottom = NodePath("PanelContainer/MarginContainer/VBoxContainer/ContinueButton")
+focus_next = NodePath("PanelContainer/MarginContainer/VBoxContainer/ContinueButton")
script = ExtResource("1_epxpl")
MainMenuControl = NodePath(".")
NewGameButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/NewGameButton")
@@ -54,26 +56,42 @@ size_flags_vertical = 3
[node name="ContinueButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
+focus_neighbor_bottom = NodePath("../NewGameButton")
+focus_next = NodePath("../NewGameButton")
text = "CONTINUE_BUTTON"
flat = true
[node name="NewGameButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
+focus_neighbor_top = NodePath("../ContinueButton")
+focus_neighbor_bottom = NodePath("../SettingsButton")
+focus_next = NodePath("../SettingsButton")
+focus_previous = NodePath("../ContinueButton")
text = "NEW_GAME_BUTTON"
flat = true
[node name="SettingsButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
+focus_neighbor_top = NodePath("../NewGameButton")
+focus_neighbor_bottom = NodePath("../CreditsButton")
+focus_next = NodePath("../CreditsButton")
+focus_previous = NodePath("../NewGameButton")
text = "SETTINGS_BUTTON"
flat = true
[node name="CreditsButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
+focus_neighbor_top = NodePath("../SettingsButton")
+focus_neighbor_bottom = NodePath("../QuitButton")
+focus_next = NodePath("../QuitButton")
+focus_previous = NodePath("../SettingsButton")
text = "CREDITS_BUTTON"
flat = true
[node name="QuitButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
+focus_neighbor_top = NodePath("../CreditsButton")
+focus_previous = NodePath("../CreditsButton")
text = "QUIT_BUTTON"
flat = true
diff --git a/project.godot b/project.godot
index 36b43cf..63c6772 100644
--- a/project.godot
+++ b/project.godot
@@ -99,6 +99,12 @@ ui_accept={
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
]
}
+ui_cancel={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
+]
+}
left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
diff --git a/scenes/level_village_1.tscn b/scenes/level_village_1.tscn
index a10347f..41e290c 100644
--- a/scenes/level_village_1.tscn
+++ b/scenes/level_village_1.tscn
@@ -58,7 +58,7 @@ ease = 2
[node name="Brick Player" parent="." instance=ExtResource("1_dnj2y")]
z_index = 10
-[node name="HitParticles" parent="Brick Player" index="25"]
+[node name="HitParticles" parent="Brick Player" index="24"]
process_material = SubResource("ParticleProcessMaterial_lgb3u")
[node name="WorldEnvironment" parent="." instance=ExtResource("2_1vw1j")]
diff --git a/scenes/pac_xon_mini_game.tscn b/scenes/pac_xon_mini_game.tscn
new file mode 100644
index 0000000..dbb2de9
--- /dev/null
+++ b/scenes/pac_xon_mini_game.tscn
@@ -0,0 +1,159 @@
+[gd_scene load_steps=18 format=3 uid="uid://bljbcv22gq872"]
+
+[ext_resource type="Script" uid="uid://dx4m2ouyvwkir" path="res://scripts/components/PacXonGridManager.cs" id="1_0g620"]
+[ext_resource type="Texture2D" uid="uid://bolouq7v3acmx" path="res://sprites/pacxon_tileset.png" id="1_7fq4x"]
+[ext_resource type="Script" uid="uid://b8lu5pdufiy37" path="res://scripts/PacXonLevel.cs" id="2_lbrge"]
+[ext_resource type="PackedScene" uid="uid://bqi5s710xb1ju" path="res://objects/entities/brick_player.tscn" id="3_tehv8"]
+[ext_resource type="Texture2D" uid="uid://dedn7c7464pg2" path="res://sprites/brick_pacxon.png" id="5_tn615"]
+[ext_resource type="PackedScene" uid="uid://b3877xt5upsj2" path="res://objects/entities/ghost.tscn" id="6_8wuxa"]
+[ext_resource type="Script" uid="uid://d23haq52m7ulv" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="7_7j16a"]
+[ext_resource type="Script" uid="uid://ccfft4b8rwgbo" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="8_2w5m3"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_tn615"]
+texture = ExtResource("1_7fq4x")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_8wuxa"]
+sources/0 = SubResource("TileSetAtlasSource_tn615")
+
+[sub_resource type="Gradient" id="Gradient_qb72p"]
+colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_lgb3u"]
+gradient = SubResource("Gradient_qb72p")
+
+[sub_resource type="Curve" id="Curve_82d6e"]
+_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
+point_count = 2
+
+[sub_resource type="CurveTexture" id="CurveTexture_xoue7"]
+curve = SubResource("Curve_82d6e")
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_lgb3u"]
+resource_local_to_scene = true
+lifetime_randomness = 1.0
+particle_flag_disable_z = true
+emission_shape = 1
+emission_sphere_radius = 8.0
+direction = Vector3(0.1, -0.5, 0)
+initial_velocity_min = 200.0
+initial_velocity_max = 400.0
+gravity = Vector3(0, 80, 0)
+damping_min = 400.0
+damping_max = 800.0
+scale_max = 3.0
+scale_curve = SubResource("CurveTexture_xoue7")
+color = Color(0.764706, 0.443137, 0, 1)
+color_ramp = SubResource("GradientTexture1D_lgb3u")
+
+[sub_resource type="Resource" id="Resource_uptla"]
+script = ExtResource("8_2w5m3")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_7j16a"]
+
+[node name="PacXonMiniGame" type="Node2D"]
+
+[node name="PacXonGridManager" type="TileMapLayer" parent="."]
+tile_set = SubResource("TileSet_8wuxa")
+script = ExtResource("1_0g620")
+PlayArea = Rect2i(1, 1, 27, 14)
+metadata/_custom_type_script = "uid://dx4m2ouyvwkir"
+
+[node name="PacXonLevel" type="Node" parent="." node_paths=PackedStringArray("Player", "GridManager", "GhostContainer", "PercentageLabel")]
+script = ExtResource("2_lbrge")
+Player = NodePath("../Brick Player")
+GridManager = NodePath("../PacXonGridManager")
+GhostContainer = NodePath("../Ghosts")
+PercentageLabel = NodePath("../Label")
+metadata/_custom_type_script = "uid://b8lu5pdufiy37"
+
+[node name="Brick Player" parent="." instance=ExtResource("3_tehv8")]
+position = Vector2(101, 213)
+motion_mode = 1
+metadata/_edit_group_ = true
+
+[node name="GroundMovementAbility" parent="Brick Player/Movements" index="0"]
+process_mode = 4
+
+[node name="GravityAbility" parent="Brick Player/Movements" index="1"]
+process_mode = 4
+
+[node name="VariableJumpAbility" parent="Brick Player/Movements" index="2"]
+process_mode = 4
+
+[node name="OneWayPlatformAbility" parent="Brick Player/Movements" index="3"]
+process_mode = 4
+
+[node name="Base" parent="Brick Player/Graphics/Root" index="0"]
+texture = ExtResource("5_tn615")
+hframes = 1
+
+[node name="Left Eye" parent="Brick Player/Graphics/Root" index="1"]
+visible = false
+
+[node name="Right Eye" parent="Brick Player/Graphics/Root" index="2"]
+visible = false
+
+[node name="HitParticles" parent="Brick Player" index="24"]
+process_material = SubResource("ParticleProcessMaterial_lgb3u")
+
+[node name="VisibleOnScreenNotifier2D" parent="Brick Player" index="27"]
+position = Vector2(0, -4.76837e-07)
+scale = Vector2(0.8, 0.8)
+
+[node name="Ghosts" type="Node2D" parent="."]
+
+[node name="Ghost" parent="Ghosts" instance=ExtResource("6_8wuxa")]
+position = Vector2(252, 155)
+
+[node name="Label" type="Label" parent="."]
+offset_left = 73.0
+offset_right = 121.0
+offset_bottom = 8.0
+text = "sdsdsd"
+
+[node name="PhantomCamera2D" type="Node2D" parent="."]
+position = Vector2(-240, -135)
+script = ExtResource("7_7j16a")
+snap_to_pixel = true
+tween_resource = SubResource("Resource_uptla")
+draw_limits = true
+limit_target = NodePath("../PacXonGridManager")
+metadata/_custom_type_script = "uid://d23haq52m7ulv"
+
+[node name="Word boundary top" type="StaticBody2D" parent="."]
+position = Vector2(0, -2)
+collision_mask = 5
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Word boundary top"]
+shape = SubResource("WorldBoundaryShape2D_7j16a")
+
+[node name="Word boundary bottom" type="StaticBody2D" parent="."]
+position = Vector2(0, 272)
+collision_mask = 5
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Word boundary bottom"]
+shape = SubResource("WorldBoundaryShape2D_7j16a")
+
+[node name="Word boundary left" type="StaticBody2D" parent="."]
+position = Vector2(0, 272)
+rotation = 1.5708
+collision_mask = 5
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Word boundary left"]
+shape = SubResource("WorldBoundaryShape2D_7j16a")
+
+[node name="Word boundary right" type="StaticBody2D" parent="."]
+position = Vector2(482, 272)
+rotation = -1.5708
+collision_mask = 5
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Word boundary right"]
+shape = SubResource("WorldBoundaryShape2D_7j16a")
+
+[editable path="Brick Player"]
diff --git a/scripts/PacXonLevel.cs b/scripts/PacXonLevel.cs
new file mode 100644
index 0000000..7a9fab9
--- /dev/null
+++ b/scripts/PacXonLevel.cs
@@ -0,0 +1,59 @@
+using System.Linq;
+using Godot;
+using Mr.BrickAdventures.scripts.components;
+
+namespace Mr.BrickAdventures.scripts;
+
+[GlobalClass]
+public partial class PacXonLevel : Node
+{
+ [Export] public PlayerController Player { get; set; }
+ [Export] public PacXonGridManager GridManager { get; set; }
+ [Export] public Node GhostContainer { get; set; }
+ [Export] public Label PercentageLabel { get; set; }
+
+ private const float WinPercentage = 0.90f;
+
+ public override void _Ready()
+ {
+ var ghosts = GhostContainer.GetChildren().OfType().ToList();
+ Player.ClearMovementAbilities();
+ Player.SetGridMovement();
+
+ foreach (var ghost in ghosts)
+ {
+ var movement = ghost.GetNode("GhostMovementComponent");
+ movement?.Initialize(GridManager, Player);
+ }
+
+ var gridMovement = Player.GetNodeOrNull("Movements/GridMovementAbility");
+ var gridInteractor = Player.GetNodeOrNull("PacXonGridInteractor");
+ var trailComponent = Player.GetNodeOrNull("PacXonTrailComponent");
+
+ if (gridMovement != null && gridInteractor != null)
+ {
+ gridInteractor.Initialize(GridManager, gridMovement, ghosts);
+ trailComponent?.Initialize(gridInteractor);
+ }
+ else
+ {
+ GD.PushError("Could not find GridMovementAbility or PacXonGridInteractor on Player.");
+ }
+
+ GridManager.FillPercentageChanged += OnFillPercentageChanged;
+ OnFillPercentageChanged(GridManager.GetFillPercentage());
+
+ var playerMapPos = GridManager.LocalToMap(Player.Position);
+ Player.GlobalPosition = GridManager.MapToLocal(playerMapPos);
+ }
+
+ private void OnFillPercentageChanged(float percentage)
+ {
+ PercentageLabel.Text = $"Fill: {percentage:P0}";
+ if (percentage >= WinPercentage)
+ {
+ GD.Print("YOU WIN!");
+ GetTree().Paused = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/PacXonLevel.cs.uid b/scripts/PacXonLevel.cs.uid
new file mode 100644
index 0000000..8fe32f2
--- /dev/null
+++ b/scripts/PacXonLevel.cs.uid
@@ -0,0 +1 @@
+uid://b8lu5pdufiy37
diff --git a/scripts/components/GhostMovementComponent.cs b/scripts/components/GhostMovementComponent.cs
new file mode 100644
index 0000000..3c71487
--- /dev/null
+++ b/scripts/components/GhostMovementComponent.cs
@@ -0,0 +1,69 @@
+using Godot;
+
+namespace Mr.BrickAdventures.scripts.components;
+
+[GlobalClass]
+public partial class GhostMovementComponent : Node2D
+{
+ [Export] public float MoveSpeed { get; set; } = 0.2f;
+ [Export] public int GridSize { get; set; } = 16;
+
+ private CharacterBody2D _body;
+ private Timer _moveTimer;
+ private PacXonGridManager _gridManager;
+ private HealthComponent _playerHealth;
+ private Vector2 _direction;
+ private readonly Vector2[] _directions = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right };
+
+ public override void _Ready()
+ {
+ _body = Owner.GetNode(".");
+ _moveTimer = new Timer { WaitTime = MoveSpeed, OneShot = false, Autostart = true };
+ AddChild(_moveTimer);
+ _moveTimer.Timeout += OnMoveTimerTimeout;
+
+ var rng = new RandomNumberGenerator();
+ _direction = _directions[rng.RandiRange(0, 3)];
+ }
+
+ public void Initialize(PacXonGridManager gridManager, PlayerController player)
+ {
+ _gridManager = gridManager;
+ _playerHealth = player.GetNode("HealthComponent");
+ }
+
+ private void OnMoveTimerTimeout()
+ {
+ if (_gridManager == null || _body == null) return;
+
+ var nextMapPos = _gridManager.LocalToMap(_body.Position + (_direction * GridSize));
+ var cellState = _gridManager.GetCellState(nextMapPos);
+
+ switch (cellState)
+ {
+ case CellState.Solid:
+ PickNewDirection();
+ break;
+
+ case CellState.Trail:
+ _playerHealth?.DecreaseHealth(9999);
+ _moveTimer.Stop();
+ break;
+
+ case CellState.Empty:
+ _body.Position += _direction * GridSize;
+ break;
+ }
+ }
+
+ private void PickNewDirection()
+ {
+ var rng = new RandomNumberGenerator();
+ Vector2 newDir;
+ do
+ {
+ newDir = _directions[rng.RandiRange(0, 3)];
+ } while (newDir == _direction || newDir == -_direction);
+ _direction = newDir;
+ }
+}
\ No newline at end of file
diff --git a/scripts/components/GhostMovementComponent.cs.uid b/scripts/components/GhostMovementComponent.cs.uid
new file mode 100644
index 0000000..d2a2fd3
--- /dev/null
+++ b/scripts/components/GhostMovementComponent.cs.uid
@@ -0,0 +1 @@
+uid://7i20oc4cyabl
diff --git a/scripts/components/Movement/GridMovementAbility.cs b/scripts/components/Movement/GridMovementAbility.cs
new file mode 100644
index 0000000..1d1a1e1
--- /dev/null
+++ b/scripts/components/Movement/GridMovementAbility.cs
@@ -0,0 +1,59 @@
+using Godot;
+
+namespace Mr.BrickAdventures.scripts.components;
+
+[GlobalClass]
+public partial class GridMovementAbility : MovementAbility
+{
+ [Export] public float MoveSpeed { get; set; } = 0.15f; // Time in seconds between moves
+ [Export] public int GridSize { get; set; } = 16; // Size of one grid cell in pixels
+
+ private Vector2 _currentDirection = Vector2.Zero;
+ private Vector2 _nextDirection = Vector2.Zero;
+ private Timer _moveTimer;
+
+ [Signal]
+ public delegate void MovedEventHandler(Vector2 newPosition);
+
+ public override void Initialize(PlayerController controller)
+ {
+ base.Initialize(controller);
+ _moveTimer = new Timer { WaitTime = MoveSpeed, OneShot = false };
+ AddChild(_moveTimer);
+ _moveTimer.Timeout += OnMoveTimerTimeout;
+ _moveTimer.Start();
+ }
+
+ public override Vector2 ProcessMovement(Vector2 currentVelocity, double delta)
+ {
+ GD.Print($"Player position: {_body.Position}, {_body.GlobalPosition}");
+
+ var inputDirection = _input.MoveDirection;
+ var newDirection = Vector2.Zero;
+
+ if (Mathf.Abs(inputDirection.Y) > 0.1f)
+ {
+ newDirection = new Vector2(0, Mathf.Sign(inputDirection.Y));
+ }
+ else if (Mathf.Abs(inputDirection.X) > 0.1f)
+ {
+ newDirection = new Vector2(Mathf.Sign(inputDirection.X), 0);
+ }
+
+ if (newDirection != Vector2.Zero && newDirection != -_currentDirection)
+ {
+ _nextDirection = newDirection;
+ }
+
+ return Vector2.Zero;
+ }
+
+ private void OnMoveTimerTimeout()
+ {
+ _currentDirection = _nextDirection;
+ if (_currentDirection == Vector2.Zero) return;
+
+ _body.Position += _currentDirection * GridSize;
+ EmitSignal(SignalName.Moved, _body.GlobalPosition);
+ }
+}
\ No newline at end of file
diff --git a/scripts/components/Movement/GridMovementAbility.cs.uid b/scripts/components/Movement/GridMovementAbility.cs.uid
new file mode 100644
index 0000000..113b5fd
--- /dev/null
+++ b/scripts/components/Movement/GridMovementAbility.cs.uid
@@ -0,0 +1 @@
+uid://ctm5glmeu502l
diff --git a/scripts/components/PacXonGridInteractor.cs b/scripts/components/PacXonGridInteractor.cs
new file mode 100644
index 0000000..99c6332
--- /dev/null
+++ b/scripts/components/PacXonGridInteractor.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using Godot;
+
+namespace Mr.BrickAdventures.scripts.components;
+
+[GlobalClass]
+public partial class PacXonGridInteractor : Node
+{
+ private enum PlayerGridState { OnSolid, DrawingTrail }
+
+ private PacXonGridManager _gridManager;
+ private HealthComponent _healthComponent;
+ private GridMovementAbility _gridMovement;
+
+ private PlayerGridState _currentState = PlayerGridState.OnSolid;
+ private readonly List _currentTrail = [];
+ private List _ghosts = [];
+
+ [Signal] public delegate void TrailStartedEventHandler(Vector2 startPosition);
+ [Signal] public delegate void TrailExtendedEventHandler(Vector2 newPosition);
+ [Signal] public delegate void TrailClearedEventHandler();
+
+ public override void _Ready()
+ {
+ _healthComponent = Owner.GetNodeOrNull("HealthComponent");
+ }
+
+ public void Initialize(PacXonGridManager gridManager, GridMovementAbility gridMovement, List ghosts)
+ {
+ _gridManager = gridManager;
+ _gridMovement = gridMovement;
+ _ghosts = ghosts;
+ _gridMovement.Moved += OnPlayerMoved;
+ }
+
+ private void OnPlayerMoved(Vector2 newPosition)
+ {
+ if (_gridManager == null) return;
+
+ var mapCoords = _gridManager.LocalToMap(newPosition);
+ var destinationState = _gridManager.GetCellState(mapCoords);
+
+ if (_currentState == PlayerGridState.DrawingTrail) EmitSignalTrailExtended(newPosition);
+
+ if (destinationState == CellState.Trail)
+ {
+ EmitSignalTrailCleared();
+ _healthComponent?.DecreaseHealth(9999);
+ return;
+ }
+
+ if (_currentState == PlayerGridState.OnSolid)
+ {
+ if (destinationState == CellState.Empty)
+ {
+ // Moved from solid ground to an empty space, start drawing.
+ _currentState = PlayerGridState.DrawingTrail;
+ _currentTrail.Clear();
+ _currentTrail.Add(mapCoords);
+ _gridManager.SetCellState(mapCoords, CellState.Trail);
+ EmitSignalTrailStarted(newPosition);
+ }
+ }
+ else if (_currentState == PlayerGridState.DrawingTrail)
+ {
+ if (destinationState == CellState.Empty)
+ {
+ // Continue drawing the trail
+ _currentTrail.Add(mapCoords);
+ _gridManager.SetCellState(mapCoords, CellState.Trail);
+ }
+ else if (destinationState == CellState.Solid)
+ {
+ _gridManager.PerformFloodFill(_ghosts);
+ GD.Print("Fill logic triggered!");
+ _currentState = PlayerGridState.OnSolid;
+ SolidifyTrail();
+ _currentTrail.Clear();
+ EmitSignalTrailCleared();
+ }
+ }
+ }
+
+ private void SolidifyTrail()
+ {
+ foreach (var pos in _currentTrail)
+ {
+ _gridManager.SetCellState(pos, CellState.Solid);
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/components/PacXonGridInteractor.cs.uid b/scripts/components/PacXonGridInteractor.cs.uid
new file mode 100644
index 0000000..be9f5bc
--- /dev/null
+++ b/scripts/components/PacXonGridInteractor.cs.uid
@@ -0,0 +1 @@
+uid://c00siqtssccr6
diff --git a/scripts/components/PacXonGridManager.cs b/scripts/components/PacXonGridManager.cs
new file mode 100644
index 0000000..8e160b3
--- /dev/null
+++ b/scripts/components/PacXonGridManager.cs
@@ -0,0 +1,121 @@
+using System.Collections.Generic;
+using System.Linq;
+using Godot;
+
+namespace Mr.BrickAdventures.scripts.components;
+
+public enum CellState
+{
+ Empty = -1,
+ Solid = 0,
+ Trail = 1,
+ Hunted = 2
+}
+
+[GlobalClass]
+public partial class PacXonGridManager : TileMapLayer
+{
+ [Export] public Rect2I PlayArea { get; set; } = new Rect2I(1, 1, 38, 28);
+
+ private int _solidCellCount = 0;
+ private int _totalPlayableCells = 0;
+
+ [Signal] public delegate void FillPercentageChangedEventHandler(float percentage);
+
+ public override void _Ready()
+ {
+ _totalPlayableCells = PlayArea.Size.X * PlayArea.Size.Y;
+ InitializeGrid();
+ }
+
+ private void InitializeGrid()
+ {
+ Clear();
+
+ for (var x = PlayArea.Position.X - 1; x <= PlayArea.End.X + 1; x++)
+ {
+ for (var y = PlayArea.Position.Y - 1; y <= PlayArea.End.Y + 1; y++)
+ {
+ if (x < PlayArea.Position.X || x > PlayArea.End.X || y < PlayArea.Position.Y || y > PlayArea.End.Y)
+ {
+ SetCell(new Vector2I(x, y), (int)CellState.Solid, Vector2I.Zero);
+ }
+ }
+ }
+ }
+
+ public CellState GetCellState(Vector2I mapCoords)
+ {
+ var tileId = GetCellSourceId(mapCoords);
+ return (CellState)tileId;
+ }
+
+ public void SetCellState(Vector2I mapCoords, CellState state)
+ {
+ if (GetCellSourceId(mapCoords) != (int)CellState.Solid && state == CellState.Solid) _solidCellCount++;
+ SetCell(mapCoords, (int)state, Vector2I.Zero);
+ }
+
+ public float GetFillPercentage()
+ {
+ return _totalPlayableCells > 0 ? (float)_solidCellCount / _totalPlayableCells : 0;
+ }
+
+ public void PerformFloodFill(List ghosts)
+ {
+ var unsafeCells = new HashSet();
+
+ foreach (var ghost in ghosts.Where(IsInstanceValid))
+ {
+ var ghostPos = LocalToMap(ghost.Position);
+ FloodFillScan(ghostPos, unsafeCells);
+ }
+
+ var filledCount = 0;
+ for (var x = PlayArea.Position.X; x <= PlayArea.End.X; x++)
+ {
+ for (var y = PlayArea.Position.Y; y <= PlayArea.End.Y; y++)
+ {
+ var currentPos = new Vector2I(x, y);
+ if (GetCellState(currentPos) == CellState.Empty && !unsafeCells.Contains(currentPos))
+ {
+ SetCellState(currentPos, CellState.Solid);
+ filledCount++;
+ }
+ }
+ }
+
+ if (filledCount > 0)
+ {
+ EmitSignal(SignalName.FillPercentageChanged, GetFillPercentage());
+ }
+ }
+
+ private void FloodFillScan(Vector2I startPos, HashSet visited)
+ {
+ if (!PlayArea.HasPoint(startPos) || visited.Contains(startPos)) return;
+
+ var q = new Queue();
+ q.Enqueue(startPos);
+ visited.Add(startPos);
+
+ while (q.Count > 0)
+ {
+ var pos = q.Dequeue();
+
+ var neighbors = new[]
+ {
+ pos + Vector2I.Up, pos + Vector2I.Down, pos + Vector2I.Left, pos + Vector2I.Right
+ };
+
+ foreach (var neighbor in neighbors)
+ {
+ if (PlayArea.HasPoint(neighbor) && !visited.Contains(neighbor) && GetCellState(neighbor) == CellState.Empty)
+ {
+ visited.Add(neighbor);
+ q.Enqueue(neighbor);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/components/PacXonGridManager.cs.uid b/scripts/components/PacXonGridManager.cs.uid
new file mode 100644
index 0000000..1101a05
--- /dev/null
+++ b/scripts/components/PacXonGridManager.cs.uid
@@ -0,0 +1 @@
+uid://dx4m2ouyvwkir
diff --git a/scripts/components/PacXonTrailComponent.cs b/scripts/components/PacXonTrailComponent.cs
new file mode 100644
index 0000000..2f4c57e
--- /dev/null
+++ b/scripts/components/PacXonTrailComponent.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using Godot;
+
+namespace Mr.BrickAdventures.scripts.components;
+
+[GlobalClass]
+public partial class PacXonTrailComponent : Line2D
+{
+ private PacXonGridInteractor _gridInteractor;
+ private readonly List _trailPoints = [];
+
+ public void Initialize(PacXonGridInteractor interactor)
+ {
+ _gridInteractor = interactor;
+ _gridInteractor.TrailStarted += OnTrailStarted;
+ _gridInteractor.TrailExtended += OnTrailExtended;
+ _gridInteractor.TrailCleared += OnTrailCleared;
+
+ Width = 8;
+ DefaultColor = new Color("#a6f684");
+ JointMode = LineJointMode.Round;
+ BeginCapMode = LineCapMode.Round;
+ EndCapMode = LineCapMode.Round;
+ }
+
+ public override void _ExitTree()
+ {
+ if (_gridInteractor != null)
+ {
+ _gridInteractor.TrailStarted -= OnTrailStarted;
+ _gridInteractor.TrailExtended -= OnTrailExtended;
+ _gridInteractor.TrailCleared -= OnTrailCleared;
+ }
+ }
+
+ private void OnTrailStarted(Vector2 startPosition)
+ {
+ _trailPoints.Clear();
+ _trailPoints.Add(ToLocal(startPosition));
+ _trailPoints.Add(ToLocal(startPosition));
+ UpdateTrail();
+ }
+
+ private void OnTrailExtended(Vector2 newPosition)
+ {
+ if (_trailPoints.Count > 0)
+ {
+ _trailPoints[^1] = ToLocal(newPosition);
+ }
+ UpdateTrail();
+ }
+
+ private void OnTrailCleared()
+ {
+ _trailPoints.Clear();
+ UpdateTrail();
+ }
+
+ private void UpdateTrail()
+ {
+ ClearPoints();
+ foreach (var point in _trailPoints)
+ {
+ AddPoint(point);
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/components/PacXonTrailComponent.cs.uid b/scripts/components/PacXonTrailComponent.cs.uid
new file mode 100644
index 0000000..5cc3182
--- /dev/null
+++ b/scripts/components/PacXonTrailComponent.cs.uid
@@ -0,0 +1 @@
+uid://cmk4m7mplqnrm
diff --git a/scripts/components/PlayerController.cs b/scripts/components/PlayerController.cs
index 27695af..48cdd0d 100644
--- a/scripts/components/PlayerController.cs
+++ b/scripts/components/PlayerController.cs
@@ -18,6 +18,7 @@ public partial class PlayerController : CharacterBody2D
[Export] public PackedScene OneWayPlatformScene { get; set; }
[Export] public PackedScene SpaceshipMovementScene { get; set; }
[Export] public PackedScene WallJumpScene { get; set; }
+ [Export] public PackedScene GridMovementScene { get; set; }
[Signal] public delegate void JumpInitiatedEventHandler();
[Signal] public delegate void MovementAbilitiesChangedEventHandler();
@@ -75,7 +76,7 @@ public partial class PlayerController : CharacterBody2D
_abilities.Add(ability);
}
- private void ClearMovementAbilities()
+ public void ClearMovementAbilities()
{
foreach (var ability in _abilities)
{
@@ -118,7 +119,14 @@ public partial class PlayerController : CharacterBody2D
if (SpaceshipMovementScene != null) AddAbility(SpaceshipMovementScene.Instantiate());
EmitSignalMovementAbilitiesChanged();
}
-
+
+ public void SetGridMovement()
+ {
+ ClearMovementAbilities();
+ if (GridMovementScene != null) AddAbility(GridMovementScene.Instantiate());
+ EmitSignalMovementAbilitiesChanged();
+ }
+
private async Task ConnectJumpAndGravityAbilities()
{
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
diff --git a/sprites/brick_pacxon.png b/sprites/brick_pacxon.png
new file mode 100644
index 0000000..cd68bb1
Binary files /dev/null and b/sprites/brick_pacxon.png differ
diff --git a/sprites/brick_pacxon.png.import b/sprites/brick_pacxon.png.import
new file mode 100644
index 0000000..06bbf8d
--- /dev/null
+++ b/sprites/brick_pacxon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dedn7c7464pg2"
+path="res://.godot/imported/brick_pacxon.png-5ed91e7ecc9f90e9d888bcdf71d54f77.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://sprites/brick_pacxon.png"
+dest_files=["res://.godot/imported/brick_pacxon.png-5ed91e7ecc9f90e9d888bcdf71d54f77.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/sprites/pacxon_tileset.png b/sprites/pacxon_tileset.png
new file mode 100644
index 0000000..ba93f50
Binary files /dev/null and b/sprites/pacxon_tileset.png differ
diff --git a/sprites/pacxon_tileset.png.import b/sprites/pacxon_tileset.png.import
new file mode 100644
index 0000000..f13ec7c
--- /dev/null
+++ b/sprites/pacxon_tileset.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bolouq7v3acmx"
+path="res://.godot/imported/pacxon_tileset.png-d5a671b56c1f3b25f32c1372daae2944.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://sprites/pacxon_tileset.png"
+dest_files=["res://.godot/imported/pacxon_tileset.png-d5a671b56c1f3b25f32c1372daae2944.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1