From 4d419b9010cda9635fbb1e1a72534a8444921ab3 Mon Sep 17 00:00:00 2001 From: Minimata Date: Fri, 23 Jan 2026 13:31:11 +0100 Subject: [PATCH] basic healthbars for enemies --- Movement tests.sln.DotSettings.user | 1 + assets/ui/white-square-100px.png | 3 ++ assets/ui/white-square-100px.png.import | 42 +++++++++++++++++++ components/health/CHealth.cs | 9 ++-- components/health/CHealthbar.cs | 26 ++++++++++++ components/health/CHealthbar.cs.uid | 1 + components/health/CHealthbar.tscn | 27 ++++++++++++ interfaces/IDamageable.cs | 1 + interfaces/IHealthable.cs | 6 ++- player_controller/Scripts/PlayerController.cs | 11 ++--- scenes/enemies/Enemy.cs | 18 +++++--- scenes/enemies/flying_enemy/flying_enemy.tscn | 6 ++- .../grounded_enemy/grounded_enemy.tscn | 6 ++- 13 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 assets/ui/white-square-100px.png create mode 100644 assets/ui/white-square-100px.png.import create mode 100644 components/health/CHealthbar.cs create mode 100644 components/health/CHealthbar.cs.uid create mode 100644 components/health/CHealthbar.tscn diff --git a/Movement tests.sln.DotSettings.user b/Movement tests.sln.DotSettings.user index 5d33025d..9aadd0c2 100644 --- a/Movement tests.sln.DotSettings.user +++ b/Movement tests.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/assets/ui/white-square-100px.png b/assets/ui/white-square-100px.png new file mode 100644 index 00000000..cc7640e3 --- /dev/null +++ b/assets/ui/white-square-100px.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:233b5d84e5b9e78c69df0353ad983a01c932d6a4b3932e08cea6a5d103d5939b +size 344 diff --git a/assets/ui/white-square-100px.png.import b/assets/ui/white-square-100px.png.import new file mode 100644 index 00000000..886c781f --- /dev/null +++ b/assets/ui/white-square-100px.png.import @@ -0,0 +1,42 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vpcxswwn1mt6" +path.s3tc="res://.godot/imported/white-square-100px.png-45d5a1e60348740b59c87085347e502a.s3tc.ctex" +path.etc2="res://.godot/imported/white-square-100px.png-45d5a1e60348740b59c87085347e502a.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/ui/white-square-100px.png" +dest_files=["res://.godot/imported/white-square-100px.png-45d5a1e60348740b59c87085347e502a.s3tc.ctex", "res://.godot/imported/white-square-100px.png-45d5a1e60348740b59c87085347e502a.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/components/health/CHealth.cs b/components/health/CHealth.cs index dd3a80e3..69ddd9bd 100644 --- a/components/health/CHealth.cs +++ b/components/health/CHealth.cs @@ -5,7 +5,7 @@ using Movementtests.interfaces; [GlobalClass] public partial class CHealth : Node, IHealthable { - public event Action HealthChanged; + public event Action HealthChanged; public event Action HealthDepleted; [Export] @@ -18,15 +18,18 @@ public partial class CHealth : Node, IHealthable CurrentHealth = RHealth.StartingHealth; } - public void ReduceHealth(IDamageable source, DamageRecord damageRecord) + public HealthChangedRecord ReduceHealth(IDamageable source, DamageRecord damageRecord) { + var previousHealth = CurrentHealth; CurrentHealth -= damageRecord.Damage.DamageDealt; - HealthChanged?.Invoke(this, CurrentHealth); + var record = new HealthChangedRecord(CurrentHealth, previousHealth, RHealth.StartingHealth); + HealthChanged?.Invoke(this, record); if (CurrentHealth <= 0) { CurrentHealth = 0; HealthDepleted?.Invoke(this); } + return record; } } diff --git a/components/health/CHealthbar.cs b/components/health/CHealthbar.cs new file mode 100644 index 00000000..7947be6e --- /dev/null +++ b/components/health/CHealthbar.cs @@ -0,0 +1,26 @@ +using Godot; +using System; +using Movementtests.interfaces; + +[GlobalClass] +public partial class CHealthbar : Node3D +{ + private Sprite3D _currentHealth; + private float _initialXScale; + + public override void _Ready() + { + Visible = false; + _currentHealth = GetNode("Health"); + _initialXScale = _currentHealth.Scale.X; + } + + public void OnHealthChanged(HealthChangedRecord healthChanged) + { + if (healthChanged.MaxHealth == 0) return; + + Visible = true; + var healthPercentage = healthChanged.CurrentHealth / healthChanged.MaxHealth; + _currentHealth.Scale = new Vector3(_initialXScale * healthPercentage, _currentHealth.Scale.Y, _currentHealth.Scale.Z); + } +} diff --git a/components/health/CHealthbar.cs.uid b/components/health/CHealthbar.cs.uid new file mode 100644 index 00000000..00a87e12 --- /dev/null +++ b/components/health/CHealthbar.cs.uid @@ -0,0 +1 @@ +uid://dve6vg6yvg4y8 diff --git a/components/health/CHealthbar.tscn b/components/health/CHealthbar.tscn new file mode 100644 index 00000000..25ac28f3 --- /dev/null +++ b/components/health/CHealthbar.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://bwx2um43k0ou4"] + +[ext_resource type="Texture2D" uid="uid://vpcxswwn1mt6" path="res://assets/ui/white-square-100px.png" id="1_jlvej"] +[ext_resource type="Script" uid="uid://dve6vg6yvg4y8" path="res://components/health/CHealthbar.cs" id="1_w5itk"] + +[node name="CHealthBar" type="Node3D"] +script = ExtResource("1_w5itk") + +[node name="Bar" type="Sprite3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.1, 0, 0, 0, 1, 0, 0, 0) +cast_shadow = 0 +billboard = 1 +transparent = false +double_sided = false +no_depth_test = true +texture = ExtResource("1_jlvej") + +[node name="Health" type="Sprite3D" parent="."] +transform = Transform3D(0.99, 0, 0, 0, 0.09, 0, 0, 0, 1, 0, 0, 0) +cast_shadow = 0 +modulate = Color(0.7191989, 0.19340843, 1.92523e-07, 1) +billboard = 1 +transparent = false +double_sided = false +no_depth_test = true +render_priority = 10 +texture = ExtResource("1_jlvej") diff --git a/interfaces/IDamageable.cs b/interfaces/IDamageable.cs index 8fb7e335..96729c31 100644 --- a/interfaces/IDamageable.cs +++ b/interfaces/IDamageable.cs @@ -8,6 +8,7 @@ public record DamageRecord(Node3D Source, RDamage Damage); public interface IDamageable { event Action DamageTaken; + DamageRecord TakeDamage(DamageRecord damageRecord); DamageRecord ComputeDamage(DamageRecord damageRecord); } \ No newline at end of file diff --git a/interfaces/IHealthable.cs b/interfaces/IHealthable.cs index 22db4842..1c3927ca 100644 --- a/interfaces/IHealthable.cs +++ b/interfaces/IHealthable.cs @@ -3,14 +3,16 @@ using Godot; namespace Movementtests.interfaces; +public record HealthChangedRecord(float CurrentHealth, float PreviousHealth, float MaxHealth); + public interface IHealthable { - event Action HealthChanged; + event Action HealthChanged; event Action HealthDepleted; [Export] RHealth RHealth { get; set; } float CurrentHealth { get; set; } - void ReduceHealth(IDamageable source, DamageRecord damageRecord); + HealthChangedRecord ReduceHealth(IDamageable source, DamageRecord damageRecord); } \ No newline at end of file diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs index 11b1a3bc..b5316c91 100644 --- a/player_controller/Scripts/PlayerController.cs +++ b/player_controller/Scripts/PlayerController.cs @@ -41,7 +41,7 @@ public partial class PlayerController : CharacterBody3D, /////////////////////////// public event Action DamageTaken; - public event Action HealthChanged; + public event Action HealthChanged; public event Action HealthDepleted; /////////////////////////// @@ -426,7 +426,7 @@ public partial class PlayerController : CharacterBody3D, } if (RKnockback != null) CKnockback!.RKnockback = RKnockback; - CDamageable.DamageTaken += ReduceHealth; + CDamageable.DamageTaken += (source, record) => ReduceHealth(source, record); CDamageable.DamageTaken += RegisterKnockback; CHealth.HealthDepleted += Kill; @@ -2042,11 +2042,12 @@ public partial class PlayerController : CharacterBody3D, { ResetTimeScale(); } - public void ReduceHealth(IDamageable source, DamageRecord damageRecord) + public HealthChangedRecord ReduceHealth(IDamageable source, DamageRecord damageRecord) { GD.Print("That's NOT fine"); - CHealth.ReduceHealth(source, damageRecord); - HealthChanged?.Invoke(this, CHealth.CurrentHealth); + var record = CHealth.ReduceHealth(source, damageRecord); + HealthChanged?.Invoke(this, record); + return record; } public void RegisterKnockback(IDamageable source, DamageRecord damageRecord) { diff --git a/scenes/enemies/Enemy.cs b/scenes/enemies/Enemy.cs index 868b40a7..a69fe52a 100644 --- a/scenes/enemies/Enemy.cs +++ b/scenes/enemies/Enemy.cs @@ -16,7 +16,7 @@ public partial class Enemy : CharacterBody3D, { // Signals and events public event Action DamageTaken; - public event Action HealthChanged; + public event Action HealthChanged; public event Action HealthDepleted; // Public export components @@ -54,6 +54,7 @@ public partial class Enemy : CharacterBody3D, // Private stuff private Area3D _damageBox; private Node3D _target; + private CHealthbar _healthbar; public override void _Ready() { @@ -75,6 +76,8 @@ public partial class Enemy : CharacterBody3D, if (CHealth is null) GD.PrintErr("This node needs a 'CHealth' child of type IHealthable!"); if (CKnockback is null) GD.PrintErr("This node needs a 'CKnockback' child of type IKnockbackable!"); + _healthbar = GetNode("CHealthBar"); + if (RMovement != null) CMovement!.RMovement = RMovement; if (RHealth != null) { @@ -86,9 +89,11 @@ public partial class Enemy : CharacterBody3D, public void SetupSignals() { - CDamageable.DamageTaken += ReduceHealth; + // Anonymous function call to erase return values of ReduceHealth + CDamageable.DamageTaken += (source, record) => ReduceHealth(source, record); CDamageable.DamageTaken += RegisterKnockback; CHealth.HealthDepleted += Kill; + HealthChanged += (source, record) => _healthbar.OnHealthChanged(record); } public override void _PhysicsProcess(double delta) @@ -147,11 +152,12 @@ public partial class Enemy : CharacterBody3D, return CDamageable.ComputeDamage(damageRecord); } - public void ReduceHealth(IDamageable source, DamageRecord damageRecord) + public HealthChangedRecord ReduceHealth(IDamageable source, DamageRecord damageRecord) { - if (CHealth is null) return; - CHealth.ReduceHealth(source, damageRecord); - HealthChanged?.Invoke(this, CHealth.CurrentHealth); + if (CHealth is null) return new HealthChangedRecord(0, 0, 0); + var record = CHealth.ReduceHealth(source, damageRecord); + HealthChanged?.Invoke(this, record); + return record; } public void Kill(IHealthable source) diff --git a/scenes/enemies/flying_enemy/flying_enemy.tscn b/scenes/enemies/flying_enemy/flying_enemy.tscn index 9f726774..8e48d499 100644 --- a/scenes/enemies/flying_enemy/flying_enemy.tscn +++ b/scenes/enemies/flying_enemy/flying_enemy.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=20 format=3 uid="uid://cmlud1hwkd6sv"] +[gd_scene load_steps=21 format=3 uid="uid://cmlud1hwkd6sv"] [ext_resource type="Script" uid="uid://bn7sc6id7n166" path="res://scenes/enemies/Enemy.cs" id="1_q8l7o"] [ext_resource type="Script" uid="uid://b6y3ugfydvch0" path="res://components/damage/RDamageModifier.cs" id="2_1bsgx"] @@ -7,6 +7,7 @@ [ext_resource type="Resource" uid="uid://bwqjaom4k7rc3" path="res://scenes/enemies/flying_enemy/flying_enemy_movement.tres" id="4_dejyg"] [ext_resource type="Script" uid="uid://bjwrpv3jpsc1e" path="res://components/health/CHealth.cs" id="4_ys4jv"] [ext_resource type="PackedScene" uid="uid://dmw5ibwrb483f" path="res://components/movement/CFlyingMovement.tscn" id="7_vaeds"] +[ext_resource type="PackedScene" uid="uid://bwx2um43k0ou4" path="res://components/health/CHealthbar.tscn" id="7_ykkxn"] [ext_resource type="Script" uid="uid://dtpxijlnb2c5" path="res://components/movement/RMovement.cs" id="8_on7rt"] [ext_resource type="Script" uid="uid://b0u23nkpaimyc" path="res://components/damage/CDamageable.cs" id="8_uotso"] [ext_resource type="PackedScene" uid="uid://bctpe34ddamg5" path="res://components/knockback/CKnockback.tscn" id="10_dejyg"] @@ -58,6 +59,9 @@ script = ExtResource("4_ys4jv") RHealth = ExtResource("2_ma2bq") metadata/_custom_type_script = "uid://bjwrpv3jpsc1e" +[node name="CHealthBar" parent="." instance=ExtResource("7_ykkxn")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0) + [node name="CDamageable" type="Node" parent="."] script = ExtResource("8_uotso") DamageModifiers = Array[Object]([SubResource("Resource_jnv07"), SubResource("Resource_53j1c")]) diff --git a/scenes/enemies/grounded_enemy/grounded_enemy.tscn b/scenes/enemies/grounded_enemy/grounded_enemy.tscn index 9bad7043..34388e97 100644 --- a/scenes/enemies/grounded_enemy/grounded_enemy.tscn +++ b/scenes/enemies/grounded_enemy/grounded_enemy.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=19 format=3 uid="uid://dxt0e2ugmttqq"] +[gd_scene load_steps=20 format=3 uid="uid://dxt0e2ugmttqq"] [ext_resource type="Script" uid="uid://bn7sc6id7n166" path="res://scenes/enemies/Enemy.cs" id="1_r6506"] [ext_resource type="Resource" uid="uid://otfc2snh8umc" path="res://scenes/enemies/grounded_enemy/grounded_enemy_damage.tres" id="2_bn56u"] @@ -7,6 +7,7 @@ [ext_resource type="Resource" uid="uid://bohbojc68j7y1" path="res://scenes/enemies/grounded_enemy/grounded_enemy_health.tres" id="2_w4lm8"] [ext_resource type="Resource" uid="uid://bqq6uukbdfysr" path="res://scenes/enemies/grounded_enemy/grounded_enemy_movement.tres" id="4_na24f"] [ext_resource type="Script" uid="uid://b0u23nkpaimyc" path="res://components/damage/CDamageable.cs" id="7_1tw73"] +[ext_resource type="PackedScene" uid="uid://bwx2um43k0ou4" path="res://components/health/CHealthbar.tscn" id="7_18xwy"] [ext_resource type="PackedScene" uid="uid://dbr7ioio158ew" path="res://components/movement/CGroundedMovement.tscn" id="7_qyswd"] [ext_resource type="Script" uid="uid://dtpxijlnb2c5" path="res://components/movement/RMovement.cs" id="8_6d4gl"] [ext_resource type="PackedScene" uid="uid://bctpe34ddamg5" path="res://components/knockback/CKnockback.tscn" id="10_jqqi6"] @@ -53,6 +54,9 @@ script = ExtResource("2_gsmti") RHealth = ExtResource("2_w4lm8") metadata/_custom_type_script = "uid://bjwrpv3jpsc1e" +[node name="CHealthBar" parent="." instance=ExtResource("7_18xwy")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.2, 0) + [node name="CDamageable" type="Node" parent="."] script = ExtResource("7_1tw73") DamageModifiers = Array[Object]([SubResource("Resource_qj0ob")])