## Resources Resources are essentially data containers. Much like nodes, they can hold functions, properties, and signals. Resources do nothing on their own. They must be a property of a node for their data to be accessed. The resoource only gets loaded into memory once. Any nodes referencing a node are accessing the SAME data. This behavior can be changed so that the resource is unique for each node referencing it. Lessons: * Create a script to use as a data object * Create a resource based on the script for the player, and the enemy * Learn to load resources Lessons: * Create a script to be the game controller, set it to autoload ## Relevance to UI A good UI is essentially a graphical representation of the state of the game. Once that state is centralized in one place it becomes easier to make UI that accurately reflects all the data in the game (such as player health, coins collected, points, timers etc.) Our goals for UI: * Create a Canvas Layer to float the UI over the viewport * Create a Control node to contain our UI nodes * Create Container nodes (vbox and hbox) * Use a MarginContainer node to wrap our UI nodes Exercise 1: Use a custom resource to track the player health. First create a script to use as the basis for the custom resource. ``` class_name CharacterStats extends Resource @export var max_health:int = 100 @export var starting_health:int = 100 @export var health:int = 100 @export var meleeDamage:int = 10 @export var rangeDamage:int = 8 ``` Second, create a custom resource that extends CharacterStats. Assign health and damage values as you wish. In `gamecontroller` load in the player resource at the `ready` function. `player = load("res://scripts/res/playerStats.tres")` This will be used for all the data related to the player. 1. In the `reset` function, return the player to full health. `playerHealth = player.max_health` 2. In the player damage function, have the player's health get removed based on the melee attack strength of the enemy. `playerHealth -= slime.meleeDamage` ### Update our Bad Guy We should update our slime so they don't run away from the player! ``` func _process(delta: float) -> void: if not right_down_cast.is_colliding(): direction = -1 sprite.flip_h = true if right_side_cast.is_colliding(): if not right_side_cast.get_collider() is Player: direction = -1 sprite.flip_h = true if not left_down_cast.is_colliding(): direction = 1 sprite.flip_h = false if left_side_cast.is_colliding(): if not left_side_cast.get_collider() is Player: direction = 1 sprite.flip_h = false position.x += direction * speed * delta ``` ### Damage and Death Animations Time to update the character so they have a little life when they die. As a reminder, the `GameController` emits two custom signals - one for player damage, and another for player death. The `SceneManager` has wired up those signals to make their way to the player. Lets update the player controller to add on hurt and death animations. First we should add two additional states to our state machine. `enum State{IDLE, RUN, JUMP, FALLING, MELEE, HURT, DEATH}` Now we can update the handlers for the signals coming from the game controller. ``` func _player_damage(): print("Player taking damage!") current_state = State.HURT ``` ``` func _player_death(): print("Player dead!!") current_state = State.DEATH ``` Then we can update the state animations to play the hurt or death animations. In the `update_animations` function: ``` State.HURT: player_graphic.play("hurt") State.DEATH: player_graphic.play("death") ``` Lastly we can update our listener function that knows when animations have completed. In `_on_animation_finished`: ``` State.HURT: current_state = State.IDLE State.DEATH: deathComplete.emit() ``` The only other thing to do in the player code is to ensure that the player can not keep moving around while they are stunned or dying. This is pretty straightforward. We can do it by simply ignoring user input while they are being damaged. In `_physics_process`: ``` if current_state != State.HURT and current_state != State.DEATH: handle_input() ```