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