From 1a4f030097dc1191ef62fc93cb745d95432ad261 Mon Sep 17 00:00:00 2001 From: OddlyTimbot Date: Mon, 1 Jun 2026 21:06:00 -0400 Subject: [PATCH] damage, dying, custom resources, simple UI --- Scenes/game.tscn | 5 + Scenes/player.tscn | 112 +++++++++++++++++- Scenes/ui.tscn | 47 ++++++++ Scripts/gameController.gd | 27 +++-- Scripts/player.gd | 23 +++- Scripts/rscs/character_statst.gd | 8 ++ Scripts/rscs/character_statst.gd.uid | 1 + Scripts/rscs/player_stats.tres | 8 ++ Scripts/rscs/slime_stats.tres | 11 ++ Scripts/scene_manager.gd | 8 ++ Scripts/ui.gd | 18 +++ Scripts/ui.gd.uid | 1 + .../player/dead/Player Death 64x64.png | Bin 0 -> 1584 bytes .../player/dead/Player Death 64x64.png.import | 40 +++++++ .../player/hurt/Player Hurt 48x48.png | Bin 0 -> 1448 bytes .../player/hurt/Player Hurt 48x48.png.import | 40 +++++++ 16 files changed, 336 insertions(+), 13 deletions(-) create mode 100644 Scenes/ui.tscn create mode 100644 Scripts/rscs/character_statst.gd create mode 100644 Scripts/rscs/character_statst.gd.uid create mode 100644 Scripts/rscs/player_stats.tres create mode 100644 Scripts/rscs/slime_stats.tres create mode 100644 Scripts/ui.gd create mode 100644 Scripts/ui.gd.uid create mode 100644 assets/graphics/player/dead/Player Death 64x64.png create mode 100644 assets/graphics/player/dead/Player Death 64x64.png.import create mode 100644 assets/graphics/player/hurt/Player Hurt 48x48.png create mode 100644 assets/graphics/player/hurt/Player Hurt 48x48.png.import diff --git a/Scenes/game.tscn b/Scenes/game.tscn index bd46077..abc4c5f 100644 --- a/Scenes/game.tscn +++ b/Scenes/game.tscn @@ -7,6 +7,7 @@ [ext_resource type="Script" uid="uid://4hekg0d8n04f" path="res://Scripts/trigger.gd" id="3_wrm1d"] [ext_resource type="PackedScene" uid="uid://dotmw0uwnqv6w" path="res://Scenes/grenade.tscn" id="6_i6g32"] [ext_resource type="PackedScene" uid="uid://rn1d2w3p88sr" path="res://Scenes/slime.tscn" id="7_1l0tm"] +[ext_resource type="PackedScene" uid="uid://dwl0ci4ejpmu2" path="res://Scenes/ui.tscn" id="8_v158k"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_v158k"] texture = ExtResource("3_1l0tm") @@ -438,5 +439,9 @@ position = Vector2(636, 375) [node name="Slime2" parent="Enemies" unique_id=689767526 instance=ExtResource("7_1l0tm")] position = Vector2(546, 406) +[node name="UI" type="CanvasLayer" parent="." unique_id=1274660153] + +[node name="Control" parent="UI" unique_id=1504539709 instance=ExtResource("8_v158k")] + [connection signal="body_entered" from="Area2D" to="Area2D" method="_on_body_entered"] [connection signal="triggerActiveSignal" from="Area2D" to="." method="_on_triggerSignal"] diff --git a/Scenes/player.tscn b/Scenes/player.tscn index 27dbeb9..393e3e1 100644 --- a/Scenes/player.tscn +++ b/Scenes/player.tscn @@ -3,14 +3,72 @@ [ext_resource type="Script" uid="uid://btmoyp0rwqmxe" path="res://Scripts/player.gd" id="1_v0iea"] [ext_resource type="Texture2D" uid="uid://dlax6d4nhkl4p" path="res://assets/graphics/player/idle/Player Idle 48x48.png" id="2_cvnsp"] [ext_resource type="Texture2D" uid="uid://cnpsd8q2hqrmp" path="res://assets/graphics/player/jump_fall/player jump 48x48.png" id="2_vgqql"] +[ext_resource type="Texture2D" uid="uid://bm2nvpkg1tihf" path="res://assets/graphics/player/dead/Player Death 64x64.png" id="2_x3wgy"] [ext_resource type="Texture2D" uid="uid://bc251ptp8ujsv" path="res://assets/graphics/player/run/player run 48x48.png" id="3_6t5aa"] +[ext_resource type="Texture2D" uid="uid://dcd86jsmvvi4m" path="res://assets/graphics/player/hurt/Player Hurt 48x48.png" id="3_fkybt"] [sub_resource type="CircleShape2D" id="CircleShape2D_2poj3"] +[sub_resource type="AtlasTexture" id="AtlasTexture_y6uwr"] +atlas = ExtResource("2_x3wgy") +region = Rect2(0, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_83xs0"] +atlas = ExtResource("2_x3wgy") +region = Rect2(48, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ypfnu"] +atlas = ExtResource("2_x3wgy") +region = Rect2(96, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xuaoq"] +atlas = ExtResource("2_x3wgy") +region = Rect2(144, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wohnl"] +atlas = ExtResource("2_x3wgy") +region = Rect2(192, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_36dho"] +atlas = ExtResource("2_x3wgy") +region = Rect2(240, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yh6i2"] +atlas = ExtResource("2_x3wgy") +region = Rect2(288, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nv4vc"] +atlas = ExtResource("2_x3wgy") +region = Rect2(336, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8gxox"] +atlas = ExtResource("2_x3wgy") +region = Rect2(384, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_11gg7"] +atlas = ExtResource("2_x3wgy") +region = Rect2(432, 0, 48, 48) + [sub_resource type="AtlasTexture" id="AtlasTexture_olqyp"] atlas = ExtResource("2_vgqql") region = Rect2(96, 0, 48, 48) +[sub_resource type="AtlasTexture" id="AtlasTexture_2ijsl"] +atlas = ExtResource("3_fkybt") +region = Rect2(0, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_p1odr"] +atlas = ExtResource("3_fkybt") +region = Rect2(48, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0gysh"] +atlas = ExtResource("3_fkybt") +region = Rect2(96, 0, 48, 48) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vfarp"] +atlas = ExtResource("3_fkybt") +region = Rect2(144, 0, 48, 48) + [sub_resource type="AtlasTexture" id="AtlasTexture_6t5aa"] atlas = ExtResource("2_cvnsp") region = Rect2(0, 0, 48, 48) @@ -95,6 +153,41 @@ region = Rect2(336, 0, 48, 48) animations = [{ "frames": [{ "duration": 1.0, +"texture": SubResource("AtlasTexture_y6uwr") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_83xs0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ypfnu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xuaoq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wohnl") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_36dho") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_yh6i2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nv4vc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8gxox") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_11gg7") +}], +"loop": false, +"name": &"dead", +"speed": 9.0 +}, { +"frames": [{ +"duration": 1.0, "texture": SubResource("AtlasTexture_olqyp") }], "loop": false, @@ -103,6 +196,23 @@ animations = [{ }, { "frames": [{ "duration": 1.0, +"texture": SubResource("AtlasTexture_2ijsl") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_p1odr") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0gysh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vfarp") +}], +"loop": false, +"name": &"hurt", +"speed": 12.0 +}, { +"frames": [{ +"duration": 1.0, "texture": SubResource("AtlasTexture_6t5aa") }, { "duration": 1.0, @@ -201,7 +311,7 @@ position = Vector2(-14, -8) texture_filter = 1 position = Vector2(0, -6) sprite_frames = SubResource("SpriteFrames_ukyrk") -animation = &"idle" +animation = &"dead" autoplay = "idle" [node name="Camera2D" type="Camera2D" parent="." unique_id=522635127] diff --git a/Scenes/ui.tscn b/Scenes/ui.tscn new file mode 100644 index 0000000..d41e84d --- /dev/null +++ b/Scenes/ui.tscn @@ -0,0 +1,47 @@ +[gd_scene format=3 uid="uid://dwl0ci4ejpmu2"] + +[ext_resource type="Script" uid="uid://c6hdmgpywednh" path="res://Scripts/ui.gd" id="1_jxero"] + +[node name="Control" type="Control" unique_id=1504539709] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_jxero") + +[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1721165743] +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +grow_horizontal = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=71460237] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer" unique_id=1544251315] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="health" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer" unique_id=623986244] +layout_mode = 2 +text = "HEALTH" + +[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer/HBoxContainer" unique_id=2101616423] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="time" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer2" unique_id=596670188] +layout_mode = 2 +text = "TIME" +horizontal_alignment = 1 + +[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer/HBoxContainer" unique_id=173654446] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="crates" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer3" unique_id=1150656356] +layout_mode = 2 +text = "CRATES" +horizontal_alignment = 2 diff --git a/Scripts/gameController.gd b/Scripts/gameController.gd index 3d4564a..5b14b7f 100644 --- a/Scripts/gameController.gd +++ b/Scripts/gameController.gd @@ -2,6 +2,9 @@ extends Node2D signal destroySignal(body) signal levelChangeSignal(level) +signal damagePlayerSignal(damageTotal,playerHealthTotal) +signal killPlayerSignal +signal timeChangeSignal(currentTimeAvailable) var levels=["res://Scenes/game.tscn","res://Scenes/level2.tscn","res://Scenes/level3.tscn"] var currentLevel = 0 @@ -15,9 +18,15 @@ var playerCurrentHealth:int = 100 #enemies var enemiesDict = {} +var enemy:CharacterStats +var player:CharacterStats + # Called when the node enters the scene tree for the first time. func _ready() -> void: get_window().grab_focus() + enemy = load("res://Scripts/rscs/slime_stats.tres") + player = load("res://Scripts/rscs/player_stats.tres") + reset() add_child(timer) timer.wait_time = 1 @@ -26,15 +35,15 @@ func _ready() -> void: timer.start() func secondCounter()->void: - print("tick") timeAvailable -=1 + timeChangeSignal.emit(timeAvailable) if timeAvailable <=0: print("You lose baby!") levelChangeSignal.emit(levels[currentLevel]) func reset()->void: timeAvailable = timers[currentLevel] - playerCurrentHealth = 100 + playerCurrentHealth = player.starting_health # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: @@ -42,12 +51,10 @@ func _process(delta: float) -> void: func _on_triggerSignal(body: Variant, intentMessage: Variant) -> void: - print("GC knows about a trigger!") if body.is_in_group("destructible") and intentMessage == "destroy": destroySignal.emit(body) func totalCrates(numberOfCrates:int)->void: - print("GC knows total crates: "+str(numberOfCrates)) if numberOfCrates <=0: print("You WON!!!!") currentLevel +=1 @@ -56,18 +63,20 @@ func totalCrates(numberOfCrates:int)->void: levelChangeSignal.emit(levels[currentLevel]) func _on_slime_damage(_body,slime)->void: - print("GC knows slime doing damage") - print("Doing damage: "+str(enemiesDict[slime]["damage"])) playerCurrentHealth -= enemiesDict[slime]["damage"] + #send a custom signal about damage + damagePlayerSignal.emit(enemiesDict[slime]["damage"],playerCurrentHealth) if playerCurrentHealth <=0: print("U ded") - levelChangeSignal.emit(levels[currentLevel]) + killPlayerSignal.emit() func addEnemyToLevel(slime)->void: #give each enemy some health and damage var randamage = randi_range(1,20) var enemyStat = { - "health": 50, - "damage": 30+randamage + "health": enemy.starting_health, + "damage": enemy.meleeDamage+randamage } enemiesDict[slime]=enemyStat +func respawn()->void: + levelChangeSignal.emit(levels[currentLevel]) diff --git a/Scripts/player.gd b/Scripts/player.gd index ec23a8c..b923d8b 100644 --- a/Scripts/player.gd +++ b/Scripts/player.gd @@ -5,13 +5,14 @@ class_name Player extends CharacterBody2D @onready var left_spawn: Marker2D = $LeftSpawn @onready var player_graphic: AnimatedSprite2D = $PlayerGraphic +signal deathAnimationComplete const SPEED = 300.0 const JUMP_VELOCITY = -400.0 var direction enum FaceDirection{LEFT,RIGHT} var facing:FaceDirection = FaceDirection.RIGHT -enum PlayerState{IDLE,RUNNING,JUMPING,FALLING} +enum PlayerState{IDLE,RUNNING,JUMPING,FALLING,HURT, DEAD} var current_player_state:PlayerState = PlayerState.IDLE var pushTarget:RigidBody2D @@ -20,8 +21,9 @@ var pushEnabled:bool = false var upJump:bool = false func _physics_process(delta: float) -> void: - handle_input() - handle_movement(delta) + if not current_player_state == PlayerState.HURT: + handle_input() + handle_movement(delta) handle_state() handle_animation() move_and_slide() @@ -118,6 +120,10 @@ func handle_animation()->void: player_graphic.play("jump") PlayerState.FALLING: player_graphic.play("fall") + PlayerState.HURT: + player_graphic.play("hurt") + PlayerState.DEAD: + player_graphic.play("dead") @@ -125,3 +131,14 @@ func _on_animation_finished() -> void: match current_player_state: PlayerState.JUMPING: upJump = false + PlayerState.HURT: + current_player_state = PlayerState.IDLE + PlayerState.DEAD: + deathAnimationComplete.emit() + +func takeDamage(_damage,_remainingHealth)->void: + print("Player knows damage "+str(_damage)) + current_player_state = PlayerState.HURT + +func die()->void: + current_player_state = PlayerState.DEAD diff --git a/Scripts/rscs/character_statst.gd b/Scripts/rscs/character_statst.gd new file mode 100644 index 0000000..1d95cd7 --- /dev/null +++ b/Scripts/rscs/character_statst.gd @@ -0,0 +1,8 @@ +class_name CharacterStats extends Resource + +@export var health:int = 100 +@export var max_health:int = 120 +@export var starting_health:int = 100 + +@export var meleeDamage:int = 10 +@export var rangeDamage:int =5 diff --git a/Scripts/rscs/character_statst.gd.uid b/Scripts/rscs/character_statst.gd.uid new file mode 100644 index 0000000..192eb85 --- /dev/null +++ b/Scripts/rscs/character_statst.gd.uid @@ -0,0 +1 @@ +uid://deef1fi6sargw diff --git a/Scripts/rscs/player_stats.tres b/Scripts/rscs/player_stats.tres new file mode 100644 index 0000000..ff991aa --- /dev/null +++ b/Scripts/rscs/player_stats.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="CharacterStats" format=3 uid="uid://cqbm43tl86gmq"] + +[ext_resource type="Script" uid="uid://deef1fi6sargw" path="res://Scripts/rscs/character_statst.gd" id="1_2wf5j"] + +[resource] +script = ExtResource("1_2wf5j") +health = 110 +metadata/_custom_type_script = "uid://deef1fi6sargw" diff --git a/Scripts/rscs/slime_stats.tres b/Scripts/rscs/slime_stats.tres new file mode 100644 index 0000000..ce63aeb --- /dev/null +++ b/Scripts/rscs/slime_stats.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="CharacterStats" format=3 uid="uid://dbhv3hltbjxk2"] + +[ext_resource type="Script" uid="uid://deef1fi6sargw" path="res://Scripts/rscs/character_statst.gd" id="1_38j4j"] + +[resource] +script = ExtResource("1_38j4j") +health = 40 +max_health = 45 +starting_health = 40 +rangeDamage = 0 +metadata/_custom_type_script = "uid://deef1fi6sargw" diff --git a/Scripts/scene_manager.gd b/Scripts/scene_manager.gd index b7373c5..8d2613c 100644 --- a/Scripts/scene_manager.gd +++ b/Scripts/scene_manager.gd @@ -2,12 +2,20 @@ class_name SceneManager extends Node2D @onready var crates: Node2D = $"../Crates" var grenade = preload("res://Scenes/grenade.tscn") @onready var enemies: Node2D = $"../Enemies" +@onready var player: Player = $"../Player" +@onready var control: Control = $"../UI/Control" func _ready() -> void: GameController.reset() print("Scene manager is ready!") GameController.destroySignal.connect(destroy) GameController.levelChangeSignal.connect(changeScene) + GameController.damagePlayerSignal.connect(player.takeDamage) + GameController.damagePlayerSignal.connect(control.upDateHealth) + GameController.timeChangeSignal.connect(control.updateTime) + + GameController.killPlayerSignal.connect(player.die) + player.deathAnimationComplete.connect(GameController.respawn) buildLevel() func destroy(body)->void: diff --git a/Scripts/ui.gd b/Scripts/ui.gd new file mode 100644 index 0000000..69910eb --- /dev/null +++ b/Scripts/ui.gd @@ -0,0 +1,18 @@ +extends Control + +@onready var health: Label = $VBoxContainer/HBoxContainer/MarginContainer/health +@onready var time: Label = $VBoxContainer/HBoxContainer/MarginContainer2/time + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass + +func upDateHealth(_damage,_health)->void: + health.text = "HEALTH: "+str(_health) +func updateTime(timeAvailable)->void: + time.text = str(timeAvailable) diff --git a/Scripts/ui.gd.uid b/Scripts/ui.gd.uid new file mode 100644 index 0000000..257fd04 --- /dev/null +++ b/Scripts/ui.gd.uid @@ -0,0 +1 @@ +uid://c6hdmgpywednh diff --git a/assets/graphics/player/dead/Player Death 64x64.png b/assets/graphics/player/dead/Player Death 64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..2e6a1ae0514b237d754893f563c87ba5f95dc109 GIT binary patch literal 1584 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