From aa95e704d496a7b31a002ee1983de5e2662bef8e Mon Sep 17 00:00:00 2001 From: Ruslan Ignatov Date: Mon, 29 Jun 2026 19:45:21 +0300 Subject: [PATCH] Added player health and health bar --- game/entities/player/player.gd | 19 +++++++ game/hud/health_bar.gd | 71 +++++++++++++++++++++++++ game/hud/health_bar.gd.uid | 1 + game/hud/health_bar.tscn | 79 ++++++++++++++++++++++++++++ game/levels/abstract_level.gd | 26 +++++++-- game/levels/abstract_level.tscn | 9 ++++ game/shaders/health_bar.gdshader | 37 +++++++++++++ game/shaders/health_bar.gdshader.uid | 1 + images/hud/health_bar.png | 3 ++ images/hud/health_bar.png.import | 40 ++++++++++++++ 10 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 game/hud/health_bar.gd create mode 100644 game/hud/health_bar.gd.uid create mode 100644 game/hud/health_bar.tscn create mode 100644 game/shaders/health_bar.gdshader create mode 100644 game/shaders/health_bar.gdshader.uid create mode 100644 images/hud/health_bar.png create mode 100644 images/hud/health_bar.png.import diff --git a/game/entities/player/player.gd b/game/entities/player/player.gd index e930ac1..3461714 100644 --- a/game/entities/player/player.gd +++ b/game/entities/player/player.gd @@ -2,6 +2,13 @@ class_name Player extends CharacterBody2D +signal health_changed(value: float) +signal dead() + + +const MAX_HEALTH = 100.0 + + @export_group("Movement", "move") @export_range(0.0, 1000.0) var move_max_speed_normal := 350 @export_range(0.0, 1000.0) var move_max_speed_slowed := 250 @@ -28,6 +35,11 @@ var _slow_down_tween: Tween var _last_wall_normal: = 0.0 +var _current_health: = MAX_HEALTH: + set(value): + _current_health = value + health_changed.emit(_current_health) + @onready var player_sprite : AnimatedSprite2D = $PlayerSprite @onready var blade_sprite : AnimatedSprite2D = $BladeSprite @@ -126,6 +138,13 @@ func _input(event: InputEvent) -> void: _attack() +func deal_damage(value: float) -> void: + _current_health -= value + health_changed.emit(_current_health) + if _current_health < 0.0 or is_zero_approx(_current_health): + dead.emit() + + func _attack() -> void: var sprite := attack_left_sprite if velocity.x <= 0.0 else attack_right_sprite sprite.show() diff --git a/game/hud/health_bar.gd b/game/hud/health_bar.gd new file mode 100644 index 0000000..ec4b6de --- /dev/null +++ b/game/hud/health_bar.gd @@ -0,0 +1,71 @@ +class_name HealthBar +extends Node2D + +const ANIMATION_ALERT = "alert" + +const SHADER_MAX_VALUE = "shader_parameter/max_value" +const SHADER_CURRENT_VALUE = "shader_parameter/current_value" +const SHADER_OLD_VALUE = "shader_parameter/old_value" + + +const OLD_VALUE_DECRETION_RATE = 25.0 + + +var _max_value := 0.0: + set(value): + _max_value = value + if is_node_ready(): + health_sprite.material.set(SHADER_MAX_VALUE, _max_value) + +var _current_value := 0.0: + set(value): + _current_value = value + if is_node_ready(): + health_sprite.material.set(SHADER_CURRENT_VALUE, _current_value) + +var _old_value := 0.0: + set(value): + _old_value = value + if is_node_ready(): + health_sprite.material.set(SHADER_OLD_VALUE, _old_value) + +var _old_value_tween : Tween + +var _is_animation_needed := false + + +@onready var background_sprite : Sprite2D = $BackgroundSprite +@onready var alert_sprite : Sprite2D = $AlertSprite +@onready var health_sprite : Sprite2D = $HealthSprite + +@onready var animation_player : AnimationPlayer = $AnimationPlayer + + +func _ready() -> void: + alert_sprite.hide() + + +func _process(_delta: float) -> void: + if _is_animation_needed and not animation_player.is_playing(): + animation_player.play(ANIMATION_ALERT) + _is_animation_needed = false + + +func initialize(max_value: float, value: float) -> void: + _max_value = max_value + _current_value = value + _old_value = value + + +func set_current_value(value: float) -> void: + if value < _current_value: + _is_animation_needed = true + + _current_value = value + var time := (_old_value - _current_value)/OLD_VALUE_DECRETION_RATE + + if _old_value_tween and _old_value_tween.is_running(): + _old_value_tween.kill() + + _old_value_tween = create_tween() + _old_value_tween.tween_property(self, "_old_value", _current_value, abs(time)) diff --git a/game/hud/health_bar.gd.uid b/game/hud/health_bar.gd.uid new file mode 100644 index 0000000..8e60fea --- /dev/null +++ b/game/hud/health_bar.gd.uid @@ -0,0 +1 @@ +uid://nkpolcm0u4dw diff --git a/game/hud/health_bar.tscn b/game/hud/health_bar.tscn new file mode 100644 index 0000000..cbbc922 --- /dev/null +++ b/game/hud/health_bar.tscn @@ -0,0 +1,79 @@ +[gd_scene format=3 uid="uid://b1x0tmnf7hx7h"] + +[ext_resource type="Script" uid="uid://nkpolcm0u4dw" path="res://game/hud/health_bar.gd" id="1_2bnd8"] +[ext_resource type="Texture2D" uid="uid://bud580fp54qdi" path="res://images/hud/health_bar.png" id="2_hc5qd"] +[ext_resource type="Shader" uid="uid://cdo2tw14pkf6n" path="res://game/shaders/health_bar.gdshader" id="3_vrpsd"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_2bnd8"] +atlas = ExtResource("2_hc5qd") +region = Rect2(0, 16, 80, 16) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hc5qd"] +atlas = ExtResource("2_hc5qd") +region = Rect2(0, 0, 80, 16) + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_hc5qd"] +shader = ExtResource("3_vrpsd") +shader_parameter/max_value = 0.0 +shader_parameter/current_value = 0.0 +shader_parameter/old_value = 0.0 +shader_parameter/health_color = Color(0.36862746, 0.99215686, 0.5529412, 1) +shader_parameter/depletion_color = Color(0.32941177, 0.6509804, 0.41568628, 1) +shader_parameter/restoration_color = Color(1, 1, 1, 1) +shader_parameter/background_color = Color(0, 0, 0, 0) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vrpsd"] +atlas = ExtResource("2_hc5qd") +region = Rect2(2, 32, 76, 16) + +[sub_resource type="Animation" id="Animation_hc5qd"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("AlertSprite:visible") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [true] +} + +[sub_resource type="Animation" id="Animation_vrpsd"] +resource_name = "alert" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("AlertSprite:visible") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), +"update": 1, +"values": [true, false, true, false, true, false] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_5m3x1"] +_data = { +&"RESET": SubResource("Animation_hc5qd"), +&"alert": SubResource("Animation_vrpsd") +} + +[node name="HealthBar" type="Node2D" unique_id=256637218] +script = ExtResource("1_2bnd8") + +[node name="BackgroundSprite" type="Sprite2D" parent="." unique_id=92025763] +texture = SubResource("AtlasTexture_2bnd8") + +[node name="AlertSprite" type="Sprite2D" parent="." unique_id=434583744] +texture = SubResource("AtlasTexture_hc5qd") + +[node name="HealthSprite" type="Sprite2D" parent="." unique_id=1962337283] +material = SubResource("ShaderMaterial_hc5qd") +texture = SubResource("AtlasTexture_vrpsd") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1840862293] +libraries/ = SubResource("AnimationLibrary_5m3x1") diff --git a/game/levels/abstract_level.gd b/game/levels/abstract_level.gd index 0f6d214..8f92ee1 100644 --- a/game/levels/abstract_level.gd +++ b/game/levels/abstract_level.gd @@ -2,6 +2,9 @@ class_name AbstractLevel extends Node2D +const CORRUPTION_DAMAGE = 10.0 + + @export var initial_corruption_x := 0.0 @export var corruption_speed := 0.0 @@ -11,6 +14,9 @@ var _corruption_x: float @onready var player : Player = $Player @onready var pause_menu : PauseMenu = $%PauseMenu +@onready var health_bar : HealthBar = $%HealthBar + +@onready var corruption_timer : Timer = $CorruptionTimer @onready var level_material : ShaderMaterial = $%LevelColorRect.material @@ -18,21 +24,35 @@ var _corruption_x: float func _ready() -> void: _corruption_x = initial_corruption_x pause_menu.hide() + health_bar.initialize(player.MAX_HEALTH, player.MAX_HEALTH) + player.health_changed.connect(health_bar.set_current_value) #TODO Add gameplay music -func _process(delta: float) -> void: - _corruption_x += corruption_speed * delta - +func _process(_delta: float) -> void: var screen_to_world := player.camera.get_canvas_transform().affine_inverse() level_material.set_shader_parameter("screen_to_world", screen_to_world) level_material.set_shader_parameter("corruption_x", _corruption_x) +func _physics_process(delta: float) -> void: + _corruption_x += corruption_speed * delta + + if player.position.x < _corruption_x: + if corruption_timer.is_stopped(): + corruption_timer.start() + else: + corruption_timer.stop() + + func _input(event: InputEvent) -> void: if event.is_action_pressed("pause"): SoundManager.play_ui_stream(SoundManager.ui_stream_accept) get_tree().paused = true pause_menu.show() Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + + +func _on_corruption_timer_timeout() -> void: + player.deal_damage(CORRUPTION_DAMAGE) diff --git a/game/levels/abstract_level.tscn b/game/levels/abstract_level.tscn index b84eee8..2774415 100644 --- a/game/levels/abstract_level.tscn +++ b/game/levels/abstract_level.tscn @@ -5,6 +5,7 @@ [ext_resource type="PackedScene" uid="uid://0e3xvmn36j5b" path="res://game/menu/pause_menu.tscn" id="3_fnnmn"] [ext_resource type="Shader" uid="uid://brudtlwas6x66" path="res://game/shaders/glitch.gdshader" id="4_6b8cu"] [ext_resource type="Texture2D" uid="uid://b8rm1kxgci4l1" path="res://images/level/beams.png" id="4_pi5fd"] +[ext_resource type="PackedScene" uid="uid://b1x0tmnf7hx7h" path="res://game/hud/health_bar.tscn" id="6_x8k35"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_6b8cu"] resource_name = "Beams" @@ -1195,3 +1196,11 @@ offset_bottom = 360.0 [node name="PauseMenu" parent="CanvasLayer" unique_id=1815975678 instance=ExtResource("3_fnnmn")] unique_name_in_owner = true process_mode = 2 + +[node name="HealthBar" parent="CanvasLayer" unique_id=256637218 instance=ExtResource("6_x8k35")] +unique_name_in_owner = true +position = Vector2(51, 19) + +[node name="CorruptionTimer" type="Timer" parent="." unique_id=783383429] + +[connection signal="timeout" from="CorruptionTimer" to="." method="_on_corruption_timer_timeout"] diff --git a/game/shaders/health_bar.gdshader b/game/shaders/health_bar.gdshader new file mode 100644 index 0000000..be5475a --- /dev/null +++ b/game/shaders/health_bar.gdshader @@ -0,0 +1,37 @@ +shader_type canvas_item; + +uniform float max_value; +uniform float current_value; +uniform float old_value; + +uniform vec4 health_color : source_color = vec4(0.0, 0.65, 0.0, 1.0); +uniform vec4 depletion_color : source_color = vec4(0.0, 0.3, 0.0, 1.0); +uniform vec4 restoration_color : source_color = vec4(0.0, 1.0, 0.0, 1.0); +uniform vec4 background_color : source_color = vec4(0.0, 0.0, 0.0, 0.0); + +void fragment() { + float fill = current_value / max_value; + float old_fill = old_value / max_value; + + vec4 color; + + if (current_value <= old_value) { + if (UV.x < fill) { + color = health_color; + } else if (UV.x < old_fill) { + color = depletion_color; + } else { + color = background_color; + } + } else { + if (UV.x < old_fill) { + color = health_color; + } else if (UV.x < fill) { + color = restoration_color; + } else { + color = background_color; + } + } + + COLOR = vec4(color.rgb, color.a * COLOR.a); +} diff --git a/game/shaders/health_bar.gdshader.uid b/game/shaders/health_bar.gdshader.uid new file mode 100644 index 0000000..84f7d40 --- /dev/null +++ b/game/shaders/health_bar.gdshader.uid @@ -0,0 +1 @@ +uid://cdo2tw14pkf6n diff --git a/images/hud/health_bar.png b/images/hud/health_bar.png new file mode 100644 index 0000000..bd2b00f --- /dev/null +++ b/images/hud/health_bar.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24be0adcba0a8e2360c47f0db6234b5ff7c5a7032d3f54551f6d2a7e4551aad5 +size 329 diff --git a/images/hud/health_bar.png.import b/images/hud/health_bar.png.import new file mode 100644 index 0000000..9378377 --- /dev/null +++ b/images/hud/health_bar.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bud580fp54qdi" +path="res://.godot/imported/health_bar.png-dcd0e5acd727c0d7b2bc135b358ef446.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://images/hud/health_bar.png" +dest_files=["res://.godot/imported/health_bar.png-dcd0e5acd727c0d7b2bc135b358ef446.ctex"] + +[params] + +compress/mode=0 +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=false +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=1