From 208d93f5b4a9c710504c8e740f0fc0737ee4c9cd Mon Sep 17 00:00:00 2001 From: OddlyTimbot Date: Mon, 10 Nov 2025 21:17:58 -0500 Subject: [PATCH] game saving like for real, bad guy movement, player damage, player death, ui with controls labels --- .../player/death/Player Death 64x64.png | Bin 0 -> 1584 bytes .../death/Player Death 64x64.png.import | 40 ++++++ .../player/hurt/Player Hurt 48x48.png | Bin 0 -> 1448 bytes .../player/hurt/Player Hurt 48x48.png.import | 40 ++++++ project.godot | 10 ++ scenes/game.tscn | 39 +++--- scenes/player.tscn | 116 +++++++++++++++++- scenes/slime.tscn | 2 +- scenes/ui.tscn | 47 +++++++ scripts/gamecontroller.gd | 51 +++++++- scripts/player.gd | 21 +++- scripts/rscs/level0_gameSaveStats.tres | 31 +++++ scripts/rscs/save_object.gd | 3 + scripts/rscs/save_object.gd.uid | 1 + scripts/rscs/slimeStats.tres | 2 +- scripts/scene_manager.gd | 102 +++++++++++++++ scripts/slime.gd | 18 +++ scripts/ui.gd | 23 ++++ scripts/ui.gd.uid | 1 + 19 files changed, 521 insertions(+), 26 deletions(-) create mode 100644 assets/graphics/player/death/Player Death 64x64.png create mode 100644 assets/graphics/player/death/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 create mode 100644 scenes/ui.tscn create mode 100644 scripts/rscs/level0_gameSaveStats.tres create mode 100644 scripts/rscs/save_object.gd create mode 100644 scripts/rscs/save_object.gd.uid create mode 100644 scripts/ui.gd create mode 100644 scripts/ui.gd.uid diff --git a/assets/graphics/player/death/Player Death 64x64.png b/assets/graphics/player/death/Player Death 64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..2e6a1ae0514b237d754893f563c87ba5f95dc109 GIT binary patch literal 1584 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: timeAvailable -=1 + countDownSignal.emit(timeAvailable) + if timeAvailable <=0: print("YOu LOOSAE Baby!") print(levels[currentLevel]) @@ -48,7 +57,12 @@ func reset()->void: # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): - pass + if Input.is_action_just_pressed("save"): + print("save the game") + saveGame() + if Input.is_action_just_pressed("load"): + print("load the game") + loadGame() func _on_trigger_fired(intent, body): @@ -64,6 +78,7 @@ func _on_coin_collected(body, coin): destroySignal.emit(coin) func totalCoins(value): + coinUpdateSignal.emit(value) if value == 0: #you won currentLevel +=1 @@ -82,11 +97,14 @@ func _on_slime_damage(_body, slime): else: print("Taking damage") playerDamagedSignal.emit(playerCurrentHealth, player.starting_health) + +func deathComplete()->void: + levelChangeSignal.emit(levels[currentLevel]) func totalEnemies(value): print("GC knows total enemies "+str(value)) -func addEnemyToLevel(slime)->void: +func addEnemyToLevel(slime, _stat=null)->void: print("GC adding enemy") var randDamage:int = randi() % 10 @@ -94,6 +112,11 @@ func addEnemyToLevel(slime)->void: "health": enemy.health, "damage": enemy.meleeDamage+randDamage } + if _stat: + enemyStat = { + "health":_stat.health, + "damage":_stat.damage + } enemiesDict[slime]= enemyStat func bulletDamage(area,bullet)->void: @@ -110,3 +133,25 @@ func bulletDamage(area,bullet)->void: print("enemy damaged") func removeEnemyFromLevel(slime)->void: enemiesDict.erase(slime) + +func saveGame()->void: + gameSaveSignal.emit(currentLevel, timeAvailable, playerCurrentHealth, enemiesDict) + +func loadGame()->void: + if ResourceLoader.exists("res://scripts/rscs/level"+str(currentLevel)+"_gameSaveStats.tres"): + var saved_game:SaveObject = load("res://scripts/rscs/level"+str(currentLevel)+"_gameSaveStats.tres") + enemiesDict.clear() + #get from loaded game save + stashData = saved_game.gameSave + print("loading the game") + gameLoadSignal.emit(stashData) + +func stashGame(stash)->void: + print("stashing game") + stashData = stash + var stashObject = SaveObject.new() + stashObject.gameSave=stashData + ResourceSaver.save(stashObject, "res://scripts/rscs/level"+str(currentLevel)+"_gameSaveStats.tres") + +func playerHealth(value)->void: + playerCurrentHealth = value diff --git a/scripts/player.gd b/scripts/player.gd index 8a1128c..f651502 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -7,7 +7,7 @@ var direction:float = 0 @export var BUMP_POWER = 100 enum FaceDirection{LEFT, RIGHT} var facing:FaceDirection = FaceDirection.RIGHT -enum State{IDLE,JUMP,FALLING,RUNNING,SHOVE} +enum State{IDLE,JUMP,FALLING,RUNNING,SHOVE,HURT,DEATH} var current_state:State = State.IDLE @onready var right_cast = $RightCast @@ -24,16 +24,24 @@ var upJump:bool = false @export var hard_gravity:float =1.5 @onready var player_graphic = $PlayerGraphic +signal playerDeathCompleteSignal + func _physics_process(delta): # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. - handle_input() + if current_state != State.HURT: + handle_input() handle_movement(delta) update_states() update_animation() move_and_slide() handle_collisions() +func handleDamage(_health,_maxHealth)->void: + current_state = State.HURT +func handleDeath()->void: + current_state = State.DEATH + func update_states()->void: #Create a state machine match current_state: @@ -49,6 +57,7 @@ func update_states()->void: current_state = State.RUNNING State.RUNNING when velocity.x ==0: current_state = State.IDLE + func update_animation()->void: match current_state: @@ -63,6 +72,10 @@ func update_animation()->void: player_graphic.play("run") State.SHOVE: player_graphic.play("shove") + State.HURT: + player_graphic.play("hurt") + State.DEATH: + player_graphic.play("death") func handle_input()->void: # Handle jump. @@ -152,3 +165,7 @@ func _on_animation_finished(): upJump=false State.SHOVE: current_state = State.IDLE + State.HURT: + current_state = State.IDLE + State.DEATH: + playerDeathCompleteSignal.emit() diff --git a/scripts/rscs/level0_gameSaveStats.tres b/scripts/rscs/level0_gameSaveStats.tres new file mode 100644 index 0000000..5a33105 --- /dev/null +++ b/scripts/rscs/level0_gameSaveStats.tres @@ -0,0 +1,31 @@ +[gd_resource type="Resource" script_class="SaveObject" load_steps=2 format=3 uid="uid://cl4l7yk43rbao"] + +[ext_resource type="Script" uid="uid://ddp6q3gi13sw1" path="res://scripts/rscs/save_object.gd" id="1_a13pi"] + +[resource] +script = ExtResource("1_a13pi") +gameSave = { +"coinsData": [Transform2D(1, 0, 0, 1.0000001, 435.00952, 343.5218), Transform2D(1, 0, 0, 1.0000001, 192.44662, 340.51724)], +"cratesData": [Transform2D(0.00016496716, -1.0015213, 1.0023261, 0.00016483471, 230.14476, 534.2836), Transform2D(-1.0023261, -6.868146e-05, 6.873665e-05, -1.0015213, 486.98053, 534.2846), Transform2D(1.0023259, -0.0007010149, 0.0007015782, 1.0015211, 353.81284, 342.28403)], +"enemiesData": [{ +"damage": 7, +"health": 50, +"position": Transform2D(1, 0, 0, 1.0000001, 736.34125, 339.51575) +}, { +"damage": 8, +"health": 50, +"position": Transform2D(1, 0, 0, 1.0000001, 535.8805, 340.51724) +}, { +"damage": 8, +"health": 50, +"position": Transform2D(1, 0, 0, 1.0000001, 483.75943, 339.51572) +}, { +"damage": 10, +"health": 50, +"position": Transform2D(1, 0, 0, 1.0000001, 437.652, 339.51575) +}], +"playerData": { +"health": 67.0, +"position": Transform2D(1, 0, 0, 1.0000197, 397.92346, 341.94016) +} +} diff --git a/scripts/rscs/save_object.gd b/scripts/rscs/save_object.gd new file mode 100644 index 0000000..69b5072 --- /dev/null +++ b/scripts/rscs/save_object.gd @@ -0,0 +1,3 @@ +class_name SaveObject extends Resource + +@export var gameSave:Dictionary = {} diff --git a/scripts/rscs/save_object.gd.uid b/scripts/rscs/save_object.gd.uid new file mode 100644 index 0000000..e51701c --- /dev/null +++ b/scripts/rscs/save_object.gd.uid @@ -0,0 +1 @@ +uid://ddp6q3gi13sw1 diff --git a/scripts/rscs/slimeStats.tres b/scripts/rscs/slimeStats.tres index 4dddcbe..f99bb0f 100644 --- a/scripts/rscs/slimeStats.tres +++ b/scripts/rscs/slimeStats.tres @@ -7,5 +7,5 @@ script = ExtResource("1_ppipo") health = 50 max_health = 50 starting_health = 50 -meleeDamage = 5 +meleeDamage = 25 metadata/_custom_type_script = "uid://oewo8kn4jbkl" diff --git a/scripts/scene_manager.gd b/scripts/scene_manager.gd index 9116c6d..cb64a61 100644 --- a/scripts/scene_manager.gd +++ b/scripts/scene_manager.gd @@ -2,8 +2,15 @@ class_name SceneManager extends Node2D var bullet = preload("res://scenes/bullet.tscn") var grenade = preload("res://scenes/grenade.tscn") +var coinScene = preload("res://scenes/coin.tscn") +var crateScene = preload("res://scenes/crate.tscn") +var enemyScene = preload("res://scenes/slime.tscn") + @onready var coins = $"../Coins" @onready var enemies = $"../Enemies" +@onready var player = $"../Player" +@onready var crates = $"../Crates" +@onready var ui = $"../CanvasLayer/ui" var bulletArray = [] @@ -26,6 +33,15 @@ func buildLevel()->void: #Wire up signals from Gamecontroller Gamecontroller.levelChangeSignal.connect(changeScene) Gamecontroller.destroySignal.connect(destroy) + Gamecontroller.gameSaveSignal.connect(saveGameByLevel) + Gamecontroller.gameLoadSignal.connect(loadGameByLevel) + Gamecontroller.playerDamagedSignal.connect(player.handleDamage) + Gamecontroller.playerDamagedSignal.connect(ui.healthUpdate) + Gamecontroller.playerDeathSignal.connect(player.handleDeath) + Gamecontroller.countDownSignal.connect(ui.timerUpdate) + Gamecontroller.coinUpdateSignal.connect(ui.coinUpdate) + + player.playerDeathCompleteSignal.connect(Gamecontroller.deathComplete) func updateEnemies()->void: var totalEnemies = 0 @@ -88,3 +104,89 @@ func changeScene(level)->void: func destroy(body)->void: body.queue_free() + +func loadGameByLevel(stash)->void: + print("load the game") + #get the player data from the stash + var playerData = stash.playerData + var coinsData = stash.coinsData + var cratesData = stash.cratesData + var enemiesData = stash.enemiesData + #get rid of any existing coins + if coins: + for coin in coins.get_children(): + #remove the listeners + coin.tree_exited.disconnect(updateCoins) + coins.remove_child(coin) + coin.queue_free() + for trans in coinsData: + var coinObj:Coin = coinScene.instantiate() + coins.add_child(coinObj) + coinObj.transform = trans + updateCoins() + #crates + if crates: + for crate in crates.get_children(): + crates.remove_child(crate) + crate.queue_free() + for crate in cratesData: + var crateObj:RigidBody2D = crateScene.instantiate() + crates.add_child(crateObj) + crateObj.transform = crate + #enemies + if enemies: + for enemy in enemies.get_children(): + enemies.remove_child(enemy) + enemy.queue_free() + for enemy in enemiesData: + var enemyObj:Slime = enemyScene.instantiate() + enemies.add_child(enemyObj) + enemyObj.transform = enemy.position + Gamecontroller.addEnemyToLevel(enemyObj, enemy) + player.transform = playerData.position + #tell the game controller the players health + Gamecontroller.playerHealth(playerData.health) + + + +func saveGameByLevel(currentLevel:int, timeAvailable:int, playerCurrentHealth:float, enemiesDict:Dictionary)->void: + print("Saving Game") + print(playerCurrentHealth) + print(player.transform) + var playerData={ + "health":playerCurrentHealth, + "position":player.transform + } + # coin save info + var coinDataArray:Array + if coins: + for coin in coins.get_children(): + print(coin.transform) + coinDataArray.push_front(coin.transform) + var crateDataArray:Array + if crates: + for crate in crates.get_children(): + crateDataArray.push_front(crate.transform) + + #enemies + var enemyDataArray:Array + for enemyKey in enemiesDict: + print("ENEMY FOUND") + print(enemyKey.transform) + print(enemiesDict[enemyKey].health) + print(enemiesDict[enemyKey].damage) + var enemy={ + "position":enemyKey.transform, + "health":enemiesDict[enemyKey].health, + "damage":enemiesDict[enemyKey].damage + } + enemyDataArray.push_front(enemy) + + var stash={ + "playerData":playerData, + "coinsData": coinDataArray, + "cratesData": crateDataArray, + "enemiesData":enemyDataArray + } + + Gamecontroller.stashGame(stash) diff --git a/scripts/slime.gd b/scripts/slime.gd index 0350297..597fcdf 100644 --- a/scripts/slime.gd +++ b/scripts/slime.gd @@ -3,6 +3,7 @@ class_name Slime extends Area2D @onready var left_cast = $LeftCast @onready var right_down_cast = $RightDownCast @onready var left_down_cast = $LeftDownCast +@onready var slime_graphic = $SlimeGraphic var speed:int = 100 var direction = 1 @@ -15,6 +16,23 @@ func _ready(): # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): + #raycast detection + if not right_down_cast.is_colliding(): + direction = -1 + slime_graphic.flip_h = true + + if not left_down_cast.is_colliding(): + direction = 1 + slime_graphic.flip_h = false + + if right_cast.is_colliding() && not right_cast.get_collider() is Player: + direction = -1 + slime_graphic.flip_h = true + + if left_cast.is_colliding() && not left_cast.get_collider() is Player: + direction = 1 + slime_graphic.flip_h = false + position.x += direction * speed * delta diff --git a/scripts/ui.gd b/scripts/ui.gd new file mode 100644 index 0000000..e5ff7c0 --- /dev/null +++ b/scripts/ui.gd @@ -0,0 +1,23 @@ +class_name UI extends Control +@onready var health = $VBoxContainer/HBoxContainer/MarginContainer/health +@onready var timer = $VBoxContainer/HBoxContainer/MarginContainer2/timer +@onready var coins = $VBoxContainer/HBoxContainer/MarginContainer3/coins + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + +func healthUpdate(currentHealth, maxHealth): + health.text = "Health: "+str(currentHealth) + +func timerUpdate(timeRemaining): + timer.text=str(timeRemaining) + +func coinUpdate(coinsRemaining): + coins.text = "Coins: "+str(coinsRemaining) diff --git a/scripts/ui.gd.uid b/scripts/ui.gd.uid new file mode 100644 index 0000000..a21afb4 --- /dev/null +++ b/scripts/ui.gd.uid @@ -0,0 +1 @@ +uid://8ysbh26qa33o