3D Boss scripts implementation

This commit is contained in:
Marco 2025-06-30 17:28:19 +02:00
commit dbf7f1a963
29 changed files with 1805 additions and 1188 deletions

View file

@ -0,0 +1,47 @@
using System.Threading.Tasks;
using Cirno.Scripts.Resources;
using Godot;
namespace Cirno.Scripts.AttackPatterns;
[GlobalClass]
[Tool]
public partial class WaitPattern : AttackPattern
{
[Export] public float SecondsToWait = 1f;
public override IPatternMachine MakeMachine(Node parent)
{
return new WaitPatternMachine(this, parent);
}
public class WaitPatternMachine(WaitPattern pattern, Node parent) : IPatternMachine
{
public Node Parent => parent;
private bool _isComplete = false;
protected IScriptHost3D Boss;
public void Start()
{
_isComplete = false;
_ = WaitAsync(pattern.SecondsToWait);
}
public void UpdatePattern(double delta)
{
}
private async Task WaitAsync(float time)
{
await Task.Delay((int)time * 1000);
_isComplete = true;
}
public bool IsComplete()
{
return _isComplete;
}
}
}

View file

@ -0,0 +1 @@
uid://cg7gi3tva4gvw

View file

@ -0,0 +1,14 @@
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Components.Actors._3D;
public partial class BulletSprite3D : Sprite3D
{
private Bullet3D _parent;
public override void _Ready()
{
_parent = GetParent<Bullet3D>();
}
}

View file

@ -0,0 +1 @@
uid://c4j8jwgs6okew

View file

@ -0,0 +1,117 @@
using System.Threading.Tasks;
using Cirno.Scripts.AttackPatterns;
using Cirno.Scripts.Components.Actors;
using Cirno.Scripts.Components.FSM.Enemy._3D;
using Cirno.Scripts.Controllers;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Resources.ScriptableBullets;
using Godot;
namespace Cirno.Scripts.Components.FSM.Boss._3D;
public partial class BossScriptHostModule3D : ModuleBase<EnemyState, CharacterBody3D>, IScriptHost3D
{
[Export] public BossScript BossScript { get; set; }
[Export]
public EnemyStorage3D StorageModule { get; private set; }
[Export] public DamageReceiver3D DamageReceiver { get; private set; }
public Node3D ParentObject => _machine.MainObject;
public Vector3 HomePosition => StorageModule.HomePosition;
private IStateMachine<EnemyState, CharacterBody3D> _machine;
private int _currentPhaseIndex = 0;
private BossPhase CurrentPhase => BossScript.Phases[_currentPhaseIndex];
private bool _waiting = false;
public float CurrentHealth => DamageReceiver.HealthProvider.CurrentResource;
public void ChangeSpriteDirection(Vector2 direction)
{
}
public override void EnterState(EnemyState state)
{
DamageReceiver.ChangeState(true);
DamageReceiver.HealthProvider.ResourceDepleted += HealthProviderOnResourceDepleted;
StartPhase(CurrentPhase);
}
public override void ExitState(EnemyState state)
{
DamageReceiver.HealthProvider.ResourceDepleted -= HealthProviderOnResourceDepleted;
DamageReceiver.ChangeState(false);
}
public override void Init(IStateMachine<EnemyState, CharacterBody3D> machine)
{
_machine = machine;
if (StorageModule.Root.EnemyResource.BossScript is not null)
{
this.BossScript = StorageModule.Root.EnemyResource.BossScript;
}
}
private void HealthProviderOnResourceDepleted()
{
_machine.SetState(EnemyState.Dead);
}
public override void Process(double delta)
{
if (_waiting) return;
CurrentPhase.UpdatePhase(delta);
if (CurrentHealth <= CurrentPhase.Threshold && _currentPhaseIndex + 1 < BossScript.Phases.Count)
{
_currentPhaseIndex++;
//_bossHud.SpellCardName = CurrentPhase.PhaseName;
StartPhase(CurrentPhase);
}
}
public override void PhysicsProcess(double delta)
{
}
private void StartPhase(BossPhase phase)
{
PoolingManager.Instance.ClearBullets();
//GameController.Instance.ClearBullets();
if (phase.PlayAnimation)
{
_waiting = true;
DamageReceiver.ChangeState(false);
_ = SwitchPhase(phase);
}
else
{
phase.Start(this);
}
}
private async Task SwitchPhase(BossPhase phase)
{
await PlayAnimation();
_waiting = false;
DamageReceiver.ChangeState(true);
phase.Start(this);
}
private async Task PlayAnimation()
{
await Task.Delay(1000);
}
}

View file

@ -0,0 +1 @@
uid://dej2tbl70yiyp

View file

