Complete C# rewrite with working game in Editor (#6)

* Refactor collectable components to C# and update resource scripts for consistency

* Update resource paths and refactor properties for consistency

* Refactor UI components to inherit from Control and update node paths for consistency

* Update node paths and group assignments for consistency across scenes

* Refactor GameManager and PlayerDeathComponent for improved state management and logging; update scene connections for player death handling

* Add PhantomCamera components and UI elements for improved scene management; refactor existing components for better integration

* Refactor skill components and update resource paths for consistency; enhance skill management in scenes

* Add new UID files and update scene configurations for dialogue components; refactor skill management and input handling

* Add next level command and refactor player retrieval in GameManager; update scene files for consistency

* Add skill upgrade system and refactor skill components for enhanced functionality; update resource paths and configurations

* Enhance ChargeProgressBar and Marketplace functionality; add owner exit handling and update skill button states

* Refactor ChargeProgressBar and SkillManager; update skill handling and improve component interactions

* Refactor player and level configurations; streamline FlipPlayerComponent and reposition Spaceship Enter
This commit is contained in:
2025-08-27 01:12:26 +02:00
committed by GitHub
parent d84f7d1740
commit d786ef4c22
532 changed files with 22009 additions and 6630 deletions

View File

@@ -4,6 +4,6 @@ namespace Mr.BrickAdventures.scripts.Resources;
public partial class CollectableResource : Resource
{
[Export] public Variant Amount { get; set; } = 0.0;
[Export] public float Amount { get; set; } = 0.0f;
[Export] public CollectableType Type { get; set; }
}

View File

@@ -0,0 +1,13 @@
using Godot;
namespace Mr.BrickAdventures.scripts.Resources;
[GlobalClass]
public partial class LevelResource : Resource
{
[Export]
public string LevelName { get; set; } = string.Empty;
[Export]
public string ScenePath { get; set; } = string.Empty;
}

View File

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

View File

@@ -8,12 +8,12 @@ public partial class SkillData : Resource
{
[Export] public string Name { get; set; } = "New Skill";
[Export] public string Description { get; set; } = "New Skill";
[Export] public Dictionary<string, Variant> Config { get; set; } = new();
[Export] public int Cost { get; set; } = 0;
[Export] public Texture2D Icon { get; set; }
[Export] public bool IsActive { get; set; } = false;
[Export] public int Level { get; set; } = 1;
[Export] public int MaxLevel { get; set; } = 1;
[Export] public SkillType Type { get; set; } = SkillType.Throw;
[Export] public PackedScene Node { get; set; }
[Export] public Array<SkillUpgrade> Upgrades { get; set; } = [];
public int MaxLevel => Upgrades.Count;
}

View File

@@ -0,0 +1,12 @@
using Godot;
using Godot.Collections;
namespace Mr.BrickAdventures.scripts.Resources;
[GlobalClass]
public partial class SkillUpgrade : Resource
{
[Export] public int Cost { get; set; } = 50;
[Export(PropertyHint.MultilineText)] public string Description { get; set; } = "Upgrade Description";
[Export] public Dictionary<string, Variant> Properties { get; set; } = new();
}

View File

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

View File

@@ -1,13 +1,13 @@
using Godot;
using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.Resources;
public partial class TapThrowInputResource : ThrowInputResource
{
public override void Update(double delta)
public override void ProcessInput(InputEvent @event)
{
if (Input.IsActionPressed("attack"))
if (@event.IsActionPressed("attack"))
{
EmitSignalThrowRequested(1f);
}

View File

@@ -9,16 +9,14 @@ public abstract partial class ThrowInputResource : Resource, IThrowInput
public virtual void ProcessInput(InputEvent @event)
{
throw new System.NotImplementedException();
}
public virtual void Update(double delta)
{
throw new System.NotImplementedException();
}
public virtual bool SupportsCharging()
{
throw new System.NotImplementedException();
return false;
}
}

View File

@@ -1,6 +1,8 @@
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.components;
using Mr.BrickAdventures.scripts.interfaces;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts;
@@ -9,9 +11,12 @@ public partial class SkillManager : Node
{
private GameManager _gameManager;
[Export] public Array<SkillData> AvailableSkills { get; set; } = [];
public Dictionary ActiveComponents { get; private set; } = new();
[Signal]
public delegate void ActiveThrowSkillChangedEventHandler(BrickThrowComponent throwComponent);
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
@@ -26,56 +31,60 @@ public partial class SkillManager : Node
if (skillData.Type == SkillType.Throw)
{
var unlocked = _gameManager.GetUnlockedSkills();
foreach (var skill in unlocked)
foreach (var sd in unlocked)
{
SkillData data = null;
foreach (var s in AvailableSkills)
{
if (s == (SkillData)skill)
if (s == sd)
{
data = s;
break;
}
}
if (data != null && data.Type == SkillType.Throw)
if (data is { Type: SkillType.Throw })
RemoveSkill(data.Name);
}
}
var instance = skillData.Node.Instantiate();
foreach (var key in skillData.Config.Keys)
if (instance is ISkill skill)
{
if (instance.HasMethod("get")) // rough presence check
{
var value = skillData.Config[key];
var parent = GetParent();
if (value.VariantType == Variant.Type.NodePath)
{
var np = (NodePath)value;
if (parent.HasNode(np))
value = parent.GetNode(np);
else if (instance.HasNode(np))
value = instance.GetNode(np);
else
continue;
}
// Set via property if exists
instance.Set(key, value);
}
skill.Initialize(Owner, skillData);
skill.Activate();
}
else
{
GD.PrintErr($"Skill scene for '{skillData.Name}' does not implement ISkill!");
instance.QueueFree();
return;
}
Owner.AddChild(instance);
ActiveComponents[skillData.Name] = instance;
if (instance is BrickThrowComponent btc)
{
EmitSignalActiveThrowSkillChanged(btc);
}
}
public void RemoveSkill(string skillName)
{
if (!ActiveComponents.TryGetValue(skillName, out var component))
return;
if (component.AsGodotObject() is BrickThrowComponent)
{
EmitSignalActiveThrowSkillChanged(null);
}
var inst = (Node)component;
if (inst is ISkill skill)
{
skill.Deactivate();
}
if (IsInstanceValid(inst))
inst.QueueFree();
@@ -97,7 +106,6 @@ public partial class SkillManager : Node
{
if (_gameManager.IsSkillUnlocked(sd))
{
GD.Print("Applying skill: ", sd.Name);
CallDeferred(MethodName.AddSkill, sd);
}
else

View File

@@ -3,7 +3,7 @@ using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.UI;
public partial class AudioSettings : Node
public partial class AudioSettings : Control
{
[Export] public Slider MasterVolumeSlider { get; set; }
[Export] public Slider MusicVolumeSlider { get; set; }
@@ -22,8 +22,15 @@ public partial class AudioSettings : Node
MasterVolumeSlider.ValueChanged += OnMasterVolumeChanged;
MusicVolumeSlider.ValueChanged += OnMusicVolumeChanged;
SfxVolumeSlider.ValueChanged += OnSfxVolumeChanged;
LoadSettings();
}
public override void _ExitTree()
{
SaveSettings();
}
public override void _UnhandledInput(InputEvent @event)
{
if (!@event.IsActionReleased("ui_cancel")) return;

View File

@@ -1,33 +1,64 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.components;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.UI;
public partial class ChargeProgressBar : Node
public partial class ChargeProgressBar : ProgressBar
{
[Export] public ProgressBar ProgressBar { get; set; }
[Export] public BrickThrowComponent ThrowComponent { get; set; }
[Export] private SkillManager _skillManager;
private BrickThrowComponent _throwComponent;
private ChargeThrowInputResource _throwInput;
public override void _Ready()
{
Owner.ChildEnteredTree += OnNodeEntered;
ProgressBar.Hide();
if (_skillManager == null)
{
return;
}
_skillManager.ActiveThrowSkillChanged += OnActiveThrowSkillChanged;
SetupDependencies();
}
private void OnActiveThrowSkillChanged(BrickThrowComponent throwComponent)
{
OnOwnerExiting();
if (throwComponent == null) return;
_throwComponent = throwComponent;
_throwComponent.TreeExiting += OnOwnerExiting;
SetupDependencies();
}
private void OnNodeEntered(Node node)
private void OnOwnerExiting()
{
if (node is not BrickThrowComponent throwComponent || ThrowComponent != null) return;
ThrowComponent = throwComponent;
SetupDependencies();
if (_throwInput != null)
{
_throwInput.ChargeStarted -= OnChargeStarted;
_throwInput.ChargeStopped -= OnChargeStopped;
_throwInput.ChargeUpdated -= OnChargeUpdated;
_throwInput = null;
}
_throwComponent = null;
}
private void SetupDependencies()
{
if (ThrowComponent.ThrowInputBehavior is ChargeThrowInputResource throwInput)
if (_throwComponent == null || ProgressBar == null)
{
return;
}
if (_throwComponent.ThrowInputBehavior is ChargeThrowInputResource throwInput)
{
_throwInput = throwInput;
}

72
scripts/UI/DeathScreen.cs Normal file
View File

@@ -0,0 +1,72 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.UI;
[GlobalClass]
public partial class DeathScreen : Control
{
[Export] public LevelResource CurrentLevel { get; set; }
[Export] public Label CurrentLevelLabel { get; set; }
[Export] public Label LivesLeftLabel { get; set; }
[Export] public float TimeoutTime { get; set; } = 2.0f;
[Export] public Godot.Collections.Array<Node> NodesToDisable { get; set; } = new();
private GameManager _gameManager;
private Timer _timer;
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
SetLabels();
}
private void SetLabels()
{
if (_gameManager == null) return;
if (CurrentLevel != null)
{
CurrentLevelLabel.Text = CurrentLevel.LevelName;
}
LivesLeftLabel.Text = $" x {_gameManager.GetLives()}";
}
private void SetupTimer()
{
_timer = new Timer();
_timer.WaitTime = TimeoutTime;
_timer.OneShot = true;
_timer.Timeout += OnTimeout;
AddChild(_timer);
_timer.Start();
}
private void ToggleNodes()
{
foreach (var node in NodesToDisable)
{
node.ProcessMode = node.ProcessMode == ProcessModeEnum.Disabled
? ProcessModeEnum.Inherit
: ProcessModeEnum.Disabled;
}
}
public void OnPlayerDeath()
{
if (_gameManager == null) return;
ToggleNodes();
SetLabels();
Show();
SetupTimer();
}
private void OnTimeout()
{
if (_gameManager == null || _gameManager.GetLives() == 0) return;
GetTree().ReloadCurrentScene();
}
}

View File

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

View File

@@ -3,7 +3,7 @@ using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.UI;
public partial class GameOverScreen : Node
public partial class GameOverScreen : Control
{
[Export] public Control GameOverPanel { get; set; }
[Export] public Button RestartButton { get; set; }

View File

@@ -4,7 +4,7 @@ using Mr.BrickAdventures.scripts.components;
namespace Mr.BrickAdventures.scripts.UI;
public partial class Hud : Node
public partial class Hud : Control
{
[Export] public HealthComponent Health { get; set; }
[Export] public Label CoinsLabel { get; set; }

View File

@@ -3,7 +3,7 @@ using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.UI;
public partial class MainMenu : Node
public partial class MainMenu : Control
{
[Export] public Control MainMenuControl { get; set; }
[Export] public Button NewGameButton { get; set; }

View File

@@ -7,13 +7,13 @@ using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.UI;
public partial class Marketplace : Node
public partial class Marketplace : Control
{
[Export] public Array<SkillData> Skills { get; set; } = [];
[Export] public GridContainer ToUnlockGrid { get; set; }
[Export] public GridContainer UnlockedGrid { get; set; }
[Export] public Font Font { get; set; }
[Export] public SkillUnlockedComponent SkillUnlockedComponent { get; set; }
[Export] public SkillUnlockerComponent SkillUnlockerComponent { get; set; }
[Export] public Array<Node> ComponentsToDisable { get; set; } = [];
[Export] public PackedScene MarketplaceButtonScene { get; set; }
[Export] public PackedScene SkillButtonScene { get; set; }
@@ -24,6 +24,8 @@ public partial class Marketplace : Node
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
var skillsToUnlock = new List<SkillData>();
foreach (var skill in Skills) skillsToUnlock.Add(skill);
@@ -33,47 +35,48 @@ public partial class Marketplace : Node
var unlockedSkills = _gameManager.GetUnlockedSkills();
foreach (var skill in unlockedSkills) CreateSkillButton(skill);
SkillUnlockedComponent.SkillUnlocked += OnSkillUnlocked;
SkillUnlockerComponent.SkillUnlocked += OnSkillUnlocked;
}
public override void _ExitTree()
{
SkillUnlockedComponent.SkillUnlocked -= OnSkillUnlocked;
SkillUnlockerComponent.SkillUnlocked -= OnSkillUnlocked;
}
public override void _Input(InputEvent @event)
{
var root = Owner as Control;
if (!@event.IsActionPressed("show_marketplace")) return;
if (root != null && root.IsVisible())
if (IsVisible())
{
root.Hide();
Hide();
foreach (var c in ComponentsToDisable) c.ProcessMode = ProcessModeEnum.Inherit;
}
else
{
root?.Show();
Show();
foreach (var c in ComponentsToDisable) c.ProcessMode = ProcessModeEnum.Disabled;
}
}
private string GetButtonText(SkillData skill)
{
return $"{Tr(skill.Name)} {skill.Cost}";
}
private void OnSkillUnlocked(SkillData skill)
{
if (_skillButtons.Count == 0) CreateSkillButton(skill);
var buttonExists = false;
foreach (var existingButton in _skillButtons)
{
if (existingButton.Name == skill.Name)
{
buttonExists = true;
break;
}
}
if (!buttonExists) CreateSkillButton(skill);
foreach (var btn in _skillButtons)
{
if (btn.Data.IsActive)
btn.Activate();
else
btn.Deactivate();
if (btn.Data.IsActive) btn.Activate();
else btn.Deactivate();
}
}
@@ -93,14 +96,13 @@ public partial class Marketplace : Node
private void CreateUpgradeButton(SkillData skill)
{
var button = MarketplaceButtonScene.Instantiate<MarketplaceButton>();
button.Text = GetButtonText(skill);
button.Data = skill;
button.Icon = skill.Icon;
button.Pressed += () => OnUpgradeButtonPressed(skill);
_unlockButtons.Add(button);
UnlockedGrid.AddChild(button);
UnlockedGrid.QueueSort();
ToUnlockGrid.AddChild(button);
ToUnlockGrid.QueueSort();
}
private void OnUpgradeButtonPressed(SkillData skill)
@@ -109,34 +111,30 @@ public partial class Marketplace : Node
{
if (skill.Level < skill.MaxLevel)
{
SkillUnlockedComponent.TryUpgradeSkill(skill);
if (!skill.IsActive) SkillUnlockedComponent.SkillManager.ToggleSkillActivation(skill);
SkillUnlockerComponent.TryUpgradeSkill(skill);
if (!skill.IsActive) SkillUnlockerComponent.SkillManager.ToggleSkillActivation(skill);
}
else
{
SkillUnlockedComponent.SkillManager.ToggleSkillActivation(skill);
SkillUnlockerComponent.SkillManager.ToggleSkillActivation(skill);
}
}
else
{
SkillUnlockedComponent.TryUnlockSkill(skill);
}
}
private void RemoveButton(SkillData skill)
{
foreach (var node in ToUnlockGrid.GetChildren())
{
var child = (Button)node;
if (child.Text != GetButtonText(skill)) continue;
child.QueueFree();
break;
SkillUnlockerComponent.TryUnlockSkill(skill);
}
}
private void OnSkillButtonPressed(SkillButton button)
{
SkillUnlockedComponent.SkillManager.ToggleSkillActivation(button.Data);
SkillUnlockerComponent.SkillManager.ToggleSkillActivation(button.Data);
foreach (var btn in _skillButtons)
{
if (btn.Data.IsActive)
btn.Activate();
else
btn.Deactivate();
}
}
}

View File

@@ -13,50 +13,78 @@ public partial class MarketplaceButton : Button
[Export] public Container SkillLevelContainer { get; set; }
private GameManager _gameManager;
private SkillUnlockedComponent _skillUnlockedComponent;
private SkillUnlockerComponent _skillUnlockerComponent;
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
Setup();
var player = _gameManager.Player;
var skillUnlockerComponent = player?.GetNodeOrNull<SkillUnlockedComponent>("SkillUnlockerComponent");
if (skillUnlockerComponent == null) return;
if (player == null) return;
skillUnlockerComponent.SkillUnlocked += OnSkillUnlock;
_skillUnlockerComponent = player.GetNodeOrNull<SkillUnlockerComponent>("SkillUnlockerComponent");
if (_skillUnlockerComponent != null)
{
_skillUnlockerComponent.SkillUnlocked += OnSkillStateChanged;
}
UpdateButtonState();
}
public override void _ExitTree()
{
_skillUnlockedComponent.SkillUnlocked -= OnSkillUnlock;
if (_skillUnlockerComponent != null)
{
_skillUnlockerComponent.SkillUnlocked -= OnSkillStateChanged;
}
}
private void OnSkillStateChanged(SkillData skill)
{
if (skill.Name == Data.Name)
{
UpdateButtonState();
}
}
private void Setup()
private void UpdateButtonState()
{
if (Data == null) return;
if (Data == null || Data.Upgrades.Count == 0)
{
Visible = false;
return;
}
var isUnlocked = _gameManager.IsSkillUnlocked(Data);
for (var i = 0; i < SkillLevelContainer.GetChildCount(); i++)
{
SkillLevelContainer.GetChild(i).QueueFree();
}
for (var i = 0; i < Data.MaxLevel; i++)
{
var icon = new TextureRect()
{
Texture = i < Data.Level ? UnlockedSkillIcon : LockedSkillIcon,
Texture = (isUnlocked && i < Data.Level) ? UnlockedSkillIcon : LockedSkillIcon,
ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional
};
SkillLevelContainer.AddChild(icon);
}
}
private void OnSkillUnlock(SkillData skill)
{
if (skill.Name != Data.Name) return;
for (var i = 0; i < Data.MaxLevel; i++)
if (!isUnlocked)
{
var icon = SkillLevelContainer.GetChildOrNull<TextureRect>(i);
if (icon == null) continue;
icon.Texture = i < Data.Level ? UnlockedSkillIcon : LockedSkillIcon;
Disabled = i >= Data.Level;
Text = $"{Tr(Data.Name)} ({Data.Upgrades[0].Cost})";
Disabled = false;
}
else if (Data.Level < Data.MaxLevel)
{
Text = $"{Tr(Data.Name)} ({Data.Upgrades[Data.Level].Cost})";
Disabled = false;
}
else
{
Text = $"{Tr(Data.Name)} (MAX)";
Disabled = true;
}
}
}

View File

@@ -3,7 +3,7 @@ using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.UI;
public partial class PauseMenu : Node
public partial class PauseMenu : Control
{
[Export] public Control PauseMenuControl { get; set; }
[Export] public Control SettingsControl { get; set; }

View File

@@ -3,7 +3,7 @@ using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.UI;
public partial class SettingsMenu : Node
public partial class SettingsMenu : Control
{
[Export] public Control InputSettingsControl { get; set; }
[Export] public Control AudioSettingsControl { get; set; }
@@ -26,10 +26,10 @@ public partial class SettingsMenu : Node
DisplaySettingsButton.Pressed += OnDisplaySettingsPressed;
GameplaySettingsButton.Pressed += OnGameplaySettingsPressed;
InputSettingsControl.Hide();
AudioSettingsControl.Hide();
DisplaySettingsControl.Hide();
GameplaySettingsControl.Hide();
InputSettingsControl?.Hide();
AudioSettingsControl?.Hide();
DisplaySettingsControl?.Hide();
GameplaySettingsControl?.Hide();
}
public override void _UnhandledInput(InputEvent @event)

View File

@@ -1,17 +0,0 @@
class_name Achievements
extends Node
func unlock_achievement(achievement_name: String) -> bool:
if not Steam.setAchievement(achievement_name):
print("Failed to unlock achievement: ", achievement_name)
return false
return true
func reset_achievement(achievement_name: String) -> bool:
if not Steam.clearAchievement(achievement_name):
print("Failed to reset achievement: ", achievement_name)
return false
return true

View File

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

View File

@@ -4,7 +4,7 @@ using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class BrickThrowComponent : Node
public partial class BrickThrowComponent : Node, ISkill
{
[Export] public PackedScene BrickScene { get; set; }
[Export] public float FireRate { get; set; } = 1.0f;
@@ -13,13 +13,22 @@ public partial class BrickThrowComponent : Node
private bool _canThrow = true;
private Timer _timer;
private SkillData _skillData;
public override void _Ready()
{
SetupTimer();
_canThrow = true;
}
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested += ThrowBrick;
public override void _ExitTree()
{
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested -= ThrowBrick;
if (_timer != null)
{
_timer.Timeout -= OnTimerTimeout;
_timer.QueueFree();
}
}
public override void _Input(InputEvent @event)
@@ -34,10 +43,12 @@ public partial class BrickThrowComponent : Node
private void SetupTimer()
{
_timer = new Timer();
_timer.WaitTime = FireRate;
_timer.OneShot = false;
_timer.Autostart = false;
_timer.Timeout += OnTimerTimeout;
AddChild(_timer);
}
private void OnTimerTimeout()
@@ -52,19 +63,59 @@ public partial class BrickThrowComponent : Node
var instance = BrickScene.Instantiate<Node2D>();
var init = instance.GetNodeOrNull<ProjectileInitComponent>("ProjectileInitComponent");
if (init != null && PlayerController.CurrentMovement is PlatformMovementComponent)
{
init.Initialize(new ProjectileInitParams()
var @params = new ProjectileInitParams()
{
Position = PlayerController.GlobalPosition,
Rotation = PlayerController.Rotation,
Direction = PlayerController.CurrentMovement.LastDirection,
PowerMultiplier = powerMultiplier,
});
};
init.Initialize(@params);
}
GetTree().CurrentScene.AddChild(instance);
_canThrow = false;
_timer.Start();
}
public void Initialize(Node owner, SkillData data)
{
PlayerController = owner as PlayerController;
_skillData = data;
ThrowInputBehavior = (ThrowInputResource)ThrowInputBehavior?.Duplicate();
if (PlayerController == null)
{
GD.PushError("BrickThrowComponent: Owner is not a PlayerController.");
}
if (_skillData.Level > 0 && _skillData.Upgrades.Count >= _skillData.Level)
{
ApplyUpgrade(_skillData.Upgrades[_skillData.Level - 1]);
}
}
public void Activate()
{
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested += ThrowBrick;
SetProcessInput(true);
}
public void Deactivate()
{
if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested -= ThrowBrick;
}
public void ApplyUpgrade(SkillUpgrade upgrade)
{
foreach (var property in upgrade.Properties)
{
Set(property.Key, property.Value);
}
}
}

View File

@@ -1,4 +1,5 @@
using Godot;
using PhantomCamera;
namespace Mr.BrickAdventures.scripts.components;
@@ -6,7 +7,6 @@ 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]
@@ -17,8 +17,17 @@ public partial class ChaseLevelComponent : Node
private bool _isChasing = false;
private Node2D _previousCameraFollowTarget = null;
private PhantomCamera2D _phantomCamera = null;
private Node2D _root = null;
public override void _Process(double delta)
public override void _Ready()
{
_phantomCamera = GetNode<Node2D>("../../%PhantomCamera").AsPhantomCamera2D();
_root = Owner as Node2D;
}
public override void _PhysicsProcess(double delta)
{
if (!_isChasing) return;
if (ChaseTarget == null) return;
@@ -31,21 +40,22 @@ public partial class ChaseLevelComponent : Node
var targetPosition = ChaseTarget.GlobalPosition;
if (Owner is not Node2D root) return;
if (_root == null) return;
var direction = (targetPosition - root.GlobalPosition).Normalized();
root.GlobalPosition += direction * ChaseSpeed * (float)delta;
var direction = (targetPosition - _root.GlobalPosition).Normalized();
var speed = direction * ChaseSpeed * (float)delta;
_root.GlobalPosition += speed;
}
public void OnShipEntered()
{
if (ChaseTarget == null || PhantomCamera == null)
if (ChaseTarget == null || _phantomCamera == null)
return;
if (_isChasing) return;
_previousCameraFollowTarget = (Node2D)PhantomCamera.Call("get_follow_target");
PhantomCamera.Call("set_follow_target", Owner as Node2D);
_previousCameraFollowTarget = _phantomCamera.FollowTarget;
_phantomCamera.FollowTarget = _root;
EmitSignalChaseStarted();
_isChasing = true;
}
@@ -70,9 +80,9 @@ public partial class ChaseLevelComponent : Node
private void StopChasing()
{
if (PhantomCamera == null) return;
if (_phantomCamera == null) return;
PhantomCamera.Call("set_follow_target", _previousCameraFollowTarget);
_phantomCamera.FollowTarget = _previousCameraFollowTarget;
EmitSignalChaseStopped();
_isChasing = false;
}

View File

@@ -13,7 +13,7 @@ public partial class CollectableComponent : Node
[Export] public CollectableResource Data { get; set; }
[Export] public AudioStreamPlayer2D Sfx {get; set; }
[Signal] public delegate void CollectedEventHandler(Variant amount, CollectableType type, Node2D body);
[Signal] public delegate void CollectedEventHandler(float amount, CollectableType type, Node2D body);
public override void _Ready()
{

View File

@@ -16,16 +16,13 @@ public partial class DamageComponent : Node
public override void _Ready()
{
if (Area == null)
if (Area != null)
{
GD.PushError($"DamageComponent: Area2D node is not set.");
return;
Area.BodyEntered += OnAreaBodyEntered;
Area.BodyExited += OnAreaBodyExited;
Area.AreaEntered += OnAreaAreaEntered;
}
Area.BodyEntered += OnAreaBodyEntered;
Area.BodyExited += OnAreaBodyExited;
Area.AreaEntered += OnAreaAreaEntered;
if (DamageTimer != null)
{
DamageTimer.Timeout += OnDamageTimerTimeout;

View File

@@ -28,7 +28,7 @@ public partial class EnemyDeathComponent : Node
private void OnDeath()
{
CallDeferred(nameof(Die));
_ = Die();
}
private async Task Die()

View File

@@ -18,6 +18,8 @@ public partial class ExitDoorComponent : Node, IUnlockable
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
if (ExitArea == null)
{
GD.PushError("ExitDoorComponent: ExitArea is not set.");
@@ -26,12 +28,15 @@ public partial class ExitDoorComponent : Node, IUnlockable
ExitArea.BodyEntered += OnExitAreaBodyEntered;
_gameManager = GetNode<GameManager>("/root/gameManager");
}
private void OnExitAreaBodyEntered(Node2D body)
{
throw new System.NotImplementedException();
if (Locked) return;
EmitSignalExitTriggered();
_gameManager.UnlockLevel((int)_gameManager.PlayerState["current_level"] + 1);
CallDeferred(nameof(GoToNextLevel));
}
public void Unlock()

View File

@@ -16,13 +16,13 @@ public partial class ExplosiveComponent : Node2D
public override void _Ready()
{
if (Damage != null)
if (Damage == null)
{
GD.PushError("ExplosiveComponent: DamageComponent is not set.");
return;
}
if (ExplodeArea != null)
if (ExplodeArea == null)
{
GD.PushError("ExplosiveComponent: ExplodeArea is not set.");
return;
@@ -30,6 +30,8 @@ public partial class ExplosiveComponent : Node2D
Area.BodyEntered += OnAreaBodyEntered;
Area.AreaEntered += OnAreaAreaEntered;
PrepareTimer();
}
private void OnAreaAreaEntered(Area2D area)

View File

@@ -30,11 +30,10 @@ public partial class FlashingComponent : Node
public void StartFlashing()
{
if (Sprite == null) return;
_tween?.Kill();
if (_tween != null && _tween.IsRunning()) return;
_tween = CreateTween();
_tween.SetParallel(true);
_tween.SetParallel(false);
var flashes = (int)(FlashDuration / FlashTime);
for (var i = 0; i < flashes; i++)

View File

@@ -15,9 +15,9 @@ public partial class GravityMotionComponent : Node2D
{
if (LaunchComponent == null) return;
var direction = LaunchComponent.InitialDirection.X > 0f ? TargetDirection : new Vector2(-TargetDirection.X, TargetDirection.Y);
direction = direction.Normalized();
_velocity = direction * LaunchComponent.Speed;
var horizontalDirection = LaunchComponent.InitialDirection.Normalized();
var combinedDirection = (horizontalDirection + TargetDirection).Normalized();
_velocity = combinedDirection * LaunchComponent.Speed;
}
public override void _PhysicsProcess(double delta)

View File

@@ -19,7 +19,7 @@ public partial class HealComponent : Node
Collectable.Collected += OnCollected;
}
private void OnCollected(Variant amount, CollectableType type, Node2D body)
private void OnCollected(float amount, CollectableType type, Node2D body)
{
if (type != CollectableType.Health) return;
@@ -28,8 +28,7 @@ public partial class HealComponent : Node
var healthComponent = body.GetNodeOrNull<HealthComponent>("HealthComponent");
if (healthComponent == null) return;
var value = amount.AsSingle();
healthComponent.IncreaseHealth(value);
healthComponent.IncreaseHealth(amount);
if (HealFx != null)
{
PlayHealFx();

View File

@@ -1,21 +1,19 @@
using System;
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.scripts.interfaces;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class MagneticSkillComponent : Node
public partial class MagneticSkillComponent : Node, ISkill
{
[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;
}
private Node2D _owner;
private SkillData _skillData;
public override void _Process(double delta)
{
@@ -33,7 +31,7 @@ public partial class MagneticSkillComponent : Node
private void OnBodyEntered(Node2D body)
{
if (!HasComponentInChildren(body, "Collectable")) return;
if (!HasComponentInChildren(body, "CollectableComponent")) return;
if (_collectablesToPickUp.Contains(body)) return;
_collectablesToPickUp.Add(body);
@@ -41,7 +39,7 @@ public partial class MagneticSkillComponent : Node
private void OnAreaEntered(Area2D area)
{
if (!HasComponentInChildren(area, "Collectable")) return;
if (!HasComponentInChildren(area, "CollectableComponent")) return;
if (_collectablesToPickUp.Contains(area)) return;
_collectablesToPickUp.Add(area);
@@ -66,13 +64,69 @@ public partial class MagneticSkillComponent : Node
private void MoveCollectableToOwner(Node2D collectable)
{
if (!IsInstanceValid(collectable)) return;
if (Owner is not Node2D root) return;
if (!IsInstanceValid(collectable) || !IsInstanceValid(_owner)) return;
var direction = (root.GlobalPosition - collectable.GlobalPosition).Normalized();
var direction = (_owner.GlobalPosition - collectable.GlobalPosition).Normalized();
var speed = direction.Length() / MagneticMoveDuration;
collectable.GlobalPosition += direction.Normalized() * speed;
}
public void Initialize(Node owner, SkillData data)
{
_owner = owner as Node2D;
_skillData = data;
if (_owner == null)
{
GD.PushWarning("MagneticSkillComponent: Owner is not a Node2D.");
}
if (MagneticArea == null)
{
if (owner is Area2D area2D) MagneticArea = area2D;
else
{
MagneticArea = owner.GetNodeOrNull<Area2D>("MagneticArea");
if (MagneticArea == null)
{
GD.PushError("MagneticSkillComponent: MagneticArea is not set.");
return;
}
}
}
if (_skillData.Level > 0 && _skillData.Upgrades.Count >= _skillData.Level)
{
ApplyUpgrade(_skillData.Upgrades[_skillData.Level - 1]);
}
}
public void Activate()
{
if (MagneticArea == null)
{
GD.PushError("MagneticSkillComponent: MagneticArea is not set.");
return;
}
MagneticArea.BodyEntered += OnBodyEntered;
MagneticArea.AreaEntered += OnAreaEntered;
}
public void Deactivate()
{
if (MagneticArea == null) return;
MagneticArea.BodyEntered -= OnBodyEntered;
MagneticArea.AreaEntered -= OnAreaEntered;
}
public void ApplyUpgrade(SkillUpgrade upgrade)
{
foreach (var property in upgrade.Properties)
{
Set(property.Key, property.Value);
}
}
}

View File

@@ -123,7 +123,8 @@ public partial class PlatformMovementComponent : Node2D, IMovement
Body.Velocity = new Vector2(direction * Speed, Body.Velocity.Y);
else
Body.Velocity = new Vector2(Mathf.MoveToward(Body.Velocity.X, 0, Speed), Body.Velocity.Y);
PreviousVelocity = Body.Velocity;
Body.MoveAndSlide();
}

View File

@@ -4,7 +4,7 @@ using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.components;
public partial class PlayerController : Node2D
public partial class PlayerController : CharacterBody2D
{
[Export]
public string DefaultMovementType { get; set; } = "platform";

View File

@@ -14,7 +14,7 @@ public partial class PlayerDeathComponent : Node2D
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/gameManager");
_gameManager = GetNode<GameManager>("/root/GameManager");
HealthComponent.Death += OnDeath;
}

View File

@@ -2,6 +2,7 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components;
[GlobalClass]
public partial class ProjectileComponent : Node2D
{
[Export] public float Speed { get; set; } = 16f;

View File

@@ -2,7 +2,7 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class ProjectileInitParams
public class ProjectileInitParams
{
public Vector2 Position { get; set; } = Vector2.Zero;
public Vector2 Direction { get; set; } = Vector2.Right;
@@ -20,7 +20,7 @@ public partial class ProjectileInitComponent : Node
var direction = p.Direction;
var rotation = p.Rotation;
var power = p.PowerMultiplier;
if (Owner is Node2D root)
{
root.GlobalPosition = position;

View File

@@ -28,9 +28,9 @@ public partial class RequirementComponent : Node
}
}
private void OnCollected(Variant amount, CollectableType type, Node2D body)
private void OnCollected(float amount, CollectableType type, Node2D body)
{
AddProgress(amount.As<int>());
AddProgress((int)amount);
}
private void AddProgress(int amount = 1)

View File

@@ -7,7 +7,7 @@ namespace Mr.BrickAdventures.scripts.components;
public partial class ScoreComponent : Node
{
private GameManager _gameManager;
private const string CoinsGroupName = "Coins";
private const string CoinsGroupName = "coins";
public override async void _Ready()
{
@@ -20,7 +20,7 @@ public partial class ScoreComponent : Node
return;
}
var coins = GetTree().GetNodesInGroup("Coins");
var coins = GetTree().GetNodesInGroup(CoinsGroupName);
foreach (var coin in coins)
{
var c = coin.GetNodeOrNull<CollectableComponent>("CollectableComponent");
@@ -30,12 +30,12 @@ public partial class ScoreComponent : Node
}
}
}
private void OnCollected(Variant amount, CollectableType type, Node2D body)
private void OnCollected(float amount, CollectableType type, Node2D body)
{
if (type != CollectableType.Coin) return;
var coinAmount = amount.As<int>();
var coinAmount = (int)amount;
var currentCoins = (int)_gameManager.CurrentSessionState["coins_collected"];
_gameManager.CurrentSessionState["coins_collected"] = currentCoins + coinAmount;
}

View File

@@ -10,7 +10,7 @@ public partial class ShipShooterComponent : Node
[Export] public Marker2D BulletSpawn { get; set; }
[Export] public AudioStreamPlayer2D ShootSfx { get; set; }
private bool _canShoot = false;
private bool _canShoot = true;
public override void _Ready()
{

View File

@@ -1,11 +1,12 @@
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.interfaces;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class SkillUnlockedComponent : Node
public partial class SkillUnlockerComponent : Node
{
[Export] public SkillManager SkillManager { get; set; }
@@ -28,11 +29,11 @@ public partial class SkillUnlockedComponent : Node
{
if (_gameManager == null) return false;
if (_gameManager.IsSkillUnlocked(skill)) return false;
if (!HasEnoughCoins(skill.Cost)) return false;
if (!HasEnoughCoins(skill.Upgrades[0].Cost)) return false;
skill.Level = 1;
skill.IsActive = true;
_gameManager.RemoveCoins(skill.Cost);
_gameManager.RemoveCoins(skill.Upgrades[0].Cost);
var skillsUnlocked = (Array<SkillData>)_gameManager.CurrentSessionState["skills_unlocked"];
skillsUnlocked.Add(skill);
@@ -59,11 +60,19 @@ public partial class SkillUnlockedComponent : Node
{
if (_gameManager == null) return false;
if (!_gameManager.IsSkillUnlocked(skill)) return false;
if (!HasEnoughCoins(skill.Cost)) return false;
if (skill.Level >= skill.MaxLevel) return false;
if (!HasEnoughCoins(skill.Upgrades[skill.Level].Cost)) return false;
_gameManager.RemoveCoins(skill.Cost);
_gameManager.RemoveCoins(skill.Upgrades[skill.Level].Cost);
skill.Level++;
if (SkillManager.ActiveComponents.TryGetValue(skill.Name, out Variant componentVariant))
{
var component = componentVariant.AsGodotObject();
if (component is ISkill skillInstance)
{
skillInstance.ApplyUpgrade(skill.Upgrades[skill.Level - 1]);
}
}
EmitSignalSkillUnlocked(skill);
return true;
}

View File

@@ -2,20 +2,19 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class SpaceshipEnterComponent : Node
public partial class SpaceshipEnterComponent : Area2D
{
[Export] public Area2D Area { get; set; }
[Signal] public delegate void SpaceshipEnteredEventHandler();
public override void _Ready()
{
Area.BodyEntered += OnBodyEntered;
BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)
{
if (body is not PlayerController) return;
EmitSignalSpaceshipEntered();
Owner.QueueFree();
QueueFree();
}
}

View File

@@ -2,14 +2,13 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class SpaceshipExitComponent : Node
public partial class SpaceshipExitComponent : Area2D
{
[Export] public Area2D Area { get; set; }
[Signal] public delegate void SpaceshipExitEventHandler();
public override void _Ready()
{
Area.BodyEntered += OnBodyEntered;
BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)

View File

@@ -2,9 +2,8 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class TooltipComponent : Node
public partial class TooltipComponent : Area2D
{
[Export] public Area2D Area { get; set; }
[Export] public Control UiRoot { get; set; }
[Export] public string Text { get; set; } = string.Empty;
[Export] public Label TooltipLabel { get; set; }
@@ -13,8 +12,8 @@ public partial class TooltipComponent : Node
{
TooltipLabel.Text = Text;
UiRoot.Visible = false;
Area.BodyEntered += OnBodyEntered;
Area.BodyExited += OnBodyExited;
BodyEntered += OnBodyEntered;
BodyExited += OnBodyExited;
}
private void OnBodyEntered(Node2D body)

View File

@@ -1,64 +0,0 @@
# class_name BeamComponent
extends Node2D
@export var expansion_speed: float = 16.0
@export var max_length: float = 512.0
@export var direction: Vector2 = Vector2.DOWN
var current_length: float = 16.0
@export var root: Node2D
@export var sprite2d: Sprite2D
@export var collision_shape: CollisionShape2D
func _ready() -> void:
collision_shape.shape.extents = Vector2(current_length / 2.0, current_length / 2.0)
sprite2d.scale = Vector2(1, 1)
collision_shape.position = Vector2.ZERO
func _process(delta: float) -> void:
var new_length = current_length + expansion_speed * delta
if new_length > max_length:
new_length = max_length
if not check_for_obstacle(new_length):
expand_beam(new_length)
func expand_beam(new_length: float) -> void:
current_length = new_length
if direction == Vector2.UP:
sprite2d.scale.y = current_length / 16.0
sprite2d.position.y = -current_length / 2.0
collision_shape.shape.extents = Vector2(8.0, current_length / 2.0)
collision_shape.position.y = -current_length / 2.0
elif direction == Vector2.DOWN:
sprite2d.scale.y = current_length / 16.0
sprite2d.position.y = current_length / 2.0
collision_shape.shape.extents = Vector2(8.0, current_length / 2.0)
collision_shape.position.y = current_length / 2.0
elif direction == Vector2.LEFT:
sprite2d.scale.y = current_length / 16.0
sprite2d.position.x = -current_length / 2.0
collision_shape.shape.extents = Vector2(current_length / 2.0, 8.0)
collision_shape.position.x = -current_length / 2.0
elif direction == Vector2.RIGHT:
sprite2d.scale.y = current_length / 16.0
sprite2d.position.x = current_length / 2.0
collision_shape.shape.extents = Vector2(current_length / 2.0, 8.0)
collision_shape.position.x = current_length / 2.0
func check_for_obstacle(new_length: float) -> bool:
var space_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state
var query_start: Vector2 = global_position
var query_end: Vector2 = global_position + direction * new_length
var query: PhysicsRayQueryParameters2D = PhysicsRayQueryParameters2D.create(query_start, query_end)
query.collide_with_areas = false
query.collide_with_bodies = true
var result: Dictionary = space_state.intersect_ray(query)
return result.size() > 0

View File

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

View File

@@ -1,59 +0,0 @@
class_name BrickThrowComponent
extends Node
@export var brick_scene: PackedScene
@export var fire_rate: float = 1.0
@export var player_controller: PlayerController
@export var timer: Timer
@export var throw_input_behavior: ThrowInputResource
var can_throw: bool = true
func _ready() -> void:
setup_timer()
can_throw = true
if throw_input_behavior:
throw_input_behavior.throw_requested.connect(throw_brick)
func _input(event: InputEvent) -> void:
if throw_input_behavior:
throw_input_behavior.process_input(event)
func _process(delta: float) -> void:
if throw_input_behavior:
throw_input_behavior.update(delta)
func setup_timer() -> void:
timer.wait_time = fire_rate
timer.one_shot = false
timer.autostart = false
timer.timeout.connect(on_timer_timeout)
func on_timer_timeout() -> void:
can_throw = true
func throw_brick(power_multiplier: float = 1.0) -> void:
if not can_throw:
return
var instance: Node2D = brick_scene.instantiate()
var init := instance.get_node_or_null("ProjectileInitComponent") as ProjectileInitComponent
if init and player_controller.current_movement is PlatformMovement:
init.initialize({
"position": player_controller.global_position,
"rotation": player_controller.rotation,
"direction": player_controller.current_movement.last_direction,
"power_multiplier": power_multiplier
})
get_tree().current_scene.add_child(instance)
can_throw = false
timer.start()

View File

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

View File

@@ -1,37 +0,0 @@
class_name BulletComponent
extends Node
@export var root: Node2D
@export var area2d: Area2D
@export var hit_terrain_fx: TerrainHitFx
@export var bullet_sprite: Sprite2D
func _ready() -> void:
area2d.body_entered.connect(on_area2d_body_entered)
area2d.area_entered.connect(on_area2d_area_entered)
func on_area2d_body_entered(body: Node2D) -> void:
if body is TileMapLayer:
if bullet_sprite:
bullet_sprite.visible = false
play_terrain_hit_fx()
return
root.queue_free()
func on_area2d_area_entered(_area: Area2D) -> void:
root.queue_free()
func play_terrain_hit_fx() -> void:
if not hit_terrain_fx:
return
await hit_terrain_fx.trigger_fx()
root.queue_free()

View File

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

View File

@@ -1,37 +0,0 @@
class_name CageComponent
extends Node
@export var lever: LeverComponent
@export var root: Node2D
@export var move_value: Vector2 = Vector2(0, -100)
@export var tween_duration: float = 0.5
@export var should_free: bool = true
func _ready() -> void:
await get_tree().process_frame
if not lever:
var levers_nodes := get_tree().get_nodes_in_group("levers")
for lever_node in levers_nodes:
var lever_component: LeverComponent = lever_node.get_node_or_null("LeverComponent")
if lever_component:
lever_component.activated.connect(on_lever_activated)
else:
lever.activated.connect(on_lever_activated)
if not root:
printerr("CageComponent: root is not set.")
return
func on_lever_activated() -> void:
var tween: Tween = create_tween()
var end_position: Vector2 = root.position + move_value
tween.tween_property(root, "position", end_position, tween_duration)
tween.tween_callback(_on_tween_completed)
func _on_tween_completed() -> void:
if not should_free:
return
root.queue_free()

View File

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

View File

@@ -1,2 +0,0 @@
class_name CanBeLaunchedComponent
extends Node

View File

@@ -1 +0,0 @@
uid://6ffxsx3gknhr

View File

@@ -1,2 +0,0 @@
class_name CanPickUpComponent
extends Node

View File

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

View File

@@ -1,2 +0,0 @@
class_name CannotStompComponent
extends Node

View File

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

View File

@@ -1,59 +0,0 @@
class_name ChargeThrowComponent
extends Node
@export var min_charge_duration: float = 0.1
@export var min_power: float = 0.5
@export var max_power: float = 2.0
@export var max_charge_time: float = 1.5
var charge_start_time: float = 0.0
var is_charging: bool = false
signal charge_started
signal charge_updated(charge_ratio: float)
signal charge_stopped
func _process(_delta: float) -> void:
if not is_charging:
return
var charge_ratio := get_charge_ratio()
charge_updated.emit(charge_ratio)
func start_charging() -> void:
is_charging = true
charge_start_time = Time.get_ticks_msec() / 1000.0
call_deferred("emit_charge_started")
func get_charge_ratio() -> float:
if not is_charging:
return 0.0
var held_time := (Time.get_ticks_msec() / 1000.0) - charge_start_time
var t = clamp(held_time / max_charge_time, 0.0, 1.0)
return lerp(min_power, max_power, t)
func stop_charging() -> float:
if not is_charging:
return min_power
var held_time := (Time.get_ticks_msec() / 1000.0) - charge_start_time
is_charging = false
charge_start_time = 0.0
charge_stopped.emit()
if held_time < min_charge_duration:
return min_power
var t = clamp(held_time / max_charge_time, 0.0, 1.0)
return lerp(min_power, max_power, t)
func emit_charge_started() -> void:
if not is_charging:
return
charge_started.emit()

View File

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

View File

@@ -1,69 +0,0 @@
class_name ChaseLevelComponent
extends Node
@export var chase_speed: float = 200.0
@export var chase_target: Marker2D
@export var phantom_camera: PhantomCamera2D
@export var minimum_distance: float = 10.0
signal chase_started
signal chase_stopped
var is_chasing: bool = false
var previous_camera_follow_target: Node2D = null
func _process(delta: float) -> void:
if not is_chasing:
return
if not chase_target:
printerr("ChaseLevelComponent: chase_target is not set.")
return
if check_if_reached_target():
stop_chasing()
return
var target_position: Vector2 = chase_target.global_position
var current_position: Vector2 = owner.global_position
var direction: Vector2 = (target_position - current_position).normalized()
owner.global_position += direction * chase_speed * delta
func on_ship_entered() -> void:
if not chase_target:
printerr("ChaseLevelComponent: chase_target is not set.")
return
if not phantom_camera:
printerr("ChaseLevelComponent: phantom_camera is not set.")
return
previous_camera_follow_target = phantom_camera.get_follow_target()
phantom_camera.set_follow_target(owner as Node2D)
chase_started.emit()
is_chasing = true
func on_ship_exited() -> void:
stop_chasing()
func check_if_reached_target() -> bool:
if not chase_target:
printerr("ChaseLevelComponent: chase_target is not set.")
return false
var target_position: Vector2 = chase_target.global_position
var current_position: Vector2 = owner.global_position
return current_position.distance_to(target_position) < minimum_distance
func stop_chasing() -> void:
if not phantom_camera:
printerr("ChaseLevelComponent: phantom_camera is not set.")
return
phantom_camera.set_follow_target(previous_camera_follow_target)
chase_stopped.emit()
is_chasing = false

View File

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

View File

@@ -1,8 +0,0 @@
class_name CleanUpComponent
extends Node
@export var root: Node
func clean_up() -> void:
root.queue_free()

View File

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

View File

@@ -1,55 +0,0 @@
class_name CollapsableComponent
extends Node
@export var to_collapse_timer: Timer
@export var reset_timer: Timer
@export var sprite2d: Sprite2D
@export var collision_shape: CollisionShape2D
@export var collapse_time: float = 0.5
@export var reset_time: float = 1.0
@export var anim_time: float = 0.25
func _ready() -> void:
reset_timers()
func _on_to_collapse_timer_timeout() -> void:
collapse_bridge()
func _on_reset_timer_timeout() -> void:
reactivate_bridge()
func collapse_bridge():
to_collapse_timer.stop()
to_collapse_timer.wait_time = collapse_time
var bridge_tween = create_tween()
bridge_tween.tween_property(sprite2d, "modulate:a", 0, anim_time)
await bridge_tween.finished
collision_shape.disabled = true
reset_timer.start()
func reactivate_bridge():
reset_timer.stop()
reset_timer.wait_time = reset_time
var bridge_tween = create_tween()
bridge_tween.tween_property(sprite2d, "modulate:a", 1, anim_time)
await bridge_tween.finished
collision_shape.disabled = false
func _on_collapsable_detector_body_entered(_body: Node2D) -> void:
to_collapse_timer.start()
func reset_timers():
to_collapse_timer.stop()
to_collapse_timer.wait_time = collapse_time
func _on_collapsable_detector_body_exited(_body: Node2D) -> void:
var collapse_time_left: float = abs(to_collapse_timer.time_left - collapse_time)
if collapse_time_left < (0.1 * collapse_time):
reset_timers()

View File

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

View File

@@ -1,38 +0,0 @@
class_name CollectableComponent
extends Node
var root: Node
var has_fade_away: bool = false
@export var area2d: Area2D
@export var collision_shape: CollisionShape2D
@export var collectable_data: CollectableResource
@export var sfx: AudioStreamPlayer2D
signal collected(amount: Variant, type: CollectableResource.CollectableType, body: Node2D)
func _ready() -> void:
if area2d:
area2d.body_entered.connect(_on_area2d_body_entered)
else:
printerr("Collectable node missing Area2D child.")
root = get_parent()
if root.has_node("FadeAwayComponent"):
has_fade_away = true
func _on_area2d_body_entered(body: Node2D) -> void:
if body.has_node("CanPickUpComponent"):
collected.emit(collectable_data.amount, collectable_data.type, body)
if collision_shape:
collision_shape.call_deferred("set_disabled", true)
if sfx:
sfx.play()
if not has_fade_away and sfx:
await sfx.finished
root.queue_free()
elif not has_fade_away:
root.queue_free()

View File

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

View File

@@ -1,94 +0,0 @@
class_name DamageComponent
extends Node
@export var damage: float = 0.25
@export var area2d: Area2D
@export var status_effect_data: StatusEffectDataResource
@export var damage_timer: Timer
var current_target: Node = null
signal effect_inflicted(target: Node2D, effect: StatusEffectDataResource)
func _ready() -> void:
if not area2d:
printerr("No area2d assigned!")
return
area2d.body_entered.connect(on_area2d_body_entered)
area2d.body_exited.connect(on_area2d_body_exited)
area2d.area_entered.connect(on_area2d_area_entered)
if damage_timer:
damage_timer.timeout.connect(on_damage_timer_timeout)
func _process(_delta: float) -> void:
if not current_target:
return
if damage_timer:
return
process_entity_and_apply_damage(current_target)
func deal_damage(target: HealthComponent) -> void:
target.decrease_health(damage)
func on_damage_timer_timeout() -> void:
if not current_target:
return
process_entity_and_apply_damage(current_target)
func process_entity_and_apply_damage(body: Node2D) -> void:
if body.has_node("HealthComponent"):
var health_component: HealthComponent = body.get_node("HealthComponent")
var invulnerability_component: InvulnerabilityComponent = body.get_node_or_null("InvulnerabilityComponent")
if invulnerability_component and invulnerability_component.is_invulnerable():
return
if status_effect_data and status_effect_data.effect_type != StatusEffectComponent.EffectType.NONE:
effect_inflicted.emit(body, status_effect_data)
deal_damage(health_component)
if invulnerability_component:
invulnerability_component.activate()
func on_area2d_body_entered(body: Node2D) -> void:
current_target = body
if not check_if_processing_is_on():
return
if damage_timer:
damage_timer.start()
process_entity_and_apply_damage(body)
func on_area2d_body_exited(body: Node2D) -> void:
if body == current_target:
current_target = null
if damage_timer:
damage_timer.stop()
func on_area2d_area_entered(area: Area2D) -> void:
if not check_if_processing_is_on():
return
if area == area2d:
return
var parent := area.get_parent()
if parent.has_node("DamageComponent"):
process_entity_and_apply_damage(parent)
func check_if_processing_is_on() -> bool:
return self.process_mode == PROCESS_MODE_INHERIT or self.process_mode == PROCESS_MODE_ALWAYS

View File

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

View File

@@ -1,23 +0,0 @@
class_name DestroyableComponent
extends Node
@export var root: Node
@export var health_component: HealthComponent
@export var destroy_effect: PackedScene
func _ready() -> void:
if not health_component:
printerr("No health component assigned!")
return
health_component.on_death.connect(on_health_component_death)
func on_health_component_death() -> void:
if destroy_effect:
var effect: Node2D = destroy_effect.instantiate()
health_component.get_parent().add_child(effect)
effect.global_position = health_component.global_position
root.queue_free()

View File

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

View File

@@ -1,20 +0,0 @@
class_name EffectInflictorComponent
extends Node
@export var damage: DamageComponent
func _ready() -> void:
if not damage:
printerr("No damage component assigned!")
return
damage.effect_inflicted.connect(on_effect_inflicted)
func on_effect_inflicted(target: Node2D, effect: StatusEffectDataResource) -> void:
var status_effect_component: StatusEffectComponent = target.get_node_or_null("StatusEffectComponent")
if not status_effect_component:
return
status_effect_component.apply_effect(effect)

View File

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

View File

@@ -1,28 +0,0 @@
class_name EnemyDeathComponent
extends Node
@export var root: Node2D
@export var tween_duration: float = 0.5
@export var collision_shape_2d: CollisionShape2D
@export var health_component: HealthComponent
func _ready() -> void:
if not collision_shape_2d:
printerr("No CollisionShape2D assigned!")
return
if not health_component:
printerr("No HealthComponent assigned!")
return
health_component.on_death.connect(_on_health_component_on_death)
func _on_health_component_on_death() -> void:
call_deferred("die")
func die() -> void:
collision_shape_2d.disabled = true
var tween := create_tween()
tween.tween_property(root, "scale", Vector2(0, 0), tween_duration)
await (tween.finished)
root.queue_free()

View File

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

View File

@@ -1,37 +0,0 @@
class_name EnemyWaveTrigger
extends Node
@export var area2d: Area2D
@export var path_follow_node: PathFollow2D
@export var speed: float = 100.0
@export var loop: bool = false
@export var activate_on_enter: bool = true
var active: bool = false
func _ready() -> void:
area2d.body_entered.connect(_on_body_entered)
if path_follow_node:
path_follow_node.progress = 0
path_follow_node.set_process(false)
func _process(delta: float) -> void:
if not active or not path_follow_node:
return
path_follow_node.progress += speed * delta
if path_follow_node.progress_ratio >= 1.0 and not loop:
active = false
path_follow_node.set_process(false)
func _on_body_entered(body: Node2D) -> void:
if activate_on_enter:
start_wave()
func start_wave() -> void:
if path_follow_node:
path_follow_node.set_process(true)
active = true

View File

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

View File

@@ -1,39 +0,0 @@
class_name ExitDoorComponent
extends Node
@export var locked: bool = true
@export var exit_area: Area2D
@export var door_sprite: Sprite2D
@export var opened_door_sfx: AudioStreamPlayer2D
@export var opened_door_frame: int = 0
signal exit_triggered
@onready var gm: GM = $"/root/GameManager"
func _ready() -> void:
if not exit_area:
printerr("ExitDoorComponent: exit_area is not set.")
return
exit_area.body_entered.connect(on_exit_area_body_entered)
func unlock() -> void:
locked = false
if door_sprite:
door_sprite.frame = opened_door_frame
if opened_door_sfx:
opened_door_sfx.play()
func on_exit_area_body_entered(_body: Node2D) -> void:
if locked:
return
exit_triggered.emit()
gm.unlock_level(gm.player_state["current_level"] + 1)
call_deferred("go_to_next_level")
func go_to_next_level() -> void:
gm.on_level_complete()

View File

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

View File

@@ -1,64 +0,0 @@
class_name ExplosiveComponent
extends Node
@export var root: Node2D
@export var damage: DamageComponent
@export var area2d: Area2D
@export var explosion_area2d: Area2D
@export var explosion_effect: PackedScene
@export var time_to_explode: float = 9.0
signal on_explosion(body: Node2D)
var timer: Timer
func _ready() -> void:
if not damage:
printerr("No damage component assigned!")
return
if not explosion_area2d:
printerr("No area2d assigned!")
return
area2d.body_entered.connect(on_area2d_body_entered)
area2d.area_entered.connect(on_area2d_area_entered)
prepare_timer()
func prepare_timer() -> void:
timer = Timer.new()
timer.set_wait_time(time_to_explode)
timer.set_one_shot(true)
timer.autostart = true
timer.timeout.connect(explode)
add_child(timer)
func explode() -> void:
timer.stop()
if explosion_effect:
var explosion_instance: GPUParticles2D = explosion_effect.instantiate()
explosion_instance.global_position = root.global_position
get_tree().current_scene.add_child(explosion_instance)
explosion_instance.emitting = true
var bodies: Array = explosion_area2d.get_overlapping_bodies()
for body in bodies:
var health_component: HealthComponent = body.get_node_or_null("HealthComponent")
if damage and health_component:
damage.deal_damage(health_component)
on_explosion.emit(body)
root.queue_free()
func on_area2d_body_entered(_body: Node2D) -> void:
explode()
func on_area2d_area_entered(_area: Area2D) -> void:
explode()

View File

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

View File

@@ -1,27 +0,0 @@
class_name FadeAwayComponent
extends Node
@export var sprite2d: Sprite2D
@export var fade_duration: float = 1.0
@export var speed: float = 10.0
@export var direction: Vector2 = Vector2.UP
@export var root: Node2D
@export var area2d: Area2D
func _ready():
root = get_parent()
if area2d:
area2d.body_entered.connect(on_area2d_body_entered)
func fade_away() -> void:
var fade_tween := create_tween().set_parallel(true)
fade_tween.tween_property(sprite2d, "modulate:a", 0, fade_duration)
fade_tween.tween_property(sprite2d, "position", sprite2d.position + (direction * speed), fade_duration)
await (fade_tween.finished)
root.queue_free()
func on_area2d_body_entered(_body: Node2D) -> void:
fade_away()

View File

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

View File

@@ -1,54 +0,0 @@
class_name FireEffectComponent
extends Node
@export var health_component: HealthComponent
@export var status_effect_component: StatusEffectComponent
@export var root: Node2D
@export var fire_fx: GPUParticles2D
var data: StatusEffectDataResource = null
var should_deal_damage: bool = false
var time_elapsed: float = 0.0
func _ready() -> void:
if not health_component:
health_component = root.get_node("HealthComponent")
if not status_effect_component:
status_effect_component = root.get_node("StatusEffectComponent")
if not health_component:
printerr("No HealthComponent assigned!")
return
if not status_effect_component:
printerr("No StatusEffectComponent assigned!")
return
status_effect_component.effect_applied.connect(on_effect_applied)
status_effect_component.effect_removed.connect(on_effect_removed)
func _process(delta: float) -> void:
if not should_deal_damage or not health_component or not data:
return
time_elapsed += delta
if time_elapsed >= 1.0:
health_component.decrease_health(data.damage_per_second)
time_elapsed = 0.0
func on_effect_applied(effect_data: StatusEffectDataResource) -> void:
if effect_data.effect_type == StatusEffectComponent.EffectType.FIRE:
data = effect_data
should_deal_damage = true
if fire_fx:
fire_fx.emitting = true
func on_effect_removed(effect_type: StatusEffectComponent.EffectType) -> void:
if effect_type == StatusEffectComponent.EffectType.FIRE:
data = null
should_deal_damage = false
if fire_fx:
fire_fx.emitting = false

View File

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

View File

@@ -1,55 +0,0 @@
class_name FlashingComponent
extends Node
@export var sprite: Node2D
@export var flash_duration: float = 0.5
@export var flash_time: float = 0.1
@export var use_modulate: bool = true
@export var health_component: HealthComponent
var tween: Tween
func _ready() -> void:
if health_component:
health_component.on_health_change.connect(on_health_change)
health_component.on_death.connect(on_death)
if not sprite:
printerr("No sprite assigned!")
return
func start_flashing() -> void:
if not sprite:
return
if tween:
tween.kill()
tween = create_tween()
tween.set_parallel(false)
var flashes: int = int(flash_duration / flash_time)
for i in range(flashes):
var opacity: float = 0.3 if i % 2 == 0 else 1.0
tween.tween_property(sprite, "modulate:a" if use_modulate else "visible", opacity if use_modulate else float(i % 2 == 0), flash_time)
tween.tween_callback(stop_flashing)
func stop_flashing() -> void:
if use_modulate:
sprite.modulate.a = 1.0
else:
sprite.visible = true
func on_health_change(delta: float, _total_health: float) -> void:
if delta < 0:
start_flashing()
func on_death() -> void:
stop_flashing()

View File

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

View File

@@ -1,27 +0,0 @@
class_name FlipPlayerComponent
extends Node2D
@export var eye_left: Sprite2D
@export var eye_right: Sprite2D
@export var platform_movement: PlatformMovement
func _process(_delta: float) -> void:
if not platform_movement:
return
var velocity := platform_movement.last_direction
if velocity.x < 0:
eye_left.frame = 1
eye_right.frame = 1
eye_left.flip_h = true
eye_right.flip_h = true
elif velocity.x > 0:
eye_left.frame = 1
eye_right.frame = 1
eye_left.flip_h = false
eye_right.flip_h = false
else:
eye_left.frame = 0
eye_right.frame = 0

View File

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

View File

@@ -1,30 +0,0 @@
class_name GravityMotionComponent
extends Node2D
@export var character_body: CharacterBody2D
@export var launch_component: LaunchComponent
@export var gravity: Vector2 = Vector2(0, 980.0)
@export var target_direction: Vector2 = Vector2(1.0, -1.0)
var velocity: Vector2 = Vector2.ZERO
func _ready() -> void:
if not launch_component:
return
var direction := target_direction if launch_component.initial_direction.x > 0 else Vector2(-target_direction.x, target_direction.y)
direction = direction.normalized()
velocity = direction * launch_component.speed
func _physics_process(delta: float) -> void:
if not character_body:
return
velocity += gravity * delta
character_body.velocity = velocity
character_body.move_and_slide()
if velocity.length_squared() > 0.01:
character_body.rotation = velocity.angle()

View File

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

View File

@@ -1,40 +0,0 @@
class_name HealComponent
extends Node
@export var heal_fx: GPUParticles2D
@export var collectable: CollectableComponent
func _ready() -> void:
if not collectable:
printerr("HealComponent: No CollectableComponent assigned.")
return
collectable.collected.connect(on_collected)
func on_collected(amount: float, type: CollectableResource.CollectableType, _body: Node2D) -> void:
if type != CollectableResource.CollectableType.HEALTH:
return
if not collectable:
printerr("HealComponent: No CollectableComponent assigned.")
return
var health_component := _body.get_node_or_null("HealthComponent") as HealthComponent
if not health_component or not health_component is HealthComponent:
printerr("HealComponent: No HealthComponent found on collected body.")
return
health_component.increase_health(amount)
if heal_fx:
play_heal_fx()
else:
owner.queue_free()
func play_heal_fx() -> void:
if not heal_fx:
return
heal_fx.restart()
heal_fx.emitting = true

View File

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

View File

@@ -1,48 +0,0 @@
class_name HealthComponent
extends Node
@export var health: float = 1.0
@export var max_health: float = 1.0
@export var hurt_fx: AudioStreamPlayer2D
@export var heal_fx: AudioStreamPlayer2D
signal on_health_change(delta: float, total_health: float)
signal on_death
func _get_delta(new_value: float) -> float:
return new_value - health
func set_health(new_value: float):
_apply_health_change(new_value)
func decrease_health(value: float):
_apply_health_change(health - value)
func increase_health(value: float):
_apply_health_change(health + value)
func _apply_health_change(new_health: float, play_fx: bool = true) -> void:
new_health = clamp(new_health, 0.0, max_health)
var delta := new_health - health
if delta == 0.0:
return # No change
if play_fx:
if delta > 0 and heal_fx:
heal_fx.play()
elif delta < 0 and hurt_fx:
hurt_fx.play()
await hurt_fx.finished
health = new_health
if health <= 0:
on_death.emit()
else:
on_health_change.emit(delta, health)

View File

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

View File

@@ -1,57 +0,0 @@
class_name HitComponent
extends Node
@export var sprite: Sprite2D
@export var health_component: HealthComponent
@export var hit_duration: float = 0.1
@export var hit_fx: GPUParticles2D
@export var flash_mode: bool = true
func _ready() -> void:
if health_component:
health_component.on_health_change.connect(on_health_change)
health_component.on_death.connect(on_death)
if not sprite:
printerr("No sprite assigned!")
return
if sprite.material and flash_mode:
sprite.material = sprite.material.duplicate()
func activate() -> void:
if not flash_mode:
return
sprite.material.set_shader_parameter("enabled", true)
func deactivate() -> void:
if not flash_mode:
return
sprite.material.set_shader_parameter("enabled", false)
func on_health_change(delta: float, total_health: float) -> void:
if delta < 0:
activate()
await get_tree().create_timer(hit_duration).timeout
deactivate()
if total_health > 0 and delta < 0:
handle_hit_fx()
func on_death() -> void:
activate()
await get_tree().create_timer(hit_duration).timeout
deactivate()
func handle_hit_fx() -> void:
if not hit_fx:
return
hit_fx.restart()
hit_fx.emitting = true

View File

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

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