Add hit animations and death for bosses

This commit is contained in:
2024-10-03 01:07:05 -07:00
parent e6584ec4dc
commit ed333f373b
13 changed files with 584 additions and 329 deletions

View File

@@ -1,4 +1,5 @@
using Chickensoft.AutoInject;
using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
@@ -12,6 +13,8 @@ namespace GameJamDungeon
public Timer AttackTimer { get; }
public void Activate();
public AutoProp<double> CurrentHP { get; }
}
[Meta(typeof(IAutoNode))]
@@ -21,6 +24,8 @@ namespace GameJamDungeon
public IBossLogic BossLogic { get; set; } = default!;
[Export] public EnemyStatResource BossResource { get; set; } = default!;
IBossLogic IProvide<IBossLogic>.Value() => BossLogic;
public BossLogic.IBinding BossBinding { get; set; } = default!;
@@ -31,6 +36,12 @@ namespace GameJamDungeon
[Node] public Timer AttackTimer { get; set; } = default!;
[Node] public AnimationPlayer HitAnimation { get; set; } = default!;
[Node] public Area3D Hitbox { get; set; } = default!;
public AutoProp<double> CurrentHP { get; set; }
public void Setup()
{
BossLogic = new BossLogic();
@@ -44,10 +55,38 @@ namespace GameJamDungeon
public void OnResolved()
{
BossBinding = BossLogic.Bind();
BossBinding
.Handle((in BossLogic.Output.HitByPlayer output) =>
{
})
.Handle((in BossLogic.Output.Defeated output) =>
{
QueueFree();
});
this.Provide();
BossLogic.Start();
CurrentHP = new AutoProp<double>(BossResource.MaximumHP);
CurrentHP.Sync += OnHPChanged;
AttackTimer.Timeout += AttackTimer_Timeout;
Hitbox.AreaEntered += Hitbox_AreaEntered;
}
private void Hitbox_AreaEntered(Area3D area)
{
if (area is IHitbox)
{
HitAnimation.Play("Hit");
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateWeaponAttackDamage(GameRepo.PlayerData.CurrentAttack.Value + GameRepo.PlayerData.BonusAttack, BossResource, GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats, isCriticalHit);
GD.Print($"Enemy Hit for {damage} damage.");
BossLogic.Input(new BossLogic.Input.HitByPlayer(damage));
}
}
public void Activate()
@@ -71,5 +110,11 @@ namespace GameJamDungeon
BossLogic.Input(new BossLogic.Input.PhysicsTick(delta));
MoveAndSlide();
}
private void OnHPChanged(double newHP)
{
if (newHP <= 0)
BossLogic.Input(new BossLogic.Input.BossDefeated());
}
}
}

View File

@@ -11,6 +11,10 @@
public readonly record struct PrimaryAttack();
public readonly record struct SecondaryAttack();
public readonly record struct HitByPlayer(double Damage);
public readonly record struct BossDefeated();
}
}
}

View File

@@ -4,7 +4,9 @@
{
public static class Output
{
public readonly record struct HitByPlayer(double CurrentHP);
public readonly record struct Defeated();
}
}
}

View File