@ -0,0 +1,59 @@
using System.Threading.Tasks;
using Cirno.Scripts.Components.FSM.Enemy._3D;
using Cirno.Scripts.Enums;
using Godot;
namespace Cirno.Scripts.Components.FSM.Boss._3D;
public partial class Idle : EnemyStateBase3D
{
public override EnemyState StateId => EnemyState.Idle;
[Export] public EnemyStorage3D Storage { get; private set; }
[Export] public GravityProvider GravityProvider { get; private set; }
[Export] public bool DebugEnabled { get; set; } = false;
public override void EnterState()
{
base.EnterState();
// player detection
// damage receiver will be a module
GD.Print("Entered Idle");
_ = DelayStart();
}
public override void ExitState()
{
base.ExitState();
// Disable DamageReceiver
}
private async Task DelayStart()
{
await Task.Delay(1000);
ChangeState(EnemyState.Shooting);
}
public override void PhysicsProcessState(double delta)
{
base.PhysicsProcessState(delta);
MainObject.Velocity = new Vector3(MainObject.Velocity.X, GravityProvider.CalculateGravityVelocity(MainObject.Velocity.Y, delta), MainObject.Velocity.Z);
MainObject.MoveAndSlide();
}
public override void ProcessState(double delta)
{
base.ProcessState(delta);
}
}

View file

@ -0,0 +1 @@
uid://05agh8h015fy

View file

@ -0,0 +1,45 @@
using Cirno.Scripts.Components.FSM.Enemy._3D;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Utils;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Components.FSM.Boss._3D;
public partial class Shooting : EnemyStateBase3D
{
public override EnemyState StateId => EnemyState.Shooting;
[Export] public EnemyStorage3D Storage { get; private set; }
[Export] public GravityProvider GravityProvider { get; private set; }
public override void EnterState()
{
base.EnterState();
// Enable damage receiver
}
public override void ExitState()
{
base.ExitState();
}
public override void PhysicsProcessState(double delta)
{
base.PhysicsProcessState(delta);
// Calculate gravity
MainObject.Velocity = new Vector3(MainObject.Velocity.X, GravityProvider.CalculateGravityVelocity(MainObject.Velocity.Y, delta), MainObject.Velocity.Z);
MainObject.MoveAndSlide();
}
public override void ProcessState(double delta)
{
base.ProcessState(delta);
}
}

View file

@ -0,0 +1 @@
uid://d2838a10x33w8

View file

@ -141,5 +141,9 @@ public partial class PoolingManager : Node
//this.CreateChild<Bullet>(bulletData.BulletScene);
return bullet as IBullet;
}
public void ClearBullets()
{
// TODO: Implement
}
}

View file

@ -0,0 +1,61 @@
using Cirno.Scripts.AttackPatterns;
using Cirno.Scripts.Utils;
using Godot;
namespace Cirno.Scripts.Resources.BulletScripts;
[GlobalClass]
[Tool]
public partial class SimpleMovementPattern3D : AttackPattern
{
[Export] private Vector2 relativeTargetPosition;
[Export] private float moveDuration = 2f;
[Export] private Tween.TransitionType transitionType = Tween.TransitionType.Linear;
[Export] private Tween.EaseType easeType = Tween.EaseType.InOut;
public override IPatternMachine MakeMachine(Node parent)
{
return new SimpleMovementPatternMachine(this, parent);
}
public class SimpleMovementPatternMachine(SimpleMovementPattern3D pattern, Node parent) : IPatternMachine
{
public Node Parent => parent;
private Tween tween;
private bool isComplete = false;
protected IScriptHost3D Boss;
public void Start()
{
if (parent is not IScriptHost3D boss)
return;
Boss = boss;
tween = Parent.CreateTween();
isComplete = false;
Vector2 targetPosition = (Boss?.HomePosition.ToVector2() ?? boss.ParentObject.GlobalPosition.ToVector2()) + pattern.relativeTargetPosition;
var targetPosition3D = targetPosition.ToVector3(boss.ParentObject.GlobalPosition.Y);
boss.ChangeSpriteDirection(-(boss.ParentObject.GlobalPosition.ToVector2() - targetPosition));
tween.TweenProperty(boss.ParentObject, "global_position", targetPosition3D, pattern.moveDuration)
.SetTrans(pattern.transitionType)
.SetEase(pattern.easeType)
.Finished += () =>
{
isComplete = true;
boss.ChangeSpriteDirection(Vector2.Zero);
};
}
public void UpdatePattern(double delta) { }
public bool IsComplete()
{
return isComplete;
}
}
}

View file

@ -0,0 +1 @@
uid://fo8sf11p058s

View file

