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:
109
scripts/components/BeamComponent.cs
Normal file
109
scripts/components/BeamComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/BeamComponent.cs.uid
Normal file
1
scripts/components/BeamComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://df1llrbm80e02
|
70
scripts/components/BrickThrowComponent.cs
Normal file
70
scripts/components/BrickThrowComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/BrickThrowComponent.cs.uid
Normal file
1
scripts/components/BrickThrowComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b0bv8kw5w5037
|
45
scripts/components/BulletComponent.cs
Normal file
45
scripts/components/BulletComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/BulletComponent.cs.uid
Normal file
1
scripts/components/BulletComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cfw8nbrarex0i
|
47
scripts/components/CageComponent.cs
Normal file
47
scripts/components/CageComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/CageComponent.cs.uid
Normal file
1
scripts/components/CageComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dojn0gw8hsv02
|
8
scripts/components/CanBeLaunchedComponent.cs
Normal file
8
scripts/components/CanBeLaunchedComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class CanBeLaunchedComponent : Node
|
||||
{
|
||||
|
||||
}
|
1
scripts/components/CanBeLaunchedComponent.cs.uid
Normal file
1
scripts/components/CanBeLaunchedComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cjcc7fia15wu3
|
8
scripts/components/CanPickUpComponent.cs
Normal file
8
scripts/components/CanPickUpComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class CanPickUpComponent : Node
|
||||
{
|
||||
|
||||
}
|
1
scripts/components/CanPickUpComponent.cs.uid
Normal file
1
scripts/components/CanPickUpComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://mnjg3p0aw1ow
|
8
scripts/components/CannotStompComponent.cs
Normal file
8
scripts/components/CannotStompComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class CannotStompComponent : Node
|
||||
{
|
||||
|
||||
}
|
1
scripts/components/CannotStompComponent.cs.uid
Normal file
1
scripts/components/CannotStompComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dh67n16bnl838
|
79
scripts/components/ChaseLevelComponent.cs
Normal file
79
scripts/components/ChaseLevelComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/ChaseLevelComponent.cs.uid
Normal file
1
scripts/components/ChaseLevelComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dnpj72mfi1ywl
|
11
scripts/components/CleanupComponent.cs
Normal file
11
scripts/components/CleanupComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class CleanupComponent : Node
|
||||
{
|
||||
public void CleanUp()
|
||||
{
|
||||
Owner.QueueFree();
|
||||
}
|
||||
}
|
1
scripts/components/CleanupComponent.cs.uid
Normal file
1
scripts/components/CleanupComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://v7tt4w6bejux
|
81
scripts/components/CollapsableComponent.cs
Normal file
81
scripts/components/CollapsableComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
1
scripts/components/CollapsableComponent.cs.uid
Normal file
1
scripts/components/CollapsableComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://xqhrb1c7f6y4
|
50
scripts/components/CollectableComponent.cs
Normal file
50
scripts/components/CollectableComponent.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/components/CollectableComponent.cs.uid
Normal file
1
scripts/components/CollectableComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://r4jybneigfcn
|
105
scripts/components/DamageComponent.cs
Normal file
105
scripts/components/DamageComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/DamageComponent.cs.uid
Normal file
1
scripts/components/DamageComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://2i7p7v135u7c
|
35
scripts/components/DestroyableComponent.cs
Normal file
35
scripts/components/DestroyableComponent.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
1
scripts/components/DestroyableComponent.cs.uid
Normal file
1
scripts/components/DestroyableComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ctfrbj52ejay4
|
27
scripts/components/EffectInflictorComponent.cs
Normal file
27
scripts/components/EffectInflictorComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
1
scripts/components/EffectInflictorComponent.cs.uid
Normal file
1
scripts/components/EffectInflictorComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://xjq33vj0rol0
|
42
scripts/components/EnemyDeathComponent.cs
Normal file
42
scripts/components/EnemyDeathComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/EnemyDeathComponent.cs.uid
Normal file
1
scripts/components/EnemyDeathComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cfdugoeduudar
|
51
scripts/components/EnemyWaveTriggerComponent.cs
Normal file
51
scripts/components/EnemyWaveTriggerComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/EnemyWaveTriggerComponent.cs.uid
Normal file
1
scripts/components/EnemyWaveTriggerComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d3fpwddc2j41x
|
52
scripts/components/ExitDoorComponent.cs
Normal file
52
scripts/components/ExitDoorComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/ExitDoorComponent.cs.uid
Normal file
1
scripts/components/ExitDoorComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c5mhwlyvfuaip
|
81
scripts/components/ExplosiveComponent.cs
Normal file
81
scripts/components/ExplosiveComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/ExplosiveComponent.cs.uid
Normal file
1
scripts/components/ExplosiveComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://7uttgdr6cr5y
|
32
scripts/components/FadeAwayComponent.cs
Normal file
32
scripts/components/FadeAwayComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/FadeAwayComponent.cs.uid
Normal file
1
scripts/components/FadeAwayComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bjln6jb1sigx2
|
71
scripts/components/FireEffectComponent.cs
Normal file
71
scripts/components/FireEffectComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
1
scripts/components/FireEffectComponent.cs.uid
Normal file
1
scripts/components/FireEffectComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cxuig4xh8nfov
|
83
scripts/components/FlashingComponent.cs
Normal file
83
scripts/components/FlashingComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/FlashingComponent.cs.uid
Normal file
1
scripts/components/FlashingComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dvyd26ricriql
|
36
scripts/components/FlipComponent.cs
Normal file
36
scripts/components/FlipComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/components/FlipComponent.cs.uid
Normal file
1
scripts/components/FlipComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dy78ak8eykw6e
|
37
scripts/components/GravityMotionComponent.cs
Normal file
37
scripts/components/GravityMotionComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/components/GravityMotionComponent.cs.uid
Normal file
1
scripts/components/GravityMotionComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cwi5qashdag1g
|
48
scripts/components/HealComponent.cs
Normal file
48
scripts/components/HealComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/HealComponent.cs.uid
Normal file
1
scripts/components/HealComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bmx6rk281yim2
|
65
scripts/components/HealthComponent.cs
Normal file
65
scripts/components/HealthComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/components/HealthComponent.cs.uid
Normal file
1
scripts/components/HealthComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dgb8bqcri7nsj
|
75
scripts/components/HitComponent.cs
Normal file
75
scripts/components/HitComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/HitComponent.cs.uid
Normal file
1
scripts/components/HitComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bo506l4x0808e
|
64
scripts/components/HomingMissileMotionComponent.cs
Normal file
64
scripts/components/HomingMissileMotionComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/HomingMissileMotionComponent.cs.uid
Normal file
1
scripts/components/HomingMissileMotionComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c2hplha6af74q
|
70
scripts/components/IceEffectComponent.cs
Normal file
70
scripts/components/IceEffectComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/components/IceEffectComponent.cs.uid
Normal file
1
scripts/components/IceEffectComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d1388lhp2gpgr
|
31
scripts/components/InvulnerabilityComponent.cs
Normal file
31
scripts/components/InvulnerabilityComponent.cs
Normal 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;
|
||||
}
|
1
scripts/components/InvulnerabilityComponent.cs.uid
Normal file
1
scripts/components/InvulnerabilityComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cecelixl41t3j
|
39
scripts/components/JumpPadComponent.cs
Normal file
39
scripts/components/JumpPadComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/JumpPadComponent.cs.uid
Normal file
1
scripts/components/JumpPadComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bgbnof7aeydmq
|
21
scripts/components/KillPlayerOutOfScreenComponent.cs
Normal file
21
scripts/components/KillPlayerOutOfScreenComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
1
scripts/components/KillPlayerOutOfScreenComponent.cs.uid
Normal file
1
scripts/components/KillPlayerOutOfScreenComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://diw6opv6yutgi
|
48
scripts/components/KnockbackComponent.cs
Normal file
48
scripts/components/KnockbackComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/KnockbackComponent.cs.uid
Normal file
1
scripts/components/KnockbackComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cgfynrn68lp12
|
24
scripts/components/LaunchComponent.cs
Normal file
24
scripts/components/LaunchComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/LaunchComponent.cs.uid
Normal file
1
scripts/components/LaunchComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbexrnnj47f87
|
66
scripts/components/LeverComponent.cs
Normal file
66
scripts/components/LeverComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/LeverComponent.cs.uid
Normal file
1
scripts/components/LeverComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://1oo22ieply7n
|
27
scripts/components/LifetimeComponent.cs
Normal file
27
scripts/components/LifetimeComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/LifetimeComponent.cs.uid
Normal file
1
scripts/components/LifetimeComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://oyf25mpc5etr
|
78
scripts/components/MagneticSkillComponent.cs
Normal file
78
scripts/components/MagneticSkillComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/MagneticSkillComponent.cs.uid
Normal file
1
scripts/components/MagneticSkillComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bi5nx8s1gisbd
|
18
scripts/components/OutOfScreenComponent.cs
Normal file
18
scripts/components/OutOfScreenComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/OutOfScreenComponent.cs.uid
Normal file
1
scripts/components/OutOfScreenComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cs6u3sh68f43j
|
71
scripts/components/PeriodicShootingComponent.cs
Normal file
71
scripts/components/PeriodicShootingComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
1
scripts/components/PeriodicShootingComponent.cs.uid
Normal file
1
scripts/components/PeriodicShootingComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bnaxy8cw3wrko
|
171
scripts/components/PlatformMovementComponent.cs
Normal file
171
scripts/components/PlatformMovementComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/PlatformMovementComponent.cs.uid
Normal file
1
scripts/components/PlatformMovementComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://btlm1f3l70il
|
103
scripts/components/PlayerController.cs
Normal file
103
scripts/components/PlayerController.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/PlayerController.cs.uid
Normal file
1
scripts/components/PlayerController.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csel4s0e4g5uf
|
36
scripts/components/PlayerDeathComponent.cs
Normal file
36
scripts/components/PlayerDeathComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/PlayerDeathComponent.cs.uid
Normal file
1
scripts/components/PlayerDeathComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://byw1legrv1ep2
|
65
scripts/components/ProgressiveDamageComponent.cs
Normal file
65
scripts/components/ProgressiveDamageComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
1
scripts/components/ProgressiveDamageComponent.cs.uid
Normal file
1
scripts/components/ProgressiveDamageComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://3qy7rm28q66a
|
26
scripts/components/ProjectileComponent.cs
Normal file
26
scripts/components/ProjectileComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/ProjectileComponent.cs.uid
Normal file
1
scripts/components/ProjectileComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bh31avqjbniik
|
37
scripts/components/ProjectileInitComponent.cs
Normal file
37
scripts/components/ProjectileInitComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/ProjectileInitComponent.cs.uid
Normal file
1
scripts/components/ProjectileInitComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c7n6ecsobohjn
|
44
scripts/components/RequirementComponent.cs
Normal file
44
scripts/components/RequirementComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
1
scripts/components/RequirementComponent.cs.uid
Normal file
1
scripts/components/RequirementComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dnh0mekg2vqxi
|
42
scripts/components/ScoreComponent.cs
Normal file
42
scripts/components/ScoreComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/ScoreComponent.cs.uid
Normal file
1
scripts/components/ScoreComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccqb8kd5m0eh7
|
38
scripts/components/ShipMovementComponent.cs
Normal file
38
scripts/components/ShipMovementComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/ShipMovementComponent.cs.uid
Normal file
1
scripts/components/ShipMovementComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cty54itmnudfm
|
59
scripts/components/ShipShooterComponent.cs
Normal file
59
scripts/components/ShipShooterComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/components/ShipShooterComponent.cs.uid
Normal file
1
scripts/components/ShipShooterComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dr3uv0j7n75s
|
117
scripts/components/SideToSideMovementComponent.cs
Normal file
117
scripts/components/SideToSideMovementComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/SideToSideMovementComponent.cs.uid
Normal file
1
scripts/components/SideToSideMovementComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d2hrr8fruho1d
|
70
scripts/components/SkillUnlockedComponent.cs
Normal file
70
scripts/components/SkillUnlockedComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
1
scripts/components/SkillUnlockedComponent.cs.uid
Normal file
1
scripts/components/SkillUnlockedComponent.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dlh5xcv2sy82s
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user