@@ -1,10 +1,14 @@
@startuml BossLogic
state "BossLogic State" as GameJamDungeon_BossLogic_State {
state "Alive" as GameJamDungeon_BossLogic_State_Alive {
state "Idle" as GameJamDungeon_BossLogic_State_Idle
}
state "ApproachPlayer" as GameJamDungeon_BossLogic_State_ApproachPlayer
state "Defeated" as GameJamDungeon_BossLogic_State_Defeated
state "EngagePlayer" as GameJamDungeon_BossLogic_State_EngagePlayer
state "Idle" as GameJamDungeon_BossLogic_State_Idle
}
GameJamDungeon_BossLogic_State_Alive --> GameJamDungeon_BossLogic_State_Alive : HitByPlayer
GameJamDungeon_BossLogic_State_ApproachPlayer --> GameJamDungeon_BossLogic_State_ApproachPlayer : PhysicsTick
GameJamDungeon_BossLogic_State_ApproachPlayer --> GameJamDungeon_BossLogic_State_EngagePlayer : PhysicsTick
GameJamDungeon_BossLogic_State_EngagePlayer --> GameJamDungeon_BossLogic_State_ApproachPlayer : PhysicsTick
@@ -12,5 +16,9 @@ GameJamDungeon_BossLogic_State_EngagePlayer --> GameJamDungeon_BossLogic_State_E
GameJamDungeon_BossLogic_State_EngagePlayer --> GameJamDungeon_BossLogic_State_EngagePlayer : PrimaryAttack
GameJamDungeon_BossLogic_State_EngagePlayer --> GameJamDungeon_BossLogic_State_EngagePlayer : SecondaryAttack
GameJamDungeon_BossLogic_State_Idle --> GameJamDungeon_BossLogic_State_ApproachPlayer : Activate
GameJamDungeon_BossLogic_State : On() → HitByPlayer
GameJamDungeon_BossLogic_State_Alive : OnHitByPlayer → HitByPlayer
[*] --> GameJamDungeon_BossLogic_State_Idle
@enduml

View File

@@ -0,0 +1,31 @@
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon
{
public partial class BossLogic
{
public partial record State
{
[Meta, Id("boss_logic_state_alive")]
public abstract partial record Alive : State, IGet<Input.HitByPlayer>, IGet<Input.BossDefeated>
{
}
public Transition On(in Input.HitByPlayer input)
{
var enemy = Get<IBoss>();
enemy.CurrentHP.OnNext(enemy.CurrentHP.Value - input.Damage);
GD.Print("Current HP: " + enemy.CurrentHP.Value);
Output(new Output.HitByPlayer());
return ToSelf();
}
public Transition On(in Input.BossDefeated input)
{
Output(new Output.Defeated());
return To<Defeated>();
}
}
}
}

View File

@@ -8,7 +8,7 @@ namespace GameJamDungeon
public partial record State
{
[Meta, Id("boss_logic_state_approach_player")]
public partial record ApproachPlayer : State, IGet<Input.PhysicsTick>
public partial record ApproachPlayer : Alive, IGet<Input.PhysicsTick>
{
public Transition On(in Input.PhysicsTick input)
{

View File

@@ -0,0 +1,15 @@
using Chickensoft.Introspection;
namespace GameJamDungeon
{
public partial class BossLogic
{
public partial record State
{
[Meta, Id("boss_logic_state_defeated")]
public partial record Defeated : State
{
}
}
}
}

View File

@@ -8,7 +8,7 @@ namespace GameJamDungeon
public partial record State
{
[Meta, Id("boss_logic_state_engage_player")]
public partial record EngagePlayer : State, IGet<Input.PhysicsTick>, IGet<Input.PrimaryAttack>, IGet<Input.SecondaryAttack>
public partial record EngagePlayer : Alive, IGet<Input.PhysicsTick>, IGet<Input.PrimaryAttack>, IGet<Input.SecondaryAttack>
{
public EngagePlayer()
{

View File

@@ -1,5 +1,4 @@
using Chickensoft.Introspection;
using static GameJamDungeon.BossLogic.Input;
namespace GameJamDungeon
{
@@ -8,7 +7,7 @@ namespace GameJamDungeon
public partial record State
{
[Meta, Id("boss_logic_state_idle")]
public partial record Idle : State, IGet<Activate>
public partial record Idle : Alive, IGet<Input.Activate>
{
public Idle()
{
@@ -21,7 +20,7 @@ namespace GameJamDungeon
}
);
}
public Transition On(in Activate input)
public Transition On(in Input.Activate input)
{
return To<ApproachPlayer>();
}

View File

@@ -47,7 +47,7 @@ void fragment() {
[resource]
render_priority = 0
shader = SubResource("Shader_veoq4")
shader_parameter/albedo = Color(0.784314, 0, 0, 1)
shader_parameter/albedo = Color(0.784314, 0, 0, 0.00784314)
shader_parameter/point_size = 1.0
shader_parameter/roughness = 1.0
shader_parameter/metallic_texture_channel = null