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

This commit is contained in:
2025-08-10 22:38:19 +02:00
parent 54ffa8a42c
commit f3aa2631f2
5 changed files with 261 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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