@ -1,4 +1,5 @@
using Godot;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Resources.Modifiers;
@ -6,7 +7,7 @@ namespace Cirno.Scripts.Resources.Modifiers;
[Tool]
public partial class DelayedContinuousRotationModifier : TimeModifier
{
public override void Update(Bullet bullet, double delta, double elapsed)
public override void Update(IBullet bullet, double delta, double elapsed)
{
bullet.RotateSpriteDegrees((float)(Value * delta));
}

View file

@ -1,4 +1,5 @@
using Godot;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Resources.Modifiers;
@ -6,7 +7,7 @@ namespace Cirno.Scripts.Resources.Modifiers;
[Tool]
public partial class DelayedPlayerFacingModifier : TimeModifier
{
public override void Start(Bullet bullet)
public override void Start(IBullet bullet)
{
bullet.FacePlayer();
}

View file

@ -1,4 +1,5 @@
using Godot;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Resources.Modifiers;
@ -6,7 +7,7 @@ namespace Cirno.Scripts.Resources.Modifiers;
[Tool]
public partial class DelayedRotationModifier : TimeModifier
{
public override void Start(Bullet bullet)
public override void Start(IBullet bullet)
{
bullet.RotateBullet(this.Value);
}

View file

@ -1,4 +1,5 @@
using Cirno.Scripts.Actors;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Resources.Modifiers;
@ -13,7 +14,7 @@ public partial class DelayedSpeedIncreaseModifier : TimeModifier
[Export] public float Duration { get; set; } = 1.0f;
public override void Update(Bullet bullet, double delta, double elapsed)
public override void Update(IBullet bullet, double delta, double elapsed)
{
float easedValue = ApplyEasing(Value, elapsed);
bullet.Speed += easedValue;

View file

@ -1,4 +1,5 @@
using Cirno.Scripts.Actors;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Resources.Modifiers;
@ -7,7 +8,7 @@ namespace Cirno.Scripts.Resources.Modifiers;
[Tool]
public partial class DelayedSpeedModifier : TimeModifier
{
public override void Start(Bullet bullet)
public override void Start(IBullet bullet)
{
bullet.Speed = this.Value;
}

View file

@ -1,4 +1,5 @@
using Cirno.Scripts.Actors;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Resources;
@ -28,12 +29,12 @@ public partial class TimeModifier : Resource
return this.MemberwiseClone() as TimeModifier;
}
public virtual void Start(Bullet bullet)
public virtual void Start(IBullet bullet)
{
}
public virtual void Update(Bullet bullet, double delta, double elapsed)
public virtual void Update(IBullet bullet, double delta, double elapsed)
{
}

View file

@ -3,6 +3,7 @@ using System.Linq;
using Cirno.Scripts.Components;
using Cirno.Scripts.Controllers;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Utils;
using Godot;
namespace Cirno.Scripts.Weapons;
@ -31,6 +32,8 @@ public partial class Bullet3D : Area3D, IBullet
public bool IsFrozen { get; private set; } = false;
public bool Enabled { get; private set; } = false;
public float SpriteRotation { get; private set; } = 0f;
[Signal]
public delegate void OnDestroyEventHandler();
@ -120,26 +123,26 @@ public partial class Bullet3D : Area3D, IBullet
private void ApplyTimeModifiers(double delta)
{
return;
// foreach (var modifier in _modifiers)
// {
// if (_elapsedTime >= modifier.TimeModifier.TimeInSeconds)
// {
// if (!modifier.Applied)
// {
// modifier.Applied = true;
// modifier.TimeModifier.Start(this);
// modifier.Elapsed = 0;
// }
// else
// {
// modifier.Elapsed += delta;
// }
//
// modifier.TimeModifier.Update(this, delta, modifier.Elapsed);
//
// }
// }
foreach (var modifier in _modifiers)
{
if (_elapsedTime >= modifier.TimeModifier.TimeInSeconds)
{
if (!modifier.Applied)
{
modifier.Applied = true;
modifier.TimeModifier.Start(this);
modifier.Elapsed = 0;
}
else
{
modifier.Elapsed += delta;
}
modifier.TimeModifier.Update(this, delta, modifier.Elapsed);
}
}
}
public virtual void RotateBullet(float degrees)
@ -148,29 +151,43 @@ public partial class Bullet3D : Area3D, IBullet
_direction = _direction.Rotated(radians).Normalized(); // Rotate direction
if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
RotateSprite(SpriteRotation + radians);
//SetRotation(Rotation + radians);
}
public virtual void RotateSpriteDegrees(float degrees)
{
if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
SpriteRotation = Mathf.DegToRad(Mathf.RadToDeg(SpriteRotation) + degrees);
//SetRotationDegrees(RotationDegrees + degrees);
}
public virtual void RotateSprite(float radians)
{
if (!BulletInfo.Attributes.HasFlag(BulletFlags.Rotateable)) return;
SpriteRotation += radians;
Vector3 axis = Basis.FromEuler(new Vector3(
Mathf.DegToRad(-45f),
Mathf.DegToRad(45f),
0f
)).Z;
Rotate(axis, radians);
//SetRotation(Rotation + radians);
}
public void FacePlayer()
{
// if (_gameManager.Player != null)
// {
// //_direction = (_gameManager.PlayerPosition.Value - this.GlobalPosition).Normalized();
// RotateBullet(0); // quick hack to rotate lasers
// //LookAt(player.GlobalPosition);
// }
if (GameController.Instance.PlayerPosition.HasValue)
{
_direction = (GameController.Instance.PlayerPosition.Value.ToVector2() - this.GlobalPosition.ToVector2()).Normalized();
RotateBullet(0); // quick hack to rotate lasers
//LookAt(player.GlobalPosition);
}
}