Csharp rewrite (#4)

* Implement BeamComponent in C# and enhance marketplace button functionality

* Add core game components including ConfigFileHandler, GameManager, SaveSystem, and UIManager

* cleanup

* Add new components: CanPickUpComponent, CollapsableComponent, DestroyableComponent, EffectInflictorComponent, StatusEffectComponent, and StatusEffectDataResource

* Add new components: EnemyDeathComponent, EnemyWaveTriggerComponent, and ExitDoorComponent

* Add new components: ExplosiveComponent, FadeAwayComponent, FireEffectComponent, FlipComponent, GravityMotionComponent, LaunchComponent, and update PlatformMovement with LastDirection property

* Add new components: HealComponent, HitComponent, HomingMissileMotionComponent, LeverComponent, and TriggerLeverComponent

* Refactor GameManager session state handling and add new components: CanBeLaunchedComponent, IceEffectComponent, JumpPadComponent, KillPlayerOutOfScreenComponent, KnockbackComponent, LifetimeComponent, MagneticSkillComponent, OutOfScreenComponent, PeriodicShootingComponent, PlayerDeathComponent, ProgressiveDamageComponent, ProjectileComponent, ProjectileInitComponent, RequirementComponent, ScoreComponent, ShipMovementComponent, ShipShooterComponent, and SideToSideMovementComponent

* Add new components: CannotStompComponent, SkillUnlockedComponent, SpaceshipEnterComponent, SpaceshipExitComponent, SpinComponent, StompDamageComponent, StraightMotionComponent, TerrainHitFx, TooltipComponent, TrailComponent, and UnlockOnRequirementComponent

* Add new components: BrickThrowComponent, BulletComponent, CageComponent, ChaseLevelComponent, CleanupComponent, and ThrowInputResource classes; implement game saving and loading logic in SaveSystem

* Add audio settings management and platform movement component

* Add ChargeProgressBar, Credits, and GameOverScreen components for UI management

* Add UID files for ConfigFileHandler, GameManager, SaveSystem, and UIManager components

* Add README.md file with game description and features; include project license and contribution guidelines

* Add Hud component for UI management; display health, coins, and lives

* Add MainMenu and Marketplace components; implement game management and skill unlocking features

* Add PauseMenu, SettingsMenu, and SkillButton components; enhance game management and UI functionality
This commit is contained in:
2025-08-15 00:45:57 +02:00
committed by GitHub
parent 2ad0fe26d2
commit d84f7d1740
188 changed files with 4774 additions and 18 deletions

View File

@@ -0,0 +1,109 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
[GlobalClass]
public partial class BeamComponent : Node2D
{
private float _currentLength = 0.0f;
private const float PixelSize = 16.0f; // Assuming 16 pixels per unit for scaling
[Export]
public float ExpansionSpeed { get; set; } = 100.0f;
[Export]
public float MaxLength { get; set; } = 512.0f;
[Export]
public Vector2 Direction { get; set; } = Vector2.Down;
[Export]
public Node2D Root { get; set; }
[Export]
public Sprite2D Sprite { get; set; }
[Export]
public CollisionShape2D CollisionShape { get; set; }
public override void _Ready()
{
if (Root == null)
{
GD.PrintErr("Root node is not set for BeamComponent.");
}
if (Sprite == null)
{
GD.PrintErr("Sprite node is not set for BeamComponent.");
}
if (CollisionShape == null)
{
GD.PrintErr("CollisionShape node is not set for BeamComponent.");
}
var shape = CollisionShape?.Shape as RectangleShape2D;
shape?.SetSize(new Vector2(_currentLength / 2.0f, _currentLength / 2.0f));
Sprite?.SetScale(new Vector2(1f, 1f));
CollisionShape?.SetPosition(Vector2.Zero);
}
public override void _Process(double delta)
{
var newLength = _currentLength + ExpansionSpeed * (float)delta;
if (newLength > MaxLength) newLength = MaxLength;
if (!CheckForObstacle(newLength)) ExpandBeam(newLength);
}
private void ExpandBeam(float newLength)
{
_currentLength = newLength;
if (Direction == Vector2.Up)
{
var pos = Sprite.Position;
var scale = Sprite.Scale;
var shape = CollisionShape?.Shape as RectangleShape2D;
Sprite.SetScale(new Vector2(scale.X, _currentLength / PixelSize));
Sprite.SetPosition(new Vector2(pos.X, -_currentLength / 2.0f));
shape?.SetSize(new Vector2(PixelSize / 2f, _currentLength / 2.0f));
CollisionShape?.SetPosition(new Vector2(CollisionShape.Position.X, -_currentLength / 2.0f));
} else if (Direction == Vector2.Down)
{
var pos = Sprite.Position;
var scale = Sprite.Scale;
var shape = CollisionShape?.Shape as RectangleShape2D;
Sprite.SetScale(new Vector2(scale.X, _currentLength / PixelSize));
Sprite.SetPosition(new Vector2(pos.X, _currentLength / 2.0f));
shape?.SetSize(new Vector2(PixelSize / 2f, _currentLength / 2.0f));
CollisionShape?.SetPosition(new Vector2(CollisionShape.Position.X, _currentLength / 2.0f));
} else if (Direction == Vector2.Left)
{
var pos = Sprite.Position;
var scale = Sprite.Scale;
var shape = CollisionShape?.Shape as RectangleShape2D;
Sprite.SetScale(new Vector2(_currentLength / PixelSize, scale.Y));
Sprite.SetPosition(new Vector2(-_currentLength / 2.0f, pos.Y));
shape?.SetSize(new Vector2(_currentLength / 2.0f, PixelSize / 2f));
CollisionShape?.SetPosition(new Vector2(-_currentLength / 2.0f, CollisionShape.Position.Y));
} else if (Direction == Vector2.Right)
{
var pos = Sprite.Position;
var scale = Sprite.Scale;
var shape = CollisionShape?.Shape as RectangleShape2D;
Sprite.SetScale(new Vector2(_currentLength / PixelSize, scale.Y));
Sprite.SetPosition(new Vector2(_currentLength / 2.0f, pos.Y));
shape?.SetSize(new Vector2(_currentLength / 2.0f, PixelSize / 2f));
CollisionShape?.SetPosition(new Vector2(_currentLength / 2.0f, CollisionShape.Position.Y));
}
}
private bool CheckForObstacle(float newLength)
{
var spaceState = GetWorld2D().DirectSpaceState;
var queryStart = GlobalPosition;
var queryEnd = queryStart + Direction.Normalized() * newLength;
var query = PhysicsRayQueryParameters2D.Create(queryStart, queryEnd);
query.CollideWithAreas = false;
query.CollideWithBodies = true;
var result = spaceState.IntersectRay(query);
return result.Count > 0;
}
}

View File

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

View File

@@ -0,0 +1,70 @@
using Godot;
using Mr.BrickAdventures.scripts.interfaces;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class BrickThrowComponent : Node
{
[Export] public PackedScene BrickScene { get; set; }
[Export] public float FireRate { get; set; } = 1.0f;
[Export] public PlayerController PlayerController { get; set; }
[Export] public ThrowInputResource ThrowInputBehavior { get; set; }
private bool _canThrow = true;
private Timer _timer;
public override void _Ready()
{
SetupTimer();
_canThrow = true;
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested += ThrowBrick;
}
public override void _Input(InputEvent @event)
{
ThrowInputBehavior?.ProcessInput(@event);
}
public override void _Process(double delta)
{
ThrowInputBehavior?.Update(delta);
}
private void SetupTimer()
{
_timer.WaitTime = FireRate;
_timer.OneShot = false;
_timer.Autostart = false;
_timer.Timeout += OnTimerTimeout;
}
private void OnTimerTimeout()
{
_canThrow = true;
}
private void ThrowBrick(float powerMultiplier = 1f)
{
if (!_canThrow || PlayerController == null || BrickScene == null)
return;
var instance = BrickScene.Instantiate<Node2D>();
var init = instance.GetNodeOrNull<ProjectileInitComponent>("ProjectileInitComponent");
if (init != null && PlayerController.CurrentMovement is PlatformMovementComponent)
{
init.Initialize(new ProjectileInitParams()
{
Position = PlayerController.GlobalPosition,
Rotation = PlayerController.Rotation,
Direction = PlayerController.CurrentMovement.LastDirection,
PowerMultiplier = powerMultiplier,
});
}
GetTree().CurrentScene.AddChild(instance);
_canThrow = false;
_timer.Start();
}
}

View File

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

View File

@@ -0,0 +1,45 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class BulletComponent : Node
{
[Export] public Area2D Area { get; set; }
[Export] public TerrainHitFx TerrainHitFx { get; set; }
[Export] public Sprite2D BulletSprite { get; set; }
public override void _Ready()
{
Area.BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)
{
if (body is TileMapLayer)
{
if (BulletSprite != null)
{
BulletSprite.Visible = false;
}
PlayTerrainHitFx();
return;
}
Owner.QueueFree();
}
private void OnAreaEntered(Area2D area)
{
Owner.QueueFree();
}
private void PlayTerrainHitFx()
{
if (TerrainHitFx == null) return;
TerrainHitFx.TriggerFx();
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,47 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CageComponent : Node
{
[Export] public LeverComponent Lever { get; set; }
[Export] public Vector2 MoveValue { get; set; } = new(0, -100f);
[Export] public float TweenDuration { get; set; } = 0.5f;
[Export] public bool ShouldFree { get; set; } = true;
private const string LeverGroupName = "levers";
public override async void _Ready()
{
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
if (Lever == null)
{
var leverNodes = GetTree().GetNodesInGroup(LeverGroupName);
foreach (var leverNode in leverNodes)
{
var lever = leverNode.GetNodeOrNull<LeverComponent>("LeverComponent");
if (lever != null) lever.Activated += OnLeverActivated;
}
}
}
private void OnLeverActivated()
{
var tween = CreateTween();
if (Owner is Node2D root)
{
var endPosition = root.Position + MoveValue;
tween.TweenProperty(root, "position", endPosition, TweenDuration);
}
tween.TweenCallback(Callable.From(OnTweenCompleted));
}
private void OnTweenCompleted()
{
if (!ShouldFree) return;
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,8 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CanBeLaunchedComponent : Node
{
}

View File

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

View File

@@ -0,0 +1,8 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CanPickUpComponent : Node
{
}

View File

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

View File

@@ -0,0 +1,8 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CannotStompComponent : Node
{
}

View File

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

View File

@@ -0,0 +1,79 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ChaseLevelComponent : Node
{
[Export] public float ChaseSpeed { get; set; } = 200.0f;
[Export] public Marker2D ChaseTarget { get; set; }
[Export] public GodotObject PhantomCamera { get; set; }
[Export] public float MinimumDistance { get; set; } = 10f;
[Signal]
public delegate void ChaseStartedEventHandler();
[Signal]
public delegate void ChaseStoppedEventHandler();
private bool _isChasing = false;
private Node2D _previousCameraFollowTarget = null;
public override void _Process(double delta)
{
if (!_isChasing) return;
if (ChaseTarget == null) return;
if (CheckIfReachedTarget())
{
StopChasing();
return;
}
var targetPosition = ChaseTarget.GlobalPosition;
if (Owner is not Node2D root) return;
var direction = (targetPosition - root.GlobalPosition).Normalized();
root.GlobalPosition += direction * ChaseSpeed * (float)delta;
}
public void OnShipEntered()
{
if (ChaseTarget == null || PhantomCamera == null)
return;
if (_isChasing) return;
_previousCameraFollowTarget = (Node2D)PhantomCamera.Call("get_follow_target");
PhantomCamera.Call("set_follow_target", Owner as Node2D);
EmitSignalChaseStarted();
_isChasing = true;
}
public void OnShipExited()
{
StopChasing();
}
private bool CheckIfReachedTarget()
{
if (ChaseTarget == null)
return false;
if (Owner is not Node2D root) return false;
var targetPosition = ChaseTarget.GlobalPosition;
var currentPosition = root.GlobalPosition;
return currentPosition.DistanceTo(targetPosition) <= MinimumDistance;
}
private void StopChasing()
{
if (PhantomCamera == null) return;
PhantomCamera.Call("set_follow_target", _previousCameraFollowTarget);
EmitSignalChaseStopped();
_isChasing = false;
}
}

View File

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

View File

@@ -0,0 +1,11 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CleanupComponent : Node
{
public void CleanUp()
{
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,81 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CollapsableComponent : Node
{
[Export] public Timer ToCollapseTimer { get; set; }
[Export] public Timer ResetTimer { get; set; }
[Export] public Sprite2D Sprite2D { get; set; }
[Export] public CollisionShape2D CollisionShape { get; set; }
[Export] public float CollapseTime { get; set; } = 0.5f;
[Export] public float ResetTime { get; set; } = 0.5f;
[Export] public float AnimationTime { get; set; } = 0.25f;
public override void _Ready()
{
ResetTimers();
ToCollapseTimer.Timeout += OnToCollapseTimerTimeout;
ResetTimer.Timeout += OnResetTimerTimeout;
}
public void OnCollapsableDetectorBodyEntered(Node2D body)
{
ToCollapseTimer.Start();
}
public void OnCollapsableDetectorBodyExited(Node2D body)
{
var collapseTimeLeft = Mathf.Abs(ToCollapseTimer.TimeLeft - CollapseTime);
if (collapseTimeLeft < (0.1f * CollapseTime))
{
ResetTimers();
}
}
private void OnToCollapseTimerTimeout()
{
_ = Collapse();
}
private void OnResetTimerTimeout()
{
_ = Reactivate();
}
private async Task Collapse()
{
ToCollapseTimer.Stop();
ToCollapseTimer.SetWaitTime(CollapseTime);
var tween = CreateTween();
tween.TweenProperty(Sprite2D, "modulate:a", 0f, AnimationTime);
await ToSignal(tween, Tween.SignalName.Finished);
CollisionShape?.CallDeferred("set_disabled", true);
ResetTimer.Start();
}
private async Task Reactivate()
{
ResetTimer.Stop();
ResetTimer.SetWaitTime(ResetTime);
var tween = CreateTween();
tween.TweenProperty(Sprite2D, "modulate:a", 1f, AnimationTime);
await ToSignal(tween, Tween.SignalName.Finished);
CollisionShape?.CallDeferred("set_disabled", false);
}
private void ResetTimers()
{
ToCollapseTimer.Stop();
ToCollapseTimer.SetWaitTime(CollapseTime);
ResetTimer.Stop();
ResetTimer.SetWaitTime(ResetTime);
}
}

View File

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

View File

@@ -0,0 +1,50 @@
using System;
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class CollectableComponent : Node
{
private bool _hasFadeAway = false;
[Export] public Area2D Area2D { get; set; }
[Export] public CollisionShape2D CollisionShape { get; set; }
[Export] public CollectableResource Data { get; set; }
[Export] public AudioStreamPlayer2D Sfx {get; set; }
[Signal] public delegate void CollectedEventHandler(Variant amount, CollectableType type, Node2D body);
public override void _Ready()
{
if (Area2D != null)
Area2D.BodyEntered += OnArea2DBodyEntered;
else
GD.PushError("Collectable node missing Area2D node.");
if (Owner.HasNode("FadeAwayComponent"))
_hasFadeAway = true;
}
private async void OnArea2DBodyEntered(Node2D body)
{
try
{
if (!body.HasNode("CanPickUpComponent")) return;
EmitSignalCollected(Data.Amount, Data.Type, body);
CollisionShape?.CallDeferred("set_disabled", true);
Sfx?.Play();
if (_hasFadeAway) return;
if (Sfx != null)
await ToSignal(Sfx, AudioStreamPlayer2D.SignalName.Finished);
Owner.QueueFree();
}
catch (Exception e)
{
GD.PushError($"Error in CollectableComponent.OnArea2DBodyEntered: {e.Message}");
}
}
}

View File

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

View File

@@ -0,0 +1,105 @@
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class DamageComponent : Node
{
[Export] public float Damage { get; set; } = 0.25f;
[Export] public Area2D Area { get; set; }
[Export] public StatusEffectDataResource StatusEffectData { get; set; }
[Export] public Timer DamageTimer { get; set; }
private Node _currentTarget = null;
[Signal] public delegate void EffectInflictedEventHandler(Node2D target, StatusEffectDataResource effect);
public override void _Ready()
{
if (Area == null)
{
GD.PushError($"DamageComponent: Area2D node is not set.");
return;
}
Area.BodyEntered += OnAreaBodyEntered;
Area.BodyExited += OnAreaBodyExited;
Area.AreaEntered += OnAreaAreaEntered;
if (DamageTimer != null)
{
DamageTimer.Timeout += OnDamageTimerTimeout;
}
}
public override void _Process(double delta)
{
if (_currentTarget == null) return;
if (DamageTimer != null) return;
ProcessEntityAndApplyDamage(_currentTarget as Node2D);
}
public void DealDamage(HealthComponent target) => target.DecreaseHealth(Damage);
private void OnAreaAreaEntered(Area2D area)
{
if (!CheckIfProcessingIsOn())
return;
if (area == Area) return;
var parent = area.GetParent();
if (parent.HasNode("DamageComponent"))
ProcessEntityAndApplyDamage(parent as Node2D);
}
private void OnAreaBodyExited(Node2D body)
{
if (body != _currentTarget) return;
_currentTarget = null;
DamageTimer?.Stop();
}
private void OnAreaBodyEntered(Node2D body)
{
_currentTarget = body;
if (!CheckIfProcessingIsOn())
return;
DamageTimer?.Start();
ProcessEntityAndApplyDamage(body);
}
private void OnDamageTimerTimeout()
{
if (_currentTarget == null) return;
ProcessEntityAndApplyDamage(_currentTarget as Node2D);
}
private void ProcessEntityAndApplyDamage(Node2D body)
{
if (body == null) return;
if (!body.HasNode("HealthComponent")) return;
var health = body.GetNode<HealthComponent>("HealthComponent");
var inv = body.GetNodeOrNull<InvulnerabilityComponent>("InvulnerabilityComponent");
if (inv != null && inv.IsInvulnerable())
return;
if (StatusEffectData != null && StatusEffectData.Type != StatusEffectType.None)
EmitSignalEffectInflicted(body, StatusEffectData);
DealDamage(health);
inv?.Activate();
}
private bool CheckIfProcessingIsOn()
{
return ProcessMode is ProcessModeEnum.Inherit or ProcessModeEnum.Always;
}
}

View File

@@ -0,0 +1 @@
uid://2i7p7v135u7c

View File

@@ -0,0 +1,35 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class DestroyableComponent : Node2D
{
[Export] public HealthComponent Health { get; set; }
[Export] public PackedScene DestroyEffect { get; set; }
public override void _Ready()
{
if (Health == null)
{
GD.PushError("DestroyableComponent: HealthComponent is not set.");
return;
}
Health.Death += OnHealthDeath;
}
private void OnHealthDeath()
{
if (DestroyEffect == null)
{
Owner.QueueFree();
return;
}
var effect = DestroyEffect.Instantiate<Node2D>();
Health.GetParent().AddChild(effect);
effect.SetGlobalPosition(Health.GlobalPosition);
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,27 @@
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class EffectInflictorComponent : Node
{
[Export] public DamageComponent Damage { get; set; }
public override void _Ready()
{
if (Damage == null)
{
GD.PushError("EffectInflictorComponent requires a DamageComponent to be set.");
return;
}
Damage.EffectInflicted += OnEffectInflicted;
}
private void OnEffectInflicted(Node2D target, StatusEffectDataResource effect)
{
var statusEffect = target.GetNodeOrNull<StatusEffectComponent>("StatusEffectComponent");
statusEffect?.ApplyEffect(effect);
}
}

View File

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

View File

@@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class EnemyDeathComponent : Node
{
[Export] public float TweenDuration { get; set; } = 0.5f;
[Export] public CollisionShape2D CollisionShape { get; set; }
[Export] public HealthComponent Health { get; set; }
public override void _Ready()
{
if (CollisionShape == null)
{
GD.PushError("EnemyDeathComponent: CollisionShape is not set.");
return;
}
if (Health == null)
{
GD.PushError("EnemyDeathComponent: Health is not set.");
return;
}
Health.Death += OnDeath;
}
private void OnDeath()
{
CallDeferred(nameof(Die));
}
private async Task Die()
{
CollisionShape.SetDisabled(true);
var tween = CreateTween();
tween.TweenProperty(Owner, "scale", Vector2.Zero, TweenDuration);
await ToSignal(tween, Tween.SignalName.Finished);
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,51 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class EnemyWaveTriggerComponent : Node
{
[Export] public Area2D Area2D { get; set; }
[Export] public PathFollow2D PathFollowNode { get; set; }
[Export] public float Speed { get; set; } = 100f;
[Export] public bool Loop { get; set; } = false;
[Export] public bool ActivateOnEnter { get; set; } = true;
private bool _isActive = false;
public override void _Ready()
{
Area2D.BodyEntered += OnBodyEntered;
if (PathFollowNode == null) return;
PathFollowNode.SetProgress(0f);
PathFollowNode.SetProcess(false);
}
public override void _Process(double delta)
{
if (!_isActive || PathFollowNode == null) return;
var progress = PathFollowNode.Progress;
progress += (float)(delta * Speed);
PathFollowNode.SetProgress(progress);
if (!(PathFollowNode.ProgressRatio >= 1f) || Loop) return;
_isActive = false;
PathFollowNode.SetProcess(false);
}
private void OnBodyEntered(Node2D body)
{
if (ActivateOnEnter) StartWave();
}
private void StartWave()
{
if (PathFollowNode == null) return;
PathFollowNode.SetProcess(true);
_isActive = true;
}
}

View File

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

View File

@@ -0,0 +1,52 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.components;
public partial class ExitDoorComponent : Node, IUnlockable
{
[Export] public bool Locked { get; set; } = true;
[Export] public Area2D ExitArea { get; set; }
[Export] public Sprite2D DoorSprite { get; set; }
[Export] public AudioStreamPlayer2D OpenDoorSfx { get; set; }
[Export] public int OpenedDoorFrame { get; set; } = 0;
[Signal] public delegate void ExitTriggeredEventHandler();
private GameManager _gameManager;
public override void _Ready()
{
if (ExitArea == null)
{
GD.PushError("ExitDoorComponent: ExitArea is not set.");
return;
}
ExitArea.BodyEntered += OnExitAreaBodyEntered;
_gameManager = GetNode<GameManager>("/root/gameManager");
}
private void OnExitAreaBodyEntered(Node2D body)
{
throw new System.NotImplementedException();
}
public void Unlock()
{
Locked = false;
if (DoorSprite != null)
{
DoorSprite.Frame = OpenedDoorFrame;
}
OpenDoorSfx?.Play();
}
private void GoToNextLevel()
{
_gameManager.OnLevelComplete();
}
}

View File

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

View File

@@ -0,0 +1,81 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ExplosiveComponent : Node2D
{
[Export] public DamageComponent Damage { get; set; }
[Export] public Area2D Area { get; set; }
[Export] public Area2D ExplodeArea { get; set; }
[Export] public PackedScene ExplosionEffect { get; set; }
[Export] public float TimeToExplode { get; set; } = 9f;
[Signal] public delegate void OnExplosionEventHandler(Node2D body);
private Timer _timer;
public override void _Ready()
{
if (Damage != null)
{
GD.PushError("ExplosiveComponent: DamageComponent is not set.");
return;
}
if (ExplodeArea != null)
{
GD.PushError("ExplosiveComponent: ExplodeArea is not set.");
return;
}
Area.BodyEntered += OnAreaBodyEntered;
Area.AreaEntered += OnAreaAreaEntered;
}
private void OnAreaAreaEntered(Area2D area)
{
Explode();
}
private void OnAreaBodyEntered(Node2D body)
{
Explode();
}
private void PrepareTimer()
{
_timer = new Timer();
_timer.SetWaitTime(TimeToExplode);
_timer.OneShot = true;
_timer.Autostart = true;
_timer.Timeout += Explode;
AddChild(_timer);
}
private void Explode()
{
_timer.Stop();
if (ExplosionEffect != null)
{
var explosionInstance = ExplosionEffect.Instantiate<GpuParticles2D>();
if (Owner is Node2D root) explosionInstance.SetGlobalPosition(root.GlobalPosition);
GetTree().CurrentScene.AddChild(explosionInstance);
explosionInstance.SetEmitting(true);
}
var bodies = ExplodeArea.GetOverlappingBodies();
foreach (var body in bodies)
{
var health = body.GetNodeOrNull<HealthComponent>("HealthComponent");
if (Damage != null && health != null)
{
Damage.DealDamage(health);
}
EmitSignalOnExplosion(body);
}
Owner.QueueFree();
}
}

View File

@@ -0,0 +1 @@
uid://7uttgdr6cr5y

View File

@@ -0,0 +1,32 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class FadeAwayComponent : Node
{
[Export] public Sprite2D Sprite { get; set; }
[Export] public float FadeDuration { get; set; } = 1f;
[Export] public float Speed { get; set; } = 10f;
[Export] public Vector2 Direction { get; set; } = Vector2.Up;
[Export] public Area2D Area { get; set; }
public override void _Ready()
{
Area.BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)
{
_ = FadeAway();
}
private async Task FadeAway()
{
var tween = CreateTween().SetParallel(true);
tween.TweenProperty(Sprite, "modulate:a", 0f, FadeDuration);
tween.TweenProperty(Sprite, "position", Sprite.Position + (Direction * Speed), FadeDuration);
await ToSignal(tween, Tween.SignalName.Finished);
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,71 @@
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class FireEffectComponent : Node
{
[Export] public HealthComponent Health { get; set; }
[Export] public StatusEffectComponent StatusEffectComponent { get; set; }
[Export] public GpuParticles2D FireFX { get; set; }
private StatusEffectDataResource _data = null;
private bool _shouldDealDamage = false;
private double _timeElapsed = 0f;
public override void _Ready()
{
if (Health == null)
{
Health = GetNode<HealthComponent>("HealthComponent");
}
if (StatusEffectComponent == null)
{
StatusEffectComponent = GetNode<StatusEffectComponent>("StatusEffectComponent");
}
if (Health == null)
{
GD.PushError("FireEffectComponent: HealthComponent is not set.");
return;
}
if (StatusEffectComponent == null)
{
GD.PushError("FireEffectComponent: StatusEffectComponent is not set.");
return;
}
StatusEffectComponent.EffectApplied += OnEffectApplied;
StatusEffectComponent.EffectRemoved += OnEffectRemoved;
}
public override void _Process(double delta)
{
if (!_shouldDealDamage || _data == null || Health == null) return;
_timeElapsed += delta;
if (_timeElapsed >= 1f)
{
Health.DecreaseHealth(_data.DamagePerSecond);
_timeElapsed = 0f;
}
}
private void OnEffectApplied(StatusEffect statusEffect)
{
if (statusEffect.EffectData.Type != StatusEffectType.Fire) return;
_data = statusEffect.EffectData;
_shouldDealDamage = true;
FireFX?.SetEmitting(true);
}
private void OnEffectRemoved(StatusEffectType type)
{
if (type != StatusEffectType.Fire) return;
_shouldDealDamage = false;
_data = null;
FireFX?.SetEmitting(false);
}
}

View File

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

View File

@@ -0,0 +1,83 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class FlashingComponent : Node
{
[Export] public Node2D Sprite { get; set; }
[Export] public float FlashDuration { get; set; } = 0.5f;
[Export] public float FlashTime { get; set; } = 0.1f;
[Export] public bool UseModulate { get; set; } = true;
[Export] public HealthComponent HealthComponent { get; set; }
private Tween _tween;
public override void _Ready()
{
if (HealthComponent != null)
{
HealthComponent.HealthChanged += OnHealthChanged;
HealthComponent.Death += OnDeath;
}
if (Sprite == null)
{
GD.PushError("FlashingComponent: Sprite node is not set.");
return;
}
}
public void StartFlashing()
{
if (Sprite == null) return;
_tween?.Kill();
_tween = CreateTween();
_tween.SetParallel(true);
var flashes = (int)(FlashDuration / FlashTime);
for (var i = 0; i < flashes; i++)
{
if (UseModulate)
{
var opacity = i % 2 == 0 ? 1.0f : 0.3f;
_tween.TweenProperty(Sprite, "modulate:a", opacity, FlashTime);
}
else
{
var visible = i % 2 == 0;
_tween.TweenProperty(Sprite, "visible", visible, FlashTime);
}
}
_tween.TweenCallback(Callable.From(StopFlashing));
}
public void StopFlashing()
{
if (UseModulate)
{
var modulateColor = Sprite.GetModulate();
modulateColor.A = 1.0f;
Sprite.SetModulate(modulateColor);
}
else
{
Sprite.SetVisible(true);
}
}
private void OnHealthChanged(float delta, float totalHealth)
{
if (delta < 0f)
{
StartFlashing();
}
}
private void OnDeath()
{
StopFlashing();
}
}

View File

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

View File

@@ -0,0 +1,36 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class FlipComponent : Node2D
{
[Export] public Sprite2D LeftEye { get; set; }
[Export] public Sprite2D RightEye { get; set; }
[Export] public PlatformMovementComponent PlatformMovement { get; set; }
public override void _Process(double delta)
{
if (PlatformMovement == null) return;
var velocity = PlatformMovement.LastDirection;
switch (velocity.X)
{
case < 0f:
LeftEye.Frame = 1;
RightEye.Frame = 1;
LeftEye.FlipH = true;
RightEye.FlipH = true;
break;
case > 0f:
LeftEye.Frame = 1;
RightEye.Frame = 1;
LeftEye.FlipH = false;
RightEye.FlipH = false;
break;
default:
LeftEye.Frame = 0;
RightEye.Frame = 0;
break;
}
}
}

View File

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

View File

@@ -0,0 +1,37 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class GravityMotionComponent : Node2D
{
[Export] public CharacterBody2D Body { get; set; }
[Export] public LaunchComponent LaunchComponent { get; set; }
[Export] public Vector2 Gravity { get; set; } = new Vector2(0, 1000f);
[Export] public Vector2 TargetDirection { get; set; } = Vector2.Up;
private Vector2 _velocity = Vector2.Zero;
public override void _Ready()
{
if (LaunchComponent == null) return;
var direction = LaunchComponent.InitialDirection.X > 0f ? TargetDirection : new Vector2(-TargetDirection.X, TargetDirection.Y);
direction = direction.Normalized();
_velocity = direction * LaunchComponent.Speed;
}
public override void _PhysicsProcess(double delta)
{
if (Body == null) return;
_velocity += Gravity * (float)delta;
Body.Velocity = _velocity;
Body.MoveAndSlide();
if (_velocity.LengthSquared() > 0.01f)
{
Body.Rotation = _velocity.Angle();
}
}
}

View File

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

View File

@@ -0,0 +1,48 @@
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class HealComponent : Node
{
[Export] public GpuParticles2D HealFx { get; set; }
[Export] public CollectableComponent Collectable { get; set; }
public override void _Ready()
{
if (Collectable == null)
{
GD.PushError("HealComponent: Collectable is not set.");
return;
}
Collectable.Collected += OnCollected;
}
private void OnCollected(Variant amount, CollectableType type, Node2D body)
{
if (type != CollectableType.Health) return;
if (Collectable == null) return;
var healthComponent = body.GetNodeOrNull<HealthComponent>("HealthComponent");
if (healthComponent == null) return;
var value = amount.AsSingle();
healthComponent.IncreaseHealth(value);
if (HealFx != null)
{
PlayHealFx();
}
Owner.QueueFree();
}
private void PlayHealFx()
{
if (HealFx == null) return;
HealFx.Restart();
HealFx.Emitting = true;
}
}

View File

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

View File

@@ -0,0 +1,65 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class HealthComponent : Node2D
{
[Export] public float Health { get; set; } = 1.0f;
[Export] public float MaxHealth { get; set; } = 1.0f;
[Export] public AudioStreamPlayer2D HurtSfx { get; set; }
[Export] public AudioStreamPlayer2D HealSfx { get; set; }
[Signal] public delegate void HealthChangedEventHandler(float delta, float totalHealth);
[Signal] public delegate void DeathEventHandler();
public void SetHealth(float newValue)
{
_ = ApplyHealthChange(newValue);
}
public void IncreaseHealth(float delta)
{
_ = ApplyHealthChange(Health + delta);
}
public void DecreaseHealth(float delta)
{
_ = ApplyHealthChange(Health - delta);
}
public float GetDelta(float newValue) => newValue - Health;
private async Task ApplyHealthChange(float newHealth, bool playSfx = true)
{
newHealth = Mathf.Clamp(newHealth, 0.0f, MaxHealth);
var delta = newHealth - Health;
if (delta == 0.0f)
return;
if (playSfx)
{
if (delta > 0f && HealSfx != null)
{
HealSfx.Play();
}
else if (delta < 0f && HurtSfx != null)
{
HurtSfx.Play();
await HurtSfx.ToSignal(HurtSfx, AudioStreamPlayer2D.SignalName.Finished);
}
}
Health = newHealth;
if (Health <= 0f)
{
EmitSignalDeath();
}
else
{
EmitSignalHealthChanged(delta, Health);
}
}
}

View File

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

View File

@@ -0,0 +1,75 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class HitComponent : Node
{
[Export] public Sprite2D Sprite { get; set; }
[Export] public HealthComponent Health { get; set; }
[Export] public float HitDuration { get; set; } = 0.1f;
[Export] public GpuParticles2D HitFx { get; set; }
[Export] public bool FlashMode { get; set; } = true;
public override void _Ready()
{
if (Health != null)
{
Health.HealthChanged += OnHealthChange;
Health.Death += OnDeath;
}
if (Sprite == null)
{
GD.PushError("HitComponent: Sprite is null");
return;
}
if (Sprite.Material != null && FlashMode)
{
Sprite.Material = (Material)Sprite.Material.Duplicate();
}
}
private void Activate()
{
if (!FlashMode) return;
Sprite.SetInstanceShaderParameter("enabled", true);
}
private void Deactivate()
{
if (!FlashMode) return;
Sprite.SetInstanceShaderParameter("enabled", false);
}
private async void OnHealthChange(float delta, float totalHealth)
{
if (!(delta < 0f)) return;
Activate();
await ToSignal(GetTree().CreateTimer(HitDuration), Timer.SignalName.Timeout);
Deactivate();
if (totalHealth > 0f && delta < 0f)
{
HandleHitFx();
}
}
private async void OnDeath()
{
Activate();
await ToSignal(GetTree().CreateTimer(HitDuration), Timer.SignalName.Timeout);
Deactivate();
}
private void HandleHitFx()
{
if (HitFx == null) return;
HitFx.Restart();
HitFx.Emitting = true;
}
}

View File

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

View File

@@ -0,0 +1,64 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class HomingMissileMotionComponent : Node
{
[Export] public LaunchComponent Launch { get; set; }
[Export] public float MaxSpeed { get; set; } = 16f;
[Export] public float Acceleration { get; set; } = 8f;
[Export] public float MaxTurnRate { get; set; } = 180f; // degrees per second
[Export] public float WobbleStrength { get; set; } = 5f; // degrees
[Export] public float Drag { get; set; } = 0.98f;
[Export] public float SteeringLerp { get; set; } = 0.05f; // low = sluggish, high = responsive
[Export] public Area2D DetectionArea { get; set; }
private Vector2 _steeringDirection = Vector2.Zero;
private Vector2 _velocity = Vector2.Zero;
private Node2D _target = null;
public override void _Ready()
{
DetectionArea.BodyEntered += OnBodyEntered;
_velocity = Launch.GetInitialVelocity();
}
public override void _PhysicsProcess(double delta)
{
if (Launch == null) return;
if (Owner is not Node2D owner) return;
if (_target == null)
{
owner.Position += _velocity * (float)delta;
return;
}
var toTarget = (_target.GlobalPosition - owner.GlobalPosition).Normalized();
_steeringDirection = _steeringDirection.Lerp(toTarget, SteeringLerp);
var angleToTarget = _velocity.AngleTo(_steeringDirection);
var maxAngle = Mathf.DegToRad(MaxTurnRate) * (float)delta;
var clampedAngle = Mathf.Clamp(angleToTarget, -maxAngle, maxAngle);
var rng = new RandomNumberGenerator();
var wobble = Mathf.DegToRad(rng.RandfRange(-WobbleStrength, WobbleStrength));
clampedAngle += wobble;
_velocity = _velocity.Rotated(clampedAngle);
_velocity *= Drag;
var desiredSpeed = Mathf.Min(MaxSpeed, _velocity.Length() + Acceleration * (float)delta);
_velocity = _velocity.Normalized() * desiredSpeed;
owner.Position += _velocity * (float)delta;
owner.Rotation = _velocity.Angle();
}
private void OnBodyEntered(Node2D body)
{
if (_target != null) return;
if (body == null) return;
_target = body;
}
}

View File

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

View File

@@ -0,0 +1,70 @@
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class IceEffectComponent : Node
{
[Export] public Array<Node> ComponentsToDisable { get; set; } = [];
[Export] public StatusEffectComponent StatusEffectComponent { get; set; }
[Export] public Node2D IceFx { get; set; }
private StatusEffectDataResource _data = null;
private int _iceEffectsApplied = 0;
public override void _Ready()
{
StatusEffectComponent.EffectApplied += OnEffectApplied;
StatusEffectComponent.EffectRemoved += OnEffectRemoved;
}
private void OnEffectApplied(StatusEffect statusEffect)
{
if (statusEffect.EffectData.Type != StatusEffectType.Ice) return;
_data = statusEffect.EffectData;
_iceEffectsApplied++;
ApplyFreeze();
}
private void OnEffectRemoved(StatusEffectType type)
{
if (type != StatusEffectType.Ice) return;
_data = null;
_iceEffectsApplied--;
RemoveFreeze();
}
private void ApplyFreeze()
{
if (IceFx != null)
{
IceFx.Visible = true;
}
foreach (var component in ComponentsToDisable)
{
if (component == null || _iceEffectsApplied == 0) continue;
component.ProcessMode = ProcessModeEnum.Disabled;
}
}
private void RemoveFreeze()
{
if (_iceEffectsApplied > 0) return;
if (IceFx != null)
{
IceFx.Visible = false;
}
foreach (var component in ComponentsToDisable)
{
if (component == null) continue;
component.ProcessMode = ProcessModeEnum.Inherit;
}
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class InvulnerabilityComponent : Node
{
[Export] public float Duration { get; set; } = 1f;
[Export] public FlashingComponent FlashingComponent { get; set; }
private bool _isInvulnerable = false;
public void Activate()
{
if (_isInvulnerable)
return;
_isInvulnerable = true;
FlashingComponent?.StartFlashing();
var timer = GetTree().CreateTimer(Duration);
timer.Timeout += Deactivate;
}
private void Deactivate()
{
_isInvulnerable = false;
FlashingComponent?.StopFlashing();
}
public bool IsInvulnerable() => _isInvulnerable;
}

View File

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

View File

@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class JumpPadComponent : Node
{
[Export] public float JumpForce { get; set; } = 10f;
[Export] public Area2D Area { get; set; }
[Export] public Sprite2D Sprite { get; set; }
[Export] public int StartAnimationIndex { get; set; } = 0;
[Export] public float AnimationDuration { get; set; } = 0.5f;
public override void _Ready()
{
Area.BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)
{
var canBeLaunched = body.GetNodeOrNull<CanBeLaunchedComponent>("CanBeLaunchedComponent");
if (canBeLaunched == null) return;
if (body is not PlayerController { CurrentMovement: PlatformMovementComponent movement }) return;
_ = HandleLaunchPadAnimation();
movement.Body.Velocity = new Vector2(movement.Body.Velocity.X, -JumpForce);
movement.JumpSfx?.Play();
}
private async Task HandleLaunchPadAnimation()
{
if (Sprite == null) return;
var timer = GetTree().CreateTimer(AnimationDuration);
Sprite.Frame = StartAnimationIndex + 1;
await ToSignal(timer, Timer.SignalName.Timeout);
Sprite.Frame = StartAnimationIndex;
}
}

View File

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

View File

@@ -0,0 +1,21 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class KillPlayerOutOfScreenComponent : Node
{
[Export] public VisibleOnScreenNotifier2D ScreenNotifier { get; set; }
[Export] public HealthComponent HealthComponent { get; set; }
private const float Damage = 6000f;
public override void _Ready()
{
ScreenNotifier.ScreenExited += HandleOutOfScreen;
}
private void HandleOutOfScreen()
{
HealthComponent?.DecreaseHealth(Damage);
}
}

View File

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

View File

@@ -0,0 +1,48 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class KnockbackComponent : Node
{
[Export] public CharacterBody2D Body { get; set; }
[Export] public float KnockbackForce { get; set; } = 25f;
[Export] public HealthComponent HealthComponent { get; set; }
private bool _knockbackMode = false;
private int _knockbackFrames = 0;
public override void _Ready()
{
HealthComponent.HealthChanged += OnHealthChanged;
}
public override void _Process(double delta)
{
if (_knockbackMode) _knockbackFrames++;
if (_knockbackFrames <= 1) return;
_knockbackMode = false;
_knockbackFrames = 0;
}
public override void _PhysicsProcess(double delta)
{
if (_knockbackMode) ApplyKnockback((float)delta);
}
private void OnHealthChanged(float delta, float totalHealth)
{
if (totalHealth <= 0f || delta >= 0f) return;
_knockbackMode = true;
}
private void ApplyKnockback(float delta)
{
var velocity = Body.Velocity.Normalized();
var knockbackDirection = new Vector2(Mathf.Sign(velocity.X), 0.4f);
var knockbackVector = -knockbackDirection * KnockbackForce * delta;
Body.Velocity += knockbackVector;
}
}

View File

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

View File

@@ -0,0 +1,24 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class LaunchComponent : Node2D
{
[Export] public Vector2 InitialDirection { get; set; } = Vector2.Right;
[Export] public float Speed { get; set; } = 16f;
[Export] public Vector2 SpawnPosition { get; set; } = Vector2.Zero;
[Export] public float SpawnRotation { get; set; } = 0f;
public override void _Ready()
{
if (Owner is not Node2D root) return;
root.GlobalPosition = SpawnPosition;
root.GlobalRotation = SpawnRotation;
}
public Vector2 GetInitialVelocity()
{
return InitialDirection.Normalized() * Speed;
}
}

View File

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

View File

@@ -0,0 +1,66 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class LeverComponent : Node
{
[Export] public Area2D Area { get; set; }
[Export] public Sprite2D Sprite { get; set; }
[Export] public int StartAnimationIndex { get; set; } = 0;
[Export] public float AnimationDuration { get; set; } = 0.5f;
[Export] public AudioStreamPlayer2D Sfx { get; set; }
[Signal]
public delegate void ActivatedEventHandler();
public override void _Ready()
{
if (Area == null)
{
GD.PushError("LeverComponent: Area is not set.");
return;
}
if (Sprite == null)
{
GD.PushError("LeverComponent: Sprite is not set.");
return;
}
Area.BodyEntered += OnBodyEntered;
Area.AreaEntered += OnAreaEntered;
}
private void OnAreaEntered(Area2D area)
{
HandleTriggerLogic(area);
}
private void OnBodyEntered(Node2D body)
{
HandleTriggerLogic(body);
}
private async Task Activate()
{
EmitSignalActivated();
Sfx?.Play();
Sprite.Frame = StartAnimationIndex + 1;
var timer = GetTree().CreateTimer(AnimationDuration);
await timer.ToSignal(timer, Timer.SignalName.Timeout);
Sprite.Frame = StartAnimationIndex;
}
private void HandleTriggerLogic(Node2D obj)
{
var triggerLever = obj.GetNodeOrNull<TriggerLeverComponent>("TriggerLeverComponent");
if (triggerLever == null)
{
GD.PushWarning("LeverComponent: TriggerLeverComponent not found in body.");
return;
}
_ = Activate();
}
}

View File

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

View File

@@ -0,0 +1,27 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class LifetimeComponent : Node
{
[Export] public float LifeTime { get; set; } = 5.0f;
private Timer _lifetimeTimer;
public override void _Ready()
{
_lifetimeTimer = new Timer();
_lifetimeTimer.WaitTime = LifeTime;
_lifetimeTimer.OneShot = true;
_lifetimeTimer.Autostart = true;
_lifetimeTimer.Timeout += OnLifetimeTimeout;
AddChild(_lifetimeTimer);
_lifetimeTimer.Start();
}
private void OnLifetimeTimeout()
{
Owner.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,78 @@
using System;
using Godot;
using Godot.Collections;
namespace Mr.BrickAdventures.scripts.components;
public partial class MagneticSkillComponent : Node
{
[Export] public Area2D MagneticArea { get; set; }
[Export] public float MagneticMoveDuration { get; set; } = 1.25f;
private Array<Node2D> _collectablesToPickUp = [];
public override void _Ready()
{
MagneticArea.AreaEntered += OnAreaEntered;
MagneticArea.BodyEntered += OnBodyEntered;
}
public override void _Process(double delta)
{
foreach (var collectable in _collectablesToPickUp)
{
if (!IsInstanceValid(collectable))
{
_collectablesToPickUp.Remove(collectable);
continue;
}
MoveCollectableToOwner(collectable);
}
}
private void OnBodyEntered(Node2D body)
{
if (!HasComponentInChildren(body, "Collectable")) return;
if (_collectablesToPickUp.Contains(body)) return;
_collectablesToPickUp.Add(body);
}
private void OnAreaEntered(Area2D area)
{
if (!HasComponentInChildren(area, "Collectable")) return;
if (_collectablesToPickUp.Contains(area)) return;
_collectablesToPickUp.Add(area);
}
private bool HasComponentInChildren(Node node, string componentName)
{
if (node == null) return false;
if (node.HasNode(componentName)) return true;
foreach (var child in node.GetChildren())
{
if (child is { } childNode && HasComponentInChildren(childNode, componentName))
{
return true;
}
}
return false;
}
private void MoveCollectableToOwner(Node2D collectable)
{
if (!IsInstanceValid(collectable)) return;
if (Owner is not Node2D root) return;
var direction = (root.GlobalPosition - collectable.GlobalPosition).Normalized();
var speed = direction.Length() / MagneticMoveDuration;
collectable.GlobalPosition += direction.Normalized() * speed;
}
}

View File

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

View File

@@ -0,0 +1,18 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class OutOfScreenComponent : Node
{
[Export] public VisibleOnScreenNotifier2D VisibilityNotifier { get; set; }
public override void _Ready()
{
VisibilityNotifier.ScreenExited += OnScreenExited;
}
private void OnScreenExited()
{
Owner?.QueueFree();
}
}

View File

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

View File

@@ -0,0 +1,71 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class PeriodicShootingComponent : Node
{
[Export] public PackedScene BulletScene { get; set; }
[Export] public float ShootInterval { get; set; } = 1.0f;
[Export] public Vector2 ShootDirection { get; set; } = Vector2.Right;
[Export] public SideToSideMovementComponent SideToSideMovement { get; set; }
[Export] public Node2D BulletSpawnRight { get; set; }
[Export] public Node2D BulletSpawnLeft { get; set; }
[Export] public float ShootingIntervalVariation { get; set; } = 0.0f;
private Timer _timer;
public override void _Ready()
{
SetupTimer();
}
public override void _Process(double delta)
{
if (SideToSideMovement == null) return;
ShootDirection = SideToSideMovement.Direction != Vector2.Zero ? SideToSideMovement.Direction : Vector2.Right;
}
private void SetupTimer()
{
_timer = new Timer();
_timer.WaitTime = GetShootInterval();
_timer.OneShot = false;
_timer.Autostart = true;
_timer.Timeout += OnTimerTimeout;
AddChild(_timer);
}
private void OnTimerTimeout()
{
Shoot();
_timer.Start();
}
private double GetShootInterval()
{
if (ShootingIntervalVariation == 0f) return ShootInterval;
var rng = new RandomNumberGenerator();
return ShootInterval + rng.RandfRange(-ShootingIntervalVariation, ShootingIntervalVariation);
}
private void Shoot()
{
if (ShootDirection == Vector2.Zero) return;
var root = Owner as Node2D;
var bulletInstance = BulletScene.Instantiate<Node2D>();
var launchComponent = bulletInstance.GetNodeOrNull<LaunchComponent>("LaunchComponent");
var spawnPosition = ShootDirection == Vector2.Right ? BulletSpawnRight.GlobalPosition : BulletSpawnLeft.GlobalPosition;
if (launchComponent != null)
{
launchComponent.InitialDirection = ShootDirection;
launchComponent.SpawnPosition = spawnPosition;
if (root != null) launchComponent.SpawnRotation = root.Rotation;
}
bulletInstance.Position = spawnPosition;
GetTree().CurrentScene.AddChild(bulletInstance);
}
}

View File

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

View File

@@ -0,0 +1,171 @@
using Godot;
using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.components;
public partial class PlatformMovementComponent : Node2D, IMovement
{
[Export]
public float Speed { get; set; } = 300.0f;
[Export]
public float JumpHeight { get; set; } = 100f;
[Export]
public float JumpTimeToPeak { get; set; } = 0.5f;
[Export]
public float JumpTimeToDescent { get; set; } = 0.4f;
[Export]
public int CoyoteFrames { get; set; } = 6;
[Export]
public AudioStreamPlayer2D JumpSfx { get; set; }
[Export]
public Node2D RotationTarget { get; set; }
[Export]
public CharacterBody2D Body { get; set; }
private float _gravity;
private bool _wasLastFloor = false;
private bool _coyoteMode = false;
private Timer _coyoteTimer;
private Vector2 _lastDirection = new Vector2(1, 0);
private float _jumpVelocity;
private float _jumpGravity;
private float _fallGravity;
public Vector2 LastDirection => _lastDirection;
public override void _Ready()
{
base._Ready();
if (Body == null)
return;
_gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity");
_jumpVelocity = ((2.0f * JumpHeight) / JumpTimeToPeak) * -1.0f;
_jumpGravity = ((-2.0f * JumpHeight) / (JumpTimeToPeak * JumpTimeToPeak)) * -1.0f;
_fallGravity = ((-2.0f * JumpHeight) / (JumpTimeToDescent * JumpTimeToDescent)) * -1.0f;
_coyoteTimer = new Timer
{
OneShot = true,
WaitTime = CoyoteFrames / 60.0f
};
_coyoteTimer.Timeout += OnCoyoteTimerTimeout;
AddChild(_coyoteTimer);
}
public string MovementType { get; } = "platform";
public bool Enabled { get; set; }
public Vector2 PreviousVelocity { get; set; }
public override void _Process(double delta)
{
base._Process(delta);
if (Body == null || !Enabled)
return;
if (Body.Velocity.X > 0.0f)
RotationTarget.Rotation = Mathf.DegToRad(-10);
else if (Body.Velocity.X < 0.0f)
RotationTarget.Rotation = Mathf.DegToRad(10);
else
RotationTarget.Rotation = 0;
CalculateJumpVars();
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
if (Body == null || !Enabled)
return;
if (Body.IsOnFloor())
{
_wasLastFloor = true;
_coyoteMode = false; // Reset coyote mode when back on the floor
_coyoteTimer.Stop(); // Stop timer when grounded
}
else
{
if (_wasLastFloor) // Start coyote timer only once
{
_coyoteMode = true;
_coyoteTimer.Start();
}
_wasLastFloor = false;
}
if (!Body.IsOnFloor())
Body.Velocity += new Vector2(0, CalculateGravity()) * (float)delta;
if (Input.IsActionPressed("jump") && (Body.IsOnFloor() || _coyoteMode))
Jump();
if (Input.IsActionJustPressed("down"))
Body.Position += new Vector2(0, 1);
float direction = Input.GetAxis("left", "right");
if (direction != 0)
_lastDirection = HandleDirection(direction);
if (direction != 0)
Body.Velocity = new Vector2(direction * Speed, Body.Velocity.Y);
else
Body.Velocity = new Vector2(Mathf.MoveToward(Body.Velocity.X, 0, Speed), Body.Velocity.Y);
Body.MoveAndSlide();
}
private void Jump()
{
if (Body == null)
return;
Body.Velocity = new Vector2(Body.Velocity.X, _jumpVelocity);
_coyoteMode = false;
if (JumpSfx != null)
JumpSfx.Play();
}
private float CalculateGravity()
{
return Body.Velocity.Y < 0.0f ? _jumpGravity : _fallGravity;
}
private void OnCoyoteTimerTimeout()
{
_coyoteMode = false;
}
private Vector2 HandleDirection(float inputDir)
{
if (inputDir > 0)
return new Vector2(1, 0);
else if (inputDir < 0)
return new Vector2(-1, 0);
return _lastDirection;
}
public void OnShipEntered()
{
RotationTarget.Rotation = 0;
}
private void CalculateJumpVars()
{
_jumpVelocity = ((2.0f * JumpHeight) / JumpTimeToPeak) * -1.0f;
_jumpGravity = ((-2.0f * JumpHeight) / (JumpTimeToPeak * JumpTimeToPeak)) * -1.0f;
_fallGravity = ((-2.0f * JumpHeight) / (JumpTimeToDescent * JumpTimeToDescent)) * -1.0f;
}
}

View File

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

View File

@@ -0,0 +1,103 @@
using System.Collections.Generic;
using Godot;
using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.components;
public partial class PlayerController : Node2D
{
[Export]
public string DefaultMovementType { get; set; } = "platform";
[Export]
public Godot.Collections.Dictionary<string, NodePath> MovementTypes { get; set; }
[Export]
public Sprite2D ShipSprite { get; set; }
public IMovement CurrentMovement = null;
[Signal]
public delegate void MovementSwitchedEventHandler(string movementType);
public override void _Ready()
{
base._Ready();
foreach (var movementType in MovementTypes.Keys)
{
var movementNode = GetNodeOrNull(movementType);
if (movementNode is IMovement playerMovement)
{
playerMovement.Enabled = false;
}
}
SwitchMovement(DefaultMovementType);
}
public override void _UnhandledInput(InputEvent @event)
{
base._UnhandledInput(@event);
if (@event is InputEventKey inputEventKey && inputEventKey.IsActionPressed("switch_movement"))
{
var nextMovementType = GetNextMovementType();
SwitchMovement(nextMovementType);
}
}
private void SwitchMovement(string movementType)
{
if (CurrentMovement != null)
{
CurrentMovement.Enabled = false;
}
if (MovementTypes.TryGetValue(movementType, out var movement))
{
CurrentMovement = GetNodeOrNull<IMovement>(movement);
if (CurrentMovement == null)
{
GD.PushError($"Movement type '{movementType}' not found in MovementTypes.");
return;
}
CurrentMovement.Enabled = true;
EmitSignalMovementSwitched(movementType);
}
else
{
GD.PushError($"Movement type '{movementType}' not found in MovementTypes.");
}
if (CurrentMovement == null)
{
GD.PushError("No current movement set after switching.");
}
}
private string GetNextMovementType()
{
var keys = new List<string>(MovementTypes.Keys);
var currentIndex = keys.IndexOf(CurrentMovement?.MovementType);
if (currentIndex == -1)
{
return DefaultMovementType;
}
currentIndex = (currentIndex + 1) % keys.Count;
return keys[currentIndex];
}
public void OnSpaceshipEntered()
{
SwitchMovement("ship");
ShipSprite.Visible = true;
}
public void OnSpaceshipExited()
{
SwitchMovement(DefaultMovementType);
ShipSprite.Visible = false;
}
}

View File

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

View File

@@ -0,0 +1,36 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.components;
public partial class PlayerDeathComponent : Node2D
{
[Export] public AudioStreamPlayer2D DeathSfx { get; set; }
[Export] public PackedScene DeathEffect { get; set; }
[Export] public HealthComponent HealthComponent { get; set; }
[Export] public Vector2 EffectScale { get; set; } = new Vector2(1.5f, 1.5f);
private GameManager _gameManager;
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/gameManager");
HealthComponent.Death += OnDeath;
}
private void OnDeath()
{
DeathSfx?.Play();
if (DeathEffect != null)
{
var effect = DeathEffect.Instantiate<Node2D>();
GetParent().AddChild(effect);
effect.GlobalPosition = GlobalPosition;
effect.Scale = EffectScale;
}
_gameManager.RemoveLives(1);
_gameManager.ResetCurrentSessionState();
}
}

View File

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

View File

@@ -0,0 +1,65 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ProgressiveDamageComponent : Node
{
[Export] public HealthComponent HealthComponent { get; set; }
[Export] public Sprite2D Sprite { get; set; }
[Export] public PlatformMovementComponent PlatformMovement { get; set; }
[Export] public float MinJumpHeight { get; set; } = 60f;
[Export] public float JumpReductionPercentage { get; set; } = 0.1f; // this is a percentage of the jump height per hit
private float _maxHealth;
private float _ogJumpHeight;
public override void _Ready()
{
_maxHealth = HealthComponent.MaxHealth;
HealthComponent.HealthChanged += OnHealthChanged;
if (PlatformMovement != null)
{
_ogJumpHeight = PlatformMovement.JumpHeight;
}
}
private void OnHealthChanged(float delta, float totalHealth)
{
var frame = GetDamageFrame();
if (frame < 0 || frame >= Sprite.GetHframes()) return;
Sprite.Frame = frame;
if (PlatformMovement != null)
{
PlatformMovement.JumpHeight = GetJumpHeight();
}
}
private int GetDamageFrame()
{
if (Sprite == null || HealthComponent == null) return 0;
var framesCount = Sprite.GetHframes();
if (framesCount == 0) return 0;
var currentHealth = HealthComponent.Health;
var healthRatio = currentHealth / _maxHealth;
return (int)(framesCount * (1f - healthRatio));
}
private float GetJumpHeight()
{
if (PlatformMovement == null) return 0f;
var jumpHeight = _ogJumpHeight;
if (jumpHeight <= 0f) return 0f;
var damageFrame = GetDamageFrame();
if (damageFrame < 0 || damageFrame >= Sprite.GetHframes()) return jumpHeight;
var reduction = JumpReductionPercentage * jumpHeight;
var calculatedJumpHeight = jumpHeight - (damageFrame * reduction);
return Mathf.Max(calculatedJumpHeight, MinJumpHeight);
}
}

View File

@@ -0,0 +1 @@
uid://3qy7rm28q66a

View File

@@ -0,0 +1,26 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ProjectileComponent : Node2D
{
[Export] public float Speed { get; set; } = 16f;
[Export] public float AngleDirection { get; set; }
[Export] public Vector2 SpawnPosition { get; set; } = Vector2.Zero;
[Export] public float SpawnRotation { get; set; } = 0f;
[Export] public CharacterBody2D Body { get; set; }
public override void _Ready()
{
GlobalPosition = SpawnPosition;
GlobalRotation = SpawnRotation;
}
public override void _PhysicsProcess(double delta)
{
if (Body == null) return;
Body.Velocity += new Vector2(0f, -Speed).Rotated(AngleDirection);
Body.MoveAndSlide();
}
}

View File

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

View File

@@ -0,0 +1,37 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ProjectileInitParams
{
public Vector2 Position { get; set; } = Vector2.Zero;
public Vector2 Direction { get; set; } = Vector2.Right;
public float Rotation { get; set; } = 0f;
public float PowerMultiplier { get; set; } = 1f;
}
public partial class ProjectileInitComponent : Node
{
[Export] public LaunchComponent LaunchComponent { get; set; }
public void Initialize(ProjectileInitParams p)
{
var position = p.Position;
var direction = p.Direction;
var rotation = p.Rotation;
var power = p.PowerMultiplier;
if (Owner is Node2D root)
{
root.GlobalPosition = position;
root.GlobalRotation = rotation;
}
if (LaunchComponent == null) return;
LaunchComponent.InitialDirection = direction;
LaunchComponent.SpawnPosition = position;
LaunchComponent.SpawnRotation = rotation;
LaunchComponent.Speed *= power;
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class RequirementComponent : Node
{
[Export] public CollectableType RequirementType { get; set; }
[Export] public int RequirementAmount { get; set; } = 1;
private int _currentAmount = 0;
private const string CollectableGroupName = "Collectables";
[Signal]
public delegate void RequirementMetEventHandler(CollectableType requirementType);
public override void _Ready()
{
var collectables = GetTree().GetNodesInGroup(CollectableGroupName);
foreach (var collectable in collectables)
{
var c = collectable.GetNodeOrNull<CollectableComponent>("CollectableComponent");
if (c != null && c.Data.Type == RequirementType)
{
c.Collected += OnCollected;
}
}
}
private void OnCollected(Variant amount, CollectableType type, Node2D body)
{
AddProgress(amount.As<int>());
}
private void AddProgress(int amount = 1)
{
_currentAmount += amount;
if (_currentAmount >= RequirementAmount)
{
EmitSignalRequirementMet(RequirementType);
}
}
}

View File

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

View File

@@ -0,0 +1,42 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class ScoreComponent : Node
{
private GameManager _gameManager;
private const string CoinsGroupName = "Coins";
public override async void _Ready()
{
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
_gameManager = GetNode<GameManager>("/root/GameManager");
if (_gameManager == null)
{
GD.PrintErr("GameManager not found in the scene tree.");
return;
}
var coins = GetTree().GetNodesInGroup("Coins");
foreach (var coin in coins)
{
var c = coin.GetNodeOrNull<CollectableComponent>("CollectableComponent");
if (c != null)
{
c.Collected += OnCollected;
}
}
}
private void OnCollected(Variant amount, CollectableType type, Node2D body)
{
if (type != CollectableType.Coin) return;
var coinAmount = amount.As<int>();
var currentCoins = (int)_gameManager.CurrentSessionState["coins_collected"];
_gameManager.CurrentSessionState["coins_collected"] = currentCoins + coinAmount;
}
}

View File

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

View File

@@ -0,0 +1,38 @@
using Godot;
using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.components;
public partial class ShipMovementComponent : Node, IMovement
{
[Export] public float MaxSpeed { get; set; } = 200f;
[Export] public float Acceleration { get; set; } = 100f;
[Export] public float Friction { get; set; } = 50f;
[Export] public CharacterBody2D Body { get; set; }
public string MovementType { get; } = "ship";
public bool Enabled { get; set; }
public Vector2 PreviousVelocity { get; set; }
private Vector2 _velocity = Vector2.Zero;
public Vector2 Velocity => _velocity;
public Vector2 LastDirection => _velocity.Normalized();
public override void _PhysicsProcess(double delta)
{
if (Body == null || !Enabled) return;
var inputVector = new Vector2(
Input.GetActionStrength("right") - Input.GetActionStrength("left"),
Input.GetActionStrength("down") - Input.GetActionStrength("up")
).Normalized();
_velocity = inputVector != Vector2.Zero ? _velocity.MoveToward(inputVector * MaxSpeed, Acceleration * (float)delta) : _velocity.MoveToward(Vector2.Zero, Friction * (float)delta);
_velocity = _velocity.LimitLength(MaxSpeed);
Body.Velocity = _velocity;
PreviousVelocity = Body.Velocity;
Body.MoveAndSlide();
}
}

View File

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

View File

@@ -0,0 +1,59 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ShipShooterComponent : Node
{
[Export] public PackedScene BulletScene { get; set; }
[Export] public float FireRate { get; set; } = 0.2f;
[Export] public Marker2D BulletSpawn { get; set; }
[Export] public AudioStreamPlayer2D ShootSfx { get; set; }
private bool _canShoot = false;
public override void _Ready()
{
SetProcess(false);
}
public override void _Process(double delta)
{
if (Input.IsActionJustPressed("attack") && _canShoot)
{
_ = Shoot();
}
}
private async Task Shoot()
{
if (!_canShoot) return;
var bullet = BulletScene.Instantiate<Node2D>();
var init = bullet.GetNodeOrNull<ProjectileInitComponent>("ProjectileInitComponent");
init?.Initialize(new ProjectileInitParams()
{
Position = BulletSpawn.GlobalPosition,
});
GetTree().CurrentScene.AddChild(bullet);
ShootSfx?.Play();
_canShoot = false;
await ToSignal(GetTree().CreateTimer(FireRate), Timer.SignalName.Timeout);
_canShoot = true;
}
private void OnShipEntered()
{
_canShoot = true;
SetProcess(true);
}
private void OnShipExited()
{
_canShoot = false;
SetProcess(false);
ShootSfx?.Stop();
}
}

View File

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

View File

@@ -0,0 +1,117 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class SideToSideMovementComponent : Node
{
[Export] public Sprite2D Sprite { get; set; }
[Export] public float Speed { get; set; } = 10.0f;
[Export] public float WaitTime { get; set; } = 1.0f;
[Export] public RayCast2D LeftRay { get; set; }
[Export] public RayCast2D RightRay { get; set; }
[Export] public RayCast2D LeftWallRay { get; set; }
[Export] public RayCast2D RightWallRay { get; set; }
private Vector2 _direction = Vector2.Left;
private Vector2 _newDirection = Vector2.Left;
private Timer _timer;
private bool _triggeredDirectionChange = false;
[Signal]
public delegate void DirectionChangedEventHandler();
public Vector2 Direction => _direction;
public override void _Ready()
{
SetupTimer();
DirectionChanged += OnDirectionChanged;
}
public override void _PhysicsProcess(double delta)
{
HandleDirection();
HandleSpriteFlip();
HandleMovement(delta);
}
private void HandleDirection()
{
// Check if we are colliding with the left wall
if (LeftWallRay.IsColliding())
{
_newDirection = Vector2.Right;
EmitSignalDirectionChanged();
return;
}
// Check if we are colliding with the right wall
if (RightWallRay.IsColliding())
{
_newDirection = Vector2.Left;
EmitSignalDirectionChanged();
return;
}
// We are not colliding with anything, which means we don't have ground to walk on. Stop moving.
if (!LeftRay.IsColliding() && !RightRay.IsColliding())
{
_newDirection = Vector2.Zero;
return;
}
// If the left ray is not colliding and the right ray is colliding, that means we have ground to the right and we should change direction to the right.
if (!LeftRay.IsColliding() && RightRay.IsColliding())
{
_newDirection = Vector2.Right;
EmitSignalDirectionChanged();
return;
}
// If the right ray is not colliding and the left ray is colliding, that means we have ground to the left and we should change direction to the left.
if (!RightRay.IsColliding() && LeftRay.IsColliding())
{
_newDirection = Vector2.Left;
EmitSignalDirectionChanged();
return;
}
}
private void HandleSpriteFlip()
{
Sprite.FlipH = _direction == Vector2.Left;
}
private void HandleMovement(double delta)
{
var root = Owner as Node2D;
if (root == null) return;
root.Position += _direction * Speed * (float)delta;
}
private void OnDirectionChanged()
{
if (_direction == _newDirection || _triggeredDirectionChange)
return;
_triggeredDirectionChange = true;
_direction = Vector2.Zero;
_timer.Start();
}
private void OnTimerTimeout()
{
_timer.Stop();
_direction = _newDirection;
_triggeredDirectionChange = false;
}
private void SetupTimer()
{
_timer = new Timer();
AddChild(_timer);
_timer.WaitTime = WaitTime;
_timer.OneShot = true;
_timer.Timeout += OnTimerTimeout;
}
}

View File

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

View File

@@ -0,0 +1,70 @@
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class SkillUnlockedComponent : Node
{
[Export] public SkillManager SkillManager { get; set; }
[Signal]
public delegate void SkillUnlockedEventHandler(SkillData skill);
private GameManager _gameManager;
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
}
private bool HasEnoughCoins(int amount)
{
return _gameManager != null && _gameManager.GetCoins() >= amount;
}
public bool TryUnlockSkill(SkillData skill)
{
if (_gameManager == null) return false;
if (_gameManager.IsSkillUnlocked(skill)) return false;
if (!HasEnoughCoins(skill.Cost)) return false;
skill.Level = 1;
skill.IsActive = true;
_gameManager.RemoveCoins(skill.Cost);
var skillsUnlocked = (Array<SkillData>)_gameManager.CurrentSessionState["skills_unlocked"];
skillsUnlocked.Add(skill);
SkillManager.AddSkill(skill);
EmitSignalSkillUnlocked(skill);
return true;
}
public void UnlockAllSkills()
{
var availableSkills = SkillManager.AvailableSkills;
foreach (var skill in availableSkills)
{
EmitSignalSkillUnlocked(skill);
}
_gameManager.UnlockSkills(availableSkills);
SkillManager.ApplyUnlockedSkills();
}
public bool TryUpgradeSkill(SkillData skill)
{
if (_gameManager == null) return false;
if (!_gameManager.IsSkillUnlocked(skill)) return false;
if (!HasEnoughCoins(skill.Cost)) return false;
if (skill.Level >= skill.MaxLevel) return false;
_gameManager.RemoveCoins(skill.Cost);
skill.Level++;
EmitSignalSkillUnlocked(skill);
return true;
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More