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:
@@ -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; }
|
||||
}
|
13
scripts/Resources/LevelResource.cs
Normal file
13
scripts/Resources/LevelResource.cs
Normal 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;
|
||||
}
|
1
scripts/Resources/LevelResource.cs.uid
Normal file
1
scripts/Resources/LevelResource.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c2h0pqhxiqswe
|
@@ -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;
|
||||
}
|
12
scripts/Resources/SkillUpgrade.cs
Normal file
12
scripts/Resources/SkillUpgrade.cs
Normal 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();
|
||||
}
|
1
scripts/Resources/SkillUpgrade.cs.uid
Normal file
1
scripts/Resources/SkillUpgrade.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dwb0e05pewcsn
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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
72
scripts/UI/DeathScreen.cs
Normal 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();
|
||||
}
|
||||
}
|
1
scripts/UI/DeathScreen.cs.uid
Normal file
1
scripts/UI/DeathScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csprksav8mj1h
|
@@ -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; }
|
||||
|
@@ -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; }
|
||||
|
@@ -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; }
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://deguukal87gcb
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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;
|
||||
|
@@ -28,7 +28,7 @@ public partial class EnemyDeathComponent : Node
|
||||
|
||||
private void OnDeath()
|
||||
{
|
||||
CallDeferred(nameof(Die));
|
||||
_ = Die();
|
||||
}
|
||||
|
||||
private async Task Die()
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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++)
|
||||
|
@@ -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)
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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";
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class ProjectileComponent : Node2D
|
||||
{
|
||||
[Export] public float Speed { get; set; } = 16f;
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://bejv75mi8npj0
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://cm06xg1l3xtw5
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://cdnwrn8v05qhi
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://ddplvyjqguxtl
|
@@ -1,2 +0,0 @@
|
||||
class_name CanBeLaunchedComponent
|
||||
extends Node
|
@@ -1 +0,0 @@
|
||||
uid://6ffxsx3gknhr
|
@@ -1,2 +0,0 @@
|
||||
class_name CanPickUpComponent
|
||||
extends Node
|
@@ -1 +0,0 @@
|
||||
uid://dcvjvjy2nhf8s
|
@@ -1,2 +0,0 @@
|
||||
class_name CannotStompComponent
|
||||
extends Node
|
@@ -1 +0,0 @@
|
||||
uid://ct7lsuwjvmwxu
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://c6fclevp3peuo
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://cf4li7whw5old
|
@@ -1,8 +0,0 @@
|
||||
class_name CleanUpComponent
|
||||
extends Node
|
||||
|
||||
@export var root: Node
|
||||
|
||||
|
||||
func clean_up() -> void:
|
||||
root.queue_free()
|
@@ -1 +0,0 @@
|
||||
uid://f74xpfg7624d
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://r0a6xjicrh74
|
@@ -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()
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://pa1bwc4no08q
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://dkmxhjtmu5xlb
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://d01dmoafptl2p
|
@@ -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)
|
@@ -1 +0,0 @@
|
||||
uid://d3brcje121krs
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://dqm371fysuk7i
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://tmahwsvpkrbv
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://bwamqffvpa452
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://beg4dk7d5pvhp
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://bg75hnr3q6grk
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://qi2irprbqru2
|
@@ -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()
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://dqmbvuutd5c3c
|
@@ -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
|
||||
|
@@ -1 +0,0 @@
|
||||
uid://oxeqvxkgj87j
|
@@ -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()
|
@@ -1 +0,0 @@
|
||||
uid://c2gbumw4x4t1v
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://cegdd1sravi5m
|
@@ -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)
|
@@ -1 +0,0 @@
|
||||
uid://btfsq0bvtrx3t
|
@@ -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
|
@@ -1 +0,0 @@
|
||||
uid://ceq8n7yw7qxpi
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user