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

This commit is contained in:
2025-08-10 12:57:58 +02:00
parent 5dbf04cc5f
commit 99473d1295
7 changed files with 209 additions and 1 deletions

View File

@@ -4,5 +4,7 @@ namespace Mr.BrickAdventures.scripts.Resources;
public partial class StatusEffectDataResource : Resource public partial class StatusEffectDataResource : Resource
{ {
[Export] public float Duration { get; set; } = 1f;
[Export] public float DamagePerSecond { get; set; } = 0.25f;
[Export] public StatusEffectType Type { get; set; } [Export] public StatusEffectType Type { get; set; }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ using Godot;
namespace Mr.BrickAdventures.scripts.components; namespace Mr.BrickAdventures.scripts.components;
public partial class HealthComponent : Node public partial class HealthComponent : Node2D
{ {
[Export] public float Health { get; set; } = 1.0f; [Export] public float Health { get; set; } = 1.0f;
[Export] public float MaxHealth { get; set; } = 1.0f; [Export] public float MaxHealth { get; set; } = 1.0f;

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Godot;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.components;
public partial class StatusEffect : GodotObject
{
public StatusEffectDataResource EffectData { get; set; }
public float ElapsedTime { get; set; }
public Timer Timer { get; set; }
}
public partial class StatusEffectComponent : Node
{
private List<StatusEffect> _activeEffects = [];
[Signal] public delegate void EffectAppliedEventHandler(StatusEffect statusEffect);
[Signal] public delegate void EffectRemovedEventHandler(StatusEffectType type);
public void ApplyEffect(StatusEffectDataResource effectData)
{
var data = effectData.Duplicate() as StatusEffectDataResource;
var timer = CreateTimer(effectData.Duration, data);
var statusEffect = new StatusEffect
{
EffectData = data,
ElapsedTime = 0f,
Timer = timer
};
_activeEffects.Add(statusEffect);
EmitSignalEffectApplied(statusEffect);
}
public void RemoveEffect(StatusEffectType type)
{
var effectToRemove = _activeEffects.Find(effect => effect.EffectData.Type == type);
if (effectToRemove.EffectData == null) return;
_activeEffects.Remove(effectToRemove);
effectToRemove.Timer.QueueFree();
EmitSignalEffectRemoved(type);
}
private Timer CreateTimer(float duration, StatusEffectDataResource effectData)
{
var timer = new Timer();
timer.SetWaitTime(duration);
timer.SetOneShot(true);
timer.SetAutostart(true);
timer.Timeout += () => RemoveEffect(effectData.Type);
AddChild(timer);
return timer;
}
}