Refactor GameManager session state handling and add new components: CanBeLaunchedComponent, IceEffectComponent, JumpPadComponent, KillPlayerOutOfScreenComponent, KnockbackComponent, LifetimeComponent, MagneticSkillComponent, OutOfScreenComponent, PeriodicShootingComponent, PlayerDeathComponent, ProgressiveDamageComponent, ProjectileComponent, ProjectileInitComponent, RequirementComponent, ScoreComponent, ShipMovementComponent, ShipShooterComponent, and SideToSideMovementComponent
This commit is contained in:
8
scripts/components/CanBeLaunchedComponent.cs
Normal file
8
scripts/components/CanBeLaunchedComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class CanBeLaunchedComponent : Node
|
||||
{
|
||||
|
||||
}
|
@@ -6,7 +6,7 @@ public partial class FlipComponent : Node2D
|
||||
{
|
||||
[Export] public Sprite2D LeftEye { get; set; }
|
||||
[Export] public Sprite2D RightEye { get; set; }
|
||||
[Export] public PlatformMovement PlatformMovement { get; set; }
|
||||
[Export] public PlatformMovementComponent PlatformMovement { get; set; }
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
|
70
scripts/components/IceEffectComponent.cs
Normal file
70
scripts/components/IceEffectComponent.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class IceEffectComponent : Node
|
||||
{
|
||||
[Export] public Array<Node> ComponentsToDisable { get; set; } = [];
|
||||
[Export] public StatusEffectComponent StatusEffectComponent { get; set; }
|
||||
[Export] public Node2D IceFx { get; set; }
|
||||
|
||||
private StatusEffectDataResource _data = null;
|
||||
private int _iceEffectsApplied = 0;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
StatusEffectComponent.EffectApplied += OnEffectApplied;
|
||||
StatusEffectComponent.EffectRemoved += OnEffectRemoved;
|
||||
}
|
||||
|
||||
private void OnEffectApplied(StatusEffect statusEffect)
|
||||
{
|
||||
if (statusEffect.EffectData.Type != StatusEffectType.Ice) return;
|
||||
|
||||
_data = statusEffect.EffectData;
|
||||
_iceEffectsApplied++;
|
||||
ApplyFreeze();
|
||||
}
|
||||
|
||||
private void OnEffectRemoved(StatusEffectType type)
|
||||
{
|
||||
if (type != StatusEffectType.Ice) return;
|
||||
_data = null;
|
||||
_iceEffectsApplied--;
|
||||
RemoveFreeze();
|
||||
}
|
||||
|
||||
private void ApplyFreeze()
|
||||
{
|
||||
if (IceFx != null)
|
||||
{
|
||||
IceFx.Visible = true;
|
||||
}
|
||||
|
||||
foreach (var component in ComponentsToDisable)
|
||||
{
|
||||
if (component == null || _iceEffectsApplied == 0) continue;
|
||||
|
||||
component.ProcessMode = ProcessModeEnum.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFreeze()
|
||||
{
|
||||
if (_iceEffectsApplied > 0) return;
|
||||
|
||||
if (IceFx != null)
|
||||
{
|
||||
IceFx.Visible = false;
|
||||
}
|
||||
|
||||
foreach (var component in ComponentsToDisable)
|
||||
{
|
||||
if (component == null) continue;
|
||||
|
||||
component.ProcessMode = ProcessModeEnum.Inherit;
|
||||
}
|
||||
}
|
||||
}
|
39
scripts/components/JumpPadComponent.cs
Normal file
39
scripts/components/JumpPadComponent.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class JumpPadComponent : Node
|
||||
{
|
||||
[Export] public float JumpForce { get; set; } = 10f;
|
||||
[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;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Area.BodyEntered += OnBodyEntered;
|
||||
}
|
||||
|
||||
private void OnBodyEntered(Node2D body)
|
||||
{
|
||||
var canBeLaunched = body.GetNodeOrNull<CanBeLaunchedComponent>("CanBeLaunchedComponent");
|
||||
if (canBeLaunched == null) return;
|
||||
|
||||
if (body is not PlayerController { CurrentMovement: PlatformMovementComponent movement }) return;
|
||||
_ = HandleLaunchPadAnimation();
|
||||
movement.Body.Velocity = new Vector2(movement.Body.Velocity.X, -JumpForce);
|
||||
movement.JumpSfx?.Play();
|
||||
}
|
||||
|
||||
private async Task HandleLaunchPadAnimation()
|
||||
{
|
||||
if (Sprite == null) return;
|
||||
|
||||
var timer = GetTree().CreateTimer(AnimationDuration);
|
||||
Sprite.Frame = StartAnimationIndex + 1;
|
||||
await ToSignal(timer, Timer.SignalName.Timeout);
|
||||
Sprite.Frame = StartAnimationIndex;
|
||||
}
|
||||
}
|
21
scripts/components/KillPlayerOutOfScreenComponent.cs
Normal file
21
scripts/components/KillPlayerOutOfScreenComponent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class KillPlayerOutOfScreenComponent : Node
|
||||
{
|
||||
[Export] public VisibleOnScreenNotifier2D ScreenNotifier { get; set; }
|
||||
[Export] public HealthComponent HealthComponent { get; set; }
|
||||
|
||||
private const float Damage = 6000f;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
ScreenNotifier.ScreenExited += HandleOutOfScreen;
|
||||
}
|
||||
|
||||
private void HandleOutOfScreen()
|
||||
{
|
||||
HealthComponent?.DecreaseHealth(Damage);
|
||||
}
|
||||
}
|
48
scripts/components/KnockbackComponent.cs
Normal file
48
scripts/components/KnockbackComponent.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class KnockbackComponent : Node
|
||||
{
|
||||
[Export] public CharacterBody2D Body { get; set; }
|
||||
[Export] public float KnockbackForce { get; set; } = 25f;
|
||||
[Export] public HealthComponent HealthComponent { get; set; }
|
||||
|
||||
private bool _knockbackMode = false;
|
||||
private int _knockbackFrames = 0;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
HealthComponent.HealthChanged += OnHealthChanged;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (_knockbackMode) _knockbackFrames++;
|
||||
|
||||
if (_knockbackFrames <= 1) return;
|
||||
|
||||
_knockbackMode = false;
|
||||
_knockbackFrames = 0;
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
if (_knockbackMode) ApplyKnockback((float)delta);
|
||||
}
|
||||
|
||||
private void OnHealthChanged(float delta, float totalHealth)
|
||||
{
|
||||
if (totalHealth <= 0f || delta >= 0f) return;
|
||||
|
||||
_knockbackMode = true;
|
||||
}
|
||||
|
||||
private void ApplyKnockback(float delta)
|
||||
{
|
||||
var velocity = Body.Velocity.Normalized();
|
||||
var knockbackDirection = new Vector2(Mathf.Sign(velocity.X), 0.4f);
|
||||
var knockbackVector = -knockbackDirection * KnockbackForce * delta;
|
||||
Body.Velocity += knockbackVector;
|
||||
}
|
||||
}
|
27
scripts/components/LifetimeComponent.cs
Normal file
27
scripts/components/LifetimeComponent.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class LifetimeComponent : Node
|
||||
{
|
||||
[Export] public float LifeTime { get; set; } = 5.0f;
|
||||
|
||||
private Timer _lifetimeTimer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_lifetimeTimer = new Timer();
|
||||
_lifetimeTimer.WaitTime = LifeTime;
|
||||
_lifetimeTimer.OneShot = true;
|
||||
_lifetimeTimer.Autostart = true;
|
||||
_lifetimeTimer.Timeout += OnLifetimeTimeout;
|
||||
|
||||
AddChild(_lifetimeTimer);
|
||||
_lifetimeTimer.Start();
|
||||
}
|
||||
|
||||
private void OnLifetimeTimeout()
|
||||
{
|
||||
Owner.QueueFree();
|
||||
}
|
||||
}
|
78
scripts/components/MagneticSkillComponent.cs
Normal file
78
scripts/components/MagneticSkillComponent.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class MagneticSkillComponent : Node
|
||||
{
|
||||
[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;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
foreach (var collectable in _collectablesToPickUp)
|
||||
{
|
||||
if (!IsInstanceValid(collectable))
|
||||
{
|
||||
_collectablesToPickUp.Remove(collectable);
|
||||
continue;
|
||||
}
|
||||
|
||||
MoveCollectableToOwner(collectable);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBodyEntered(Node2D body)
|
||||
{
|
||||
if (!HasComponentInChildren(body, "Collectable")) return;
|
||||
|
||||
if (_collectablesToPickUp.Contains(body)) return;
|
||||
_collectablesToPickUp.Add(body);
|
||||
}
|
||||
|
||||
private void OnAreaEntered(Area2D area)
|
||||
{
|
||||
if (!HasComponentInChildren(area, "Collectable")) return;
|
||||
|
||||
if (_collectablesToPickUp.Contains(area)) return;
|
||||
_collectablesToPickUp.Add(area);
|
||||
}
|
||||
|
||||
private bool HasComponentInChildren(Node node, string componentName)
|
||||
{
|
||||
if (node == null) return false;
|
||||
|
||||
if (node.HasNode(componentName)) return true;
|
||||
|
||||
foreach (var child in node.GetChildren())
|
||||
{
|
||||
if (child is { } childNode && HasComponentInChildren(childNode, componentName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void MoveCollectableToOwner(Node2D collectable)
|
||||
{
|
||||
if (!IsInstanceValid(collectable)) return;
|
||||
|
||||
if (Owner is not Node2D root) return;
|
||||
|
||||
var direction = (root.GlobalPosition - collectable.GlobalPosition).Normalized();
|
||||
var speed = direction.Length() / MagneticMoveDuration;
|
||||
|
||||
collectable.GlobalPosition += direction.Normalized() * speed;
|
||||
}
|
||||
}
|
18
scripts/components/OutOfScreenComponent.cs
Normal file
18
scripts/components/OutOfScreenComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class OutOfScreenComponent : Node
|
||||
{
|
||||
[Export] public VisibleOnScreenNotifier2D VisibilityNotifier { get; set; }
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
VisibilityNotifier.ScreenExited += OnScreenExited;
|
||||
}
|
||||
|
||||
private void OnScreenExited()
|
||||
{
|
||||
Owner?.QueueFree();
|
||||
}
|
||||
}
|
71
scripts/components/PeriodicShootingComponent.cs
Normal file
71
scripts/components/PeriodicShootingComponent.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class PeriodicShootingComponent : Node
|
||||
{
|
||||
[Export] public PackedScene BulletScene { get; set; }
|
||||
[Export] public float ShootInterval { get; set; } = 1.0f;
|
||||
[Export] public Vector2 ShootDirection { get; set; } = Vector2.Right;
|
||||
[Export] public SideToSideMovementComponent SideToSideMovement { get; set; }
|
||||
[Export] public Node2D BulletSpawnRight { get; set; }
|
||||
[Export] public Node2D BulletSpawnLeft { get; set; }
|
||||
[Export] public float ShootingIntervalVariation { get; set; } = 0.0f;
|
||||
|
||||
private Timer _timer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
SetupTimer();
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (SideToSideMovement == null) return;
|
||||
|
||||
ShootDirection = SideToSideMovement.Direction != Vector2.Zero ? SideToSideMovement.Direction : Vector2.Right;
|
||||
}
|
||||
|
||||
private void SetupTimer()
|
||||
{
|
||||
_timer = new Timer();
|
||||
_timer.WaitTime = GetShootInterval();
|
||||
_timer.OneShot = false;
|
||||
_timer.Autostart = true;
|
||||
_timer.Timeout += OnTimerTimeout;
|
||||
AddChild(_timer);
|
||||
}
|
||||
|
||||
private void OnTimerTimeout()
|
||||
{
|
||||
Shoot();
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private double GetShootInterval()
|
||||
{
|
||||
if (ShootingIntervalVariation == 0f) return ShootInterval;
|
||||
|
||||
var rng = new RandomNumberGenerator();
|
||||
return ShootInterval + rng.RandfRange(-ShootingIntervalVariation, ShootingIntervalVariation);
|
||||
}
|
||||
|
||||
private void Shoot()
|
||||
{
|
||||
if (ShootDirection == Vector2.Zero) return;
|
||||
|
||||
var root = Owner as Node2D;
|
||||
var bulletInstance = BulletScene.Instantiate<Node2D>();
|
||||
var launchComponent = bulletInstance.GetNodeOrNull<LaunchComponent>("LaunchComponent");
|
||||
var spawnPosition = ShootDirection == Vector2.Right ? BulletSpawnRight.GlobalPosition : BulletSpawnLeft.GlobalPosition;
|
||||
if (launchComponent != null)
|
||||
{
|
||||
launchComponent.InitialDirection = ShootDirection;
|
||||
launchComponent.SpawnPosition = spawnPosition;
|
||||
if (root != null) launchComponent.SpawnRotation = root.Rotation;
|
||||
}
|
||||
|
||||
bulletInstance.Position = spawnPosition;
|
||||
GetTree().CurrentScene.AddChild(bulletInstance);
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ using Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class PlatformMovement : Node2D, IMovement
|
||||
public partial class PlatformMovementComponent : Node2D, IMovement
|
||||
{
|
||||
[Export]
|
||||
public float Speed { get; set; } = 300.0f;
|
@@ -15,7 +15,7 @@ public partial class PlayerController : Node2D
|
||||
[Export]
|
||||
public Sprite2D ShipSprite { get; set; }
|
||||
|
||||
private IMovement _currentMovement = null;
|
||||
public IMovement CurrentMovement = null;
|
||||
[Signal]
|
||||
public delegate void MovementSwitchedEventHandler(string movementType);
|
||||
|
||||
@@ -48,20 +48,20 @@ public partial class PlayerController : Node2D
|
||||
|
||||
private void SwitchMovement(string movementType)
|
||||
{
|
||||
if (_currentMovement != null)
|
||||
if (CurrentMovement != null)
|
||||
{
|
||||
_currentMovement.Enabled = false;
|
||||
CurrentMovement.Enabled = false;
|
||||
}
|
||||
|
||||
if (MovementTypes.TryGetValue(movementType, out var movement))
|
||||
{
|
||||
_currentMovement = GetNodeOrNull<IMovement>(movement);
|
||||
if (_currentMovement == null)
|
||||
CurrentMovement = GetNodeOrNull<IMovement>(movement);
|
||||
if (CurrentMovement == null)
|
||||
{
|
||||
GD.PushError($"Movement type '{movementType}' not found in MovementTypes.");
|
||||
return;
|
||||
}
|
||||
_currentMovement.Enabled = true;
|
||||
CurrentMovement.Enabled = true;
|
||||
EmitSignalMovementSwitched(movementType);
|
||||
}
|
||||
else
|
||||
@@ -69,7 +69,7 @@ public partial class PlayerController : Node2D
|
||||
GD.PushError($"Movement type '{movementType}' not found in MovementTypes.");
|
||||
}
|
||||
|
||||
if (_currentMovement == null)
|
||||
if (CurrentMovement == null)
|
||||
{
|
||||
GD.PushError("No current movement set after switching.");
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public partial class PlayerController : Node2D
|
||||
private string GetNextMovementType()
|
||||
{
|
||||
var keys = new List<string>(MovementTypes.Keys);
|
||||
var currentIndex = keys.IndexOf(_currentMovement?.MovementType);
|
||||
var currentIndex = keys.IndexOf(CurrentMovement?.MovementType);
|
||||
|
||||
if (currentIndex == -1)
|
||||
{
|
||||
|
36
scripts/components/PlayerDeathComponent.cs
Normal file
36
scripts/components/PlayerDeathComponent.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.Autoloads;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class PlayerDeathComponent : Node2D
|
||||
{
|
||||
[Export] public AudioStreamPlayer2D DeathSfx { get; set; }
|
||||
[Export] public PackedScene DeathEffect { get; set; }
|
||||
[Export] public HealthComponent HealthComponent { get; set; }
|
||||
[Export] public Vector2 EffectScale { get; set; } = new Vector2(1.5f, 1.5f);
|
||||
|
||||
private GameManager _gameManager;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_gameManager = GetNode<GameManager>("/root/gameManager");
|
||||
HealthComponent.Death += OnDeath;
|
||||
}
|
||||
|
||||
private void OnDeath()
|
||||
{
|
||||
DeathSfx?.Play();
|
||||
|
||||
if (DeathEffect != null)
|
||||
{
|
||||
var effect = DeathEffect.Instantiate<Node2D>();
|
||||
GetParent().AddChild(effect);
|
||||
effect.GlobalPosition = GlobalPosition;
|
||||
effect.Scale = EffectScale;
|
||||
}
|
||||
|
||||
_gameManager.RemoveLives(1);
|
||||
_gameManager.ResetCurrentSessionState();
|
||||
}
|
||||
}
|
65
scripts/components/ProgressiveDamageComponent.cs
Normal file
65
scripts/components/ProgressiveDamageComponent.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ProgressiveDamageComponent : Node
|
||||
{
|
||||
[Export] public HealthComponent HealthComponent { get; set; }
|
||||
[Export] public Sprite2D Sprite { get; set; }
|
||||
[Export] public PlatformMovementComponent PlatformMovement { get; set; }
|
||||
[Export] public float MinJumpHeight { get; set; } = 60f;
|
||||
[Export] public float JumpReductionPercentage { get; set; } = 0.1f; // this is a percentage of the jump height per hit
|
||||
|
||||
private float _maxHealth;
|
||||
private float _ogJumpHeight;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_maxHealth = HealthComponent.MaxHealth;
|
||||
HealthComponent.HealthChanged += OnHealthChanged;
|
||||
|
||||
if (PlatformMovement != null)
|
||||
{
|
||||
_ogJumpHeight = PlatformMovement.JumpHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHealthChanged(float delta, float totalHealth)
|
||||
{
|
||||
var frame = GetDamageFrame();
|
||||
if (frame < 0 || frame >= Sprite.GetHframes()) return;
|
||||
|
||||
Sprite.Frame = frame;
|
||||
if (PlatformMovement != null)
|
||||
{
|
||||
PlatformMovement.JumpHeight = GetJumpHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDamageFrame()
|
||||
{
|
||||
if (Sprite == null || HealthComponent == null) return 0;
|
||||
|
||||
var framesCount = Sprite.GetHframes();
|
||||
if (framesCount == 0) return 0;
|
||||
|
||||
var currentHealth = HealthComponent.Health;
|
||||
var healthRatio = currentHealth / _maxHealth;
|
||||
return (int)(framesCount * (1f - healthRatio));
|
||||
}
|
||||
|
||||
private float GetJumpHeight()
|
||||
{
|
||||
if (PlatformMovement == null) return 0f;
|
||||
|
||||
var jumpHeight = _ogJumpHeight;
|
||||
if (jumpHeight <= 0f) return 0f;
|
||||
|
||||
var damageFrame = GetDamageFrame();
|
||||
if (damageFrame < 0 || damageFrame >= Sprite.GetHframes()) return jumpHeight;
|
||||
|
||||
var reduction = JumpReductionPercentage * jumpHeight;
|
||||
var calculatedJumpHeight = jumpHeight - (damageFrame * reduction);
|
||||
return Mathf.Max(calculatedJumpHeight, MinJumpHeight);
|
||||
}
|
||||
}
|
26
scripts/components/ProjectileComponent.cs
Normal file
26
scripts/components/ProjectileComponent.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ProjectileComponent : Node2D
|
||||
{
|
||||
[Export] public float Speed { get; set; } = 16f;
|
||||
[Export] public float AngleDirection { get; set; }
|
||||
[Export] public Vector2 SpawnPosition { get; set; } = Vector2.Zero;
|
||||
[Export] public float SpawnRotation { get; set; } = 0f;
|
||||
[Export] public CharacterBody2D Body { get; set; }
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
GlobalPosition = SpawnPosition;
|
||||
GlobalRotation = SpawnRotation;
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
if (Body == null) return;
|
||||
|
||||
Body.Velocity += new Vector2(0f, -Speed).Rotated(AngleDirection);
|
||||
Body.MoveAndSlide();
|
||||
}
|
||||
}
|
37
scripts/components/ProjectileInitComponent.cs
Normal file
37
scripts/components/ProjectileInitComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ProjectileInitParams
|
||||
{
|
||||
public Vector2 Position { get; set; } = Vector2.Zero;
|
||||
public Vector2 Direction { get; set; } = Vector2.Right;
|
||||
public float Rotation { get; set; } = 0f;
|
||||
public float PowerMultiplier { get; set; } = 1f;
|
||||
}
|
||||
|
||||
public partial class ProjectileInitComponent : Node
|
||||
{
|
||||
[Export] public LaunchComponent LaunchComponent { get; set; }
|
||||
|
||||
public void Initialize(ProjectileInitParams p)
|
||||
{
|
||||
var position = p.Position;
|
||||
var direction = p.Direction;
|
||||
var rotation = p.Rotation;
|
||||
var power = p.PowerMultiplier;
|
||||
|
||||
if (Owner is Node2D root)
|
||||
{
|
||||
root.GlobalPosition = position;
|
||||
root.GlobalRotation = rotation;
|
||||
}
|
||||
|
||||
if (LaunchComponent == null) return;
|
||||
|
||||
LaunchComponent.InitialDirection = direction;
|
||||
LaunchComponent.SpawnPosition = position;
|
||||
LaunchComponent.SpawnRotation = rotation;
|
||||
LaunchComponent.Speed *= power;
|
||||
}
|
||||
}
|
44
scripts/components/RequirementComponent.cs
Normal file
44
scripts/components/RequirementComponent.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class RequirementComponent : Node
|
||||
{
|
||||
[Export] public CollectableType RequirementType { get; set; }
|
||||
[Export] public int RequirementAmount { get; set; } = 1;
|
||||
|
||||
private int _currentAmount = 0;
|
||||
|
||||
private const string CollectableGroupName = "Collectables";
|
||||
|
||||
[Signal]
|
||||
public delegate void RequirementMetEventHandler(CollectableType requirementType);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var collectables = GetTree().GetNodesInGroup(CollectableGroupName);
|
||||
foreach (var collectable in collectables)
|
||||
{
|
||||
var c = collectable.GetNodeOrNull<CollectableComponent>("CollectableComponent");
|
||||
if (c != null && c.Data.Type == RequirementType)
|
||||
{
|
||||
c.Collected += OnCollected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollected(Variant amount, CollectableType type, Node2D body)
|
||||
{
|
||||
AddProgress(amount.As<int>());
|
||||
}
|
||||
|
||||
private void AddProgress(int amount = 1)
|
||||
{
|
||||
_currentAmount += amount;
|
||||
if (_currentAmount >= RequirementAmount)
|
||||
{
|
||||
EmitSignalRequirementMet(RequirementType);
|
||||
}
|
||||
}
|
||||
}
|
42
scripts/components/ScoreComponent.cs
Normal file
42
scripts/components/ScoreComponent.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.Autoloads;
|
||||
using Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ScoreComponent : Node
|
||||
{
|
||||
private GameManager _gameManager;
|
||||
private const string CoinsGroupName = "Coins";
|
||||
|
||||
public override async void _Ready()
|
||||
{
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
|
||||
|
||||
_gameManager = GetNode<GameManager>("/root/GameManager");
|
||||
if (_gameManager == null)
|
||||
{
|
||||
GD.PrintErr("GameManager not found in the scene tree.");
|
||||
return;
|
||||
}
|
||||
|
||||
var coins = GetTree().GetNodesInGroup("Coins");
|
||||
foreach (var coin in coins)
|
||||
{
|
||||
var c = coin.GetNodeOrNull<CollectableComponent>("CollectableComponent");
|
||||
if (c != null)
|
||||
{
|
||||
c.Collected += OnCollected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollected(Variant amount, CollectableType type, Node2D body)
|
||||
{
|
||||
if (type != CollectableType.Coin) return;
|
||||
|
||||
var coinAmount = amount.As<int>();
|
||||
var currentCoins = (int)_gameManager.CurrentSessionState["coins_collected"];
|
||||
_gameManager.CurrentSessionState["coins_collected"] = currentCoins + coinAmount;
|
||||
}
|
||||
}
|
37
scripts/components/ShipMovementComponent.cs
Normal file
37
scripts/components/ShipMovementComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Godot;
|
||||
using Mr.BrickAdventures.scripts.interfaces;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ShipMovementComponent : Node, IMovement
|
||||
{
|
||||
[Export] public float MaxSpeed { get; set; } = 200f;
|
||||
[Export] public float Acceleration { get; set; } = 100f;
|
||||
[Export] public float Friction { get; set; } = 50f;
|
||||
[Export] public CharacterBody2D Body { get; set; }
|
||||
|
||||
public string MovementType { get; } = "ship";
|
||||
public bool Enabled { get; set; }
|
||||
public Vector2 PreviousVelocity { get; set; }
|
||||
|
||||
private Vector2 _velocity = Vector2.Zero;
|
||||
|
||||
public Vector2 Velocity => _velocity;
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
if (Body == null || !Enabled) return;
|
||||
|
||||
var inputVector = new Vector2(
|
||||
Input.GetActionStrength("right") - Input.GetActionStrength("left"),
|
||||
Input.GetActionStrength("down") - Input.GetActionStrength("up")
|
||||
).Normalized();
|
||||
|
||||
_velocity = inputVector != Vector2.Zero ? _velocity.MoveToward(inputVector * MaxSpeed, Acceleration * (float)delta) : _velocity.MoveToward(Vector2.Zero, Friction * (float)delta);
|
||||
|
||||
_velocity = _velocity.LimitLength(MaxSpeed);
|
||||
Body.Velocity = _velocity;
|
||||
PreviousVelocity = Body.Velocity;
|
||||
Body.MoveAndSlide();
|
||||
}
|
||||
}
|
59
scripts/components/ShipShooterComponent.cs
Normal file
59
scripts/components/ShipShooterComponent.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class ShipShooterComponent : Node
|
||||
{
|
||||
[Export] public PackedScene BulletScene { get; set; }
|
||||
[Export] public float FireRate { get; set; } = 0.2f;
|
||||
[Export] public Marker2D BulletSpawn { get; set; }
|
||||
[Export] public AudioStreamPlayer2D ShootSfx { get; set; }
|
||||
|
||||
private bool _canShoot = false;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
SetProcess(false);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (Input.IsActionJustPressed("attack") && _canShoot)
|
||||
{
|
||||
_ = Shoot();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Shoot()
|
||||
{
|
||||
if (!_canShoot) return;
|
||||
|
||||
var bullet = BulletScene.Instantiate<Node2D>();
|
||||
var init = bullet.GetNodeOrNull<ProjectileInitComponent>("ProjectileInitComponent");
|
||||
init?.Initialize(new ProjectileInitParams()
|
||||
{
|
||||
Position = BulletSpawn.GlobalPosition,
|
||||
});
|
||||
|
||||
GetTree().CurrentScene.AddChild(bullet);
|
||||
ShootSfx?.Play();
|
||||
|
||||
_canShoot = false;
|
||||
await ToSignal(GetTree().CreateTimer(FireRate), Timer.SignalName.Timeout);
|
||||
_canShoot = true;
|
||||
}
|
||||
|
||||
private void OnShipEntered()
|
||||
{
|
||||
_canShoot = true;
|
||||
SetProcess(true);
|
||||
}
|
||||
|
||||
private void OnShipExited()
|
||||
{
|
||||
_canShoot = false;
|
||||
SetProcess(false);
|
||||
ShootSfx?.Stop();
|
||||
}
|
||||
}
|
117
scripts/components/SideToSideMovementComponent.cs
Normal file
117
scripts/components/SideToSideMovementComponent.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.components;
|
||||
|
||||
public partial class SideToSideMovementComponent : Node
|
||||
{
|
||||
[Export] public Sprite2D Sprite { get; set; }
|
||||
[Export] public float Speed { get; set; } = 10.0f;
|
||||
[Export] public float WaitTime { get; set; } = 1.0f;
|
||||
[Export] public RayCast2D LeftRay { get; set; }
|
||||
[Export] public RayCast2D RightRay { get; set; }
|
||||
[Export] public RayCast2D LeftWallRay { get; set; }
|
||||
[Export] public RayCast2D RightWallRay { get; set; }
|
||||
|
||||
private Vector2 _direction = Vector2.Left;
|
||||
private Vector2 _newDirection = Vector2.Left;
|
||||
private Timer _timer;
|
||||
private bool _triggeredDirectionChange = false;
|
||||
|
||||
[Signal]
|
||||
public delegate void DirectionChangedEventHandler();
|
||||
|
||||
public Vector2 Direction => _direction;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
SetupTimer();
|
||||
DirectionChanged += OnDirectionChanged;
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
HandleDirection();
|
||||
HandleSpriteFlip();
|
||||
HandleMovement(delta);
|
||||
}
|
||||
|
||||
private void HandleDirection()
|
||||
{
|
||||
// Check if we are colliding with the left wall
|
||||
if (LeftWallRay.IsColliding())
|
||||
{
|
||||
_newDirection = Vector2.Right;
|
||||
EmitSignalDirectionChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are colliding with the right wall
|
||||
if (RightWallRay.IsColliding())
|
||||
{
|
||||
_newDirection = Vector2.Left;
|
||||
EmitSignalDirectionChanged();
|
||||
return;
|
||||
}
|
||||
// We are not colliding with anything, which means we don't have ground to walk on. Stop moving.
|
||||
if (!LeftRay.IsColliding() && !RightRay.IsColliding())
|
||||
{
|
||||
_newDirection = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the left ray is not colliding and the right ray is colliding, that means we have ground to the right and we should change direction to the right.
|
||||
if (!LeftRay.IsColliding() && RightRay.IsColliding())
|
||||
{
|
||||
_newDirection = Vector2.Right;
|
||||
EmitSignalDirectionChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the right ray is not colliding and the left ray is colliding, that means we have ground to the left and we should change direction to the left.
|
||||
if (!RightRay.IsColliding() && LeftRay.IsColliding())
|
||||
{
|
||||
_newDirection = Vector2.Left;
|
||||
EmitSignalDirectionChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSpriteFlip()
|
||||
{
|
||||
Sprite.FlipH = _direction == Vector2.Left;
|
||||
}
|
||||
|
||||
private void HandleMovement(double delta)
|
||||
{
|
||||
var root = Owner as Node2D;
|
||||
if (root == null) return;
|
||||
|
||||
root.Position += _direction * Speed * (float)delta;
|
||||
}
|
||||
|
||||
private void OnDirectionChanged()
|
||||
{
|
||||
if (_direction == _newDirection || _triggeredDirectionChange)
|
||||
return;
|
||||
|
||||
_triggeredDirectionChange = true;
|
||||
_direction = Vector2.Zero;
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void OnTimerTimeout()
|
||||
{
|
||||
_timer.Stop();
|
||||
_direction = _newDirection;
|
||||
_triggeredDirectionChange = false;
|
||||
}
|
||||
|
||||
private void SetupTimer()
|
||||
{
|
||||
_timer = new Timer();
|
||||
AddChild(_timer);
|
||||
_timer.WaitTime = WaitTime;
|
||||
_timer.OneShot = true;
|
||||
_timer.Timeout += OnTimerTimeout;
|
||||
}
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
class_name PlatformMovement
|
||||
extends PlayerMovement
|
||||
|
||||
@export var speed: float = 300.0
|
||||
@export var jump_height: float = 100
|
||||
@export var jump_time_to_peak: float = 0.5
|
||||
@export var jump_time_to_descent: float = 0.4
|
||||
@export var coyote_frames: int = 6
|
||||
@export var jump_sfx: AudioStreamPlayer2D
|
||||
@export var rotation_target: Node2D
|
||||
@export var body: CharacterBody2D
|
||||
|
||||
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
|
||||
var was_last_floor := false
|
||||
var coyote_mode := false
|
||||
var coyote_timer: Timer
|
||||
var last_direction := Vector2.RIGHT
|
||||
|
||||
@onready var jump_velocity: float = ((2.0 * jump_height) / jump_time_to_peak) * -1.0
|
||||
@onready var jump_gravity: float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
|
||||
@onready var fall_gravity: float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not body:
|
||||
return
|
||||
|
||||
coyote_timer = Timer.new()
|
||||
coyote_timer.one_shot = true
|
||||
coyote_timer.wait_time = coyote_frames / 60.0
|
||||
coyote_timer.timeout.connect(on_coyote_timer_timeout)
|
||||
add_child(coyote_timer)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not body or not enabled:
|
||||
return
|
||||
|
||||
if body.velocity.x > 0.0:
|
||||
rotation_target.rotation = deg_to_rad(-10)
|
||||
elif body.velocity.x < 0.0:
|
||||
rotation_target.rotation = deg_to_rad(10)
|
||||
else:
|
||||
rotation_target.rotation = 0
|
||||
|
||||
calculate_jump_vars()
|
||||
|
||||
|
||||
func _physics_process(delta) -> void:
|
||||
if not body or not enabled:
|
||||
return
|
||||
|
||||
if body.is_on_floor():
|
||||
was_last_floor = true
|
||||
coyote_mode = false # Reset coyote mode when back on the floor
|
||||
coyote_timer.stop() # Stop timer when grounded
|
||||
else:
|
||||
if was_last_floor: # Start coyote timer only once
|
||||
coyote_mode = true
|
||||
coyote_timer.start()
|
||||
was_last_floor = false
|
||||
|
||||
if not body.is_on_floor():
|
||||
body.velocity.y += calculate_gravity() * delta
|
||||
|
||||
if Input.is_action_pressed("jump") and (body.is_on_floor() or coyote_mode):
|
||||
jump()
|
||||
|
||||
if Input.is_action_just_pressed("down"):
|
||||
body.position.y += 1
|
||||
|
||||
var direction := Input.get_axis("left", "right")
|
||||
if direction != 0:
|
||||
last_direction = handle_direction(direction)
|
||||
|
||||
if direction:
|
||||
body.velocity.x = direction * speed
|
||||
else:
|
||||
body.velocity.x = move_toward(body.velocity.x, 0, speed)
|
||||
|
||||
previous_velocity = body.velocity
|
||||
body.move_and_slide()
|
||||
|
||||
|
||||
func jump() -> void:
|
||||
if not body:
|
||||
return
|
||||
|
||||
body.velocity.y = jump_velocity
|
||||
coyote_mode = false
|
||||
if jump_sfx:
|
||||
jump_sfx.play()
|
||||
|
||||
|
||||
func calculate_gravity() -> float:
|
||||
return jump_gravity if body.velocity.y < 0.0 else fall_gravity
|
||||
|
||||
|
||||
func on_coyote_timer_timeout() -> void:
|
||||
coyote_mode = false
|
||||
|
||||
|
||||
func handle_direction(input_dir: float) -> Vector2:
|
||||
if input_dir > 0:
|
||||
return Vector2.RIGHT
|
||||
elif input_dir < 0:
|
||||
return Vector2.LEFT
|
||||
return last_direction
|
||||
|
||||
|
||||
func on_ship_entered() -> void:
|
||||
rotation_target.rotation = 0
|
||||
|
||||
|
||||
func calculate_jump_vars() -> void:
|
||||
jump_velocity = ((2.0 * jump_height) / jump_time_to_peak) * -1.0
|
||||
jump_gravity = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
|
||||
fall_gravity = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
|
@@ -1 +0,0 @@
|
||||
uid://c1wtrgw0x77xo
|
Reference in New Issue
Block a user