Add new components: HealComponent, HitComponent, HomingMissileMotionComponent, LeverComponent, and TriggerLeverComponent
This commit is contained in:
48
scripts/components/HealComponent.cs
Normal file
48
scripts/components/HealComponent.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Godot;
|
||||||
|
using Mr.BrickAdventures.scripts.Resources;
|
||||||
|
|
||||||
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
|
public partial class HealComponent : Node
|
||||||
|
{
|
||||||
|
[Export] public GpuParticles2D HealFx { get; set; }
|
||||||
|
[Export] public CollectableComponent Collectable { get; set; }
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
if (Collectable == null)
|
||||||
|
{
|
||||||
|
GD.PushError("HealComponent: Collectable is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collectable.Collected += OnCollected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollected(Variant amount, CollectableType type, Node2D body)
|
||||||
|
{
|
||||||
|
if (type != CollectableType.Health) return;
|
||||||
|
|
||||||
|
if (Collectable == null) return;
|
||||||
|
|
||||||
|
var healthComponent = body.GetNodeOrNull<HealthComponent>("HealthComponent");
|
||||||
|
if (healthComponent == null) return;
|
||||||
|
|
||||||
|
var value = amount.AsSingle();
|
||||||
|
healthComponent.IncreaseHealth(value);
|
||||||
|
if (HealFx != null)
|
||||||
|
{
|
||||||
|
PlayHealFx();
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayHealFx()
|
||||||
|
{
|
||||||
|
if (HealFx == null) return;
|
||||||
|
|
||||||
|
HealFx.Restart();
|
||||||
|
HealFx.Emitting = true;
|
||||||
|
}
|
||||||
|
}
|
75
scripts/components/HitComponent.cs
Normal file
75
scripts/components/HitComponent.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
|
public partial class HitComponent : Node
|
||||||
|
{
|
||||||
|
[Export] public Sprite2D Sprite { get; set; }
|
||||||
|
[Export] public HealthComponent Health { get; set; }
|
||||||
|
[Export] public float HitDuration { get; set; } = 0.1f;
|
||||||
|
[Export] public GpuParticles2D HitFx { get; set; }
|
||||||
|
[Export] public bool FlashMode { get; set; } = true;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
if (Health != null)
|
||||||
|
{
|
||||||
|
Health.HealthChanged += OnHealthChange;
|
||||||
|
Health.Death += OnDeath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sprite == null)
|
||||||
|
{
|
||||||
|
GD.PushError("HitComponent: Sprite is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sprite.Material != null && FlashMode)
|
||||||
|
{
|
||||||
|
Sprite.Material = (Material)Sprite.Material.Duplicate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Activate()
|
||||||
|
{
|
||||||
|
if (!FlashMode) return;
|
||||||
|
|
||||||
|
Sprite.SetInstanceShaderParameter("enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Deactivate()
|
||||||
|
{
|
||||||
|
if (!FlashMode) return;
|
||||||
|
|
||||||
|
Sprite.SetInstanceShaderParameter("enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnHealthChange(float delta, float totalHealth)
|
||||||
|
{
|
||||||
|
if (!(delta < 0f)) return;
|
||||||
|
|
||||||
|
Activate();
|
||||||
|
await ToSignal(GetTree().CreateTimer(HitDuration), Timer.SignalName.Timeout);
|
||||||
|
Deactivate();
|
||||||
|
|
||||||
|
if (totalHealth > 0f && delta < 0f)
|
||||||
|
{
|
||||||
|
HandleHitFx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnDeath()
|
||||||
|
{
|
||||||
|
Activate();
|
||||||
|
await ToSignal(GetTree().CreateTimer(HitDuration), Timer.SignalName.Timeout);
|
||||||
|
Deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleHitFx()
|
||||||
|
{
|
||||||
|
if (HitFx == null) return;
|
||||||
|
|
||||||
|
HitFx.Restart();
|
||||||
|
HitFx.Emitting = true;
|
||||||
|
}
|
||||||
|
}
|
64
scripts/components/HomingMissileMotionComponent.cs
Normal file
64
scripts/components/HomingMissileMotionComponent.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
|
public partial class HomingMissileMotionComponent : Node
|
||||||
|
{
|
||||||
|
[Export] public LaunchComponent Launch { get; set; }
|
||||||
|
[Export] public float MaxSpeed { get; set; } = 16f;
|
||||||
|
[Export] public float Acceleration { get; set; } = 8f;
|
||||||
|
[Export] public float MaxTurnRate { get; set; } = 180f; // degrees per second
|
||||||
|
[Export] public float WobbleStrength { get; set; } = 5f; // degrees
|
||||||
|
[Export] public float Drag { get; set; } = 0.98f;
|
||||||
|
[Export] public float SteeringLerp { get; set; } = 0.05f; // low = sluggish, high = responsive
|
||||||
|
[Export] public Area2D DetectionArea { get; set; }
|
||||||
|
|
||||||
|
private Vector2 _steeringDirection = Vector2.Zero;
|
||||||
|
private Vector2 _velocity = Vector2.Zero;
|
||||||
|
private Node2D _target = null;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
DetectionArea.BodyEntered += OnBodyEntered;
|
||||||
|
_velocity = Launch.GetInitialVelocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
if (Launch == null) return;
|
||||||
|
if (Owner is not Node2D owner) return;
|
||||||
|
if (_target == null)
|
||||||
|
{
|
||||||
|
owner.Position += _velocity * (float)delta;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var toTarget = (_target.GlobalPosition - owner.GlobalPosition).Normalized();
|
||||||
|
_steeringDirection = _steeringDirection.Lerp(toTarget, SteeringLerp);
|
||||||
|
|
||||||
|
var angleToTarget = _velocity.AngleTo(_steeringDirection);
|
||||||
|
var maxAngle = Mathf.DegToRad(MaxTurnRate) * (float)delta;
|
||||||
|
var clampedAngle = Mathf.Clamp(angleToTarget, -maxAngle, maxAngle);
|
||||||
|
|
||||||
|
var rng = new RandomNumberGenerator();
|
||||||
|
var wobble = Mathf.DegToRad(rng.RandfRange(-WobbleStrength, WobbleStrength));
|
||||||
|
clampedAngle += wobble;
|
||||||
|
|
||||||
|
_velocity = _velocity.Rotated(clampedAngle);
|
||||||
|
_velocity *= Drag;
|
||||||
|
|
||||||
|
var desiredSpeed = Mathf.Min(MaxSpeed, _velocity.Length() + Acceleration * (float)delta);
|
||||||
|
_velocity = _velocity.Normalized() * desiredSpeed;
|
||||||
|
|
||||||
|
owner.Position += _velocity * (float)delta;
|
||||||
|
owner.Rotation = _velocity.Angle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBodyEntered(Node2D body)
|
||||||
|
{
|
||||||
|
if (_target != null) return;
|
||||||
|
if (body == null) return;
|
||||||
|
|
||||||
|
_target = body;
|
||||||
|
}
|
||||||
|
}
|
66
scripts/components/LeverComponent.cs
Normal file
66
scripts/components/LeverComponent.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
|
public partial class LeverComponent : Node
|
||||||
|
{
|
||||||
|
[Export] public Area2D Area { get; set; }
|
||||||
|
[Export] public Sprite2D Sprite { get; set; }
|
||||||
|
[Export] public int StartAnimationIndex { get; set; } = 0;
|
||||||
|
[Export] public float AnimationDuration { get; set; } = 0.5f;
|
||||||
|
[Export] public AudioStreamPlayer2D Sfx { get; set; }
|
||||||
|
|
||||||
|
[Signal]
|
||||||
|
public delegate void ActivatedEventHandler();
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
if (Area == null)
|
||||||
|
{
|
||||||
|
GD.PushError("LeverComponent: Area is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sprite == null)
|
||||||
|
{
|
||||||
|
GD.PushError("LeverComponent: Sprite is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Area.BodyEntered += OnBodyEntered;
|
||||||
|
Area.AreaEntered += OnAreaEntered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAreaEntered(Area2D area)
|
||||||
|
{
|
||||||
|
HandleTriggerLogic(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBodyEntered(Node2D body)
|
||||||
|
{
|
||||||
|
HandleTriggerLogic(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Activate()
|
||||||
|
{
|
||||||
|
EmitSignalActivated();
|
||||||
|
Sfx?.Play();
|
||||||
|
Sprite.Frame = StartAnimationIndex + 1;
|
||||||
|
var timer = GetTree().CreateTimer(AnimationDuration);
|
||||||
|
await timer.ToSignal(timer, Timer.SignalName.Timeout);
|
||||||
|
Sprite.Frame = StartAnimationIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleTriggerLogic(Node2D obj)
|
||||||
|
{
|
||||||
|
var triggerLever = obj.GetNodeOrNull<TriggerLeverComponent>("TriggerLeverComponent");
|
||||||
|
if (triggerLever == null)
|
||||||
|
{
|
||||||
|
GD.PushWarning("LeverComponent: TriggerLeverComponent not found in body.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Activate();
|
||||||
|
}
|
||||||
|
}
|
8
scripts/components/TriggerLeverComponent.cs
Normal file
8
scripts/components/TriggerLeverComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
|
public partial class TriggerLeverComponent : Node
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user