Question
Hello, I am currently follwong the 2D action adventure rpg tutorial series from Micheal Games. For my healtbars i tried going a dofferent direction and im having problems implementing them right.
My Canvas layer has a script that is set to a global Script.
The healtbar has a script that controls both Progressbars and the code itself works.
I have tested it by just adding ist directly to my Play scene.
But now that i have my setup as its own scene with a global autoload, im getting either errors, or the bars are not updating.
My Player has an hp and max_hp value that im tring to pass as a signal (with emit) in his “hp_changed func”.
I seem to be referenzing thoses variables wrong.
My question in general ist just,
How to i get variables from a different scene (in this example “Player.hp and Player.max_hp”) to my autoload global “PlayerHud”
Is it right that i autoload the script which is on my CanvasLayer to initialize the healthbar? This is returning null on many variables because the player didnt load in yes.
Sorry if im not provising rhe code or if i forgot anything. Im writing this on my phone rn.
Also i am very new to godot and game making in general.
Without code is hard to tell where the problems are.
Did you add the script (.gd file) or the scene (.tscn file) as the autoload? If you did the former then try removing it and adding the .tscn file as the autoload. This may fix the errors.
To access your player you could pass it to the autoload like:
I tried it both ways. Currently my game only runs if i autoload the script that is attached to my HUD scene. if i add my scene to autoload i geht this errorcform my Player script:
Invalid access to property or key ‘health_bar’ on a base object of type ‘Nil’.
Player script:
class_name Player extends CharacterBody2D
var cardinal_direction : Vector2 = Vector2.DOWN
var direction : Vector2 = Vector2.ZERO
const DIR_4 = [ Vector2.RIGHT, Vector2.DOWN, Vector2.LEFT, Vector2.UP ]
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var effect_animation_player: AnimationPlayer = $EffectAnimationPlayer
@onready var hit_box: HitBox = $HitBox
@onready var sprite: Sprite2D = $Sprite2D
@onready var state_machine: PlayerStateMachine = $StateMachine
@onready var damage_bar: ProgressBar = $HealthBar/DamageBar
#@onready var health_bar: ProgressBar = $HealthBar
var health_bar : HealthBar = null
signal DirectionChanged( new_direction: Vector2 )
signal player_damaged( hurt_box : HurtBox)
signal healthchanged
var invulnerable : bool = false
@export var hp : int = 3
@export var max_hp : int = 20
# Called when the node enters the scene tree for the first time.
func _ready():
PlayerManager.player = self
PlayerManager.player_ready.emit(self)
state_machine.initialize(self)
hit_box.damaged.connect(_take_damage)
await get_tree().create_timer(0.05).timeout # Kurze Wartezeit für HUD
var path = "/root/PlayerHud/Control/HealthBar"
while PlayerHud.health_bar == null:
await get_tree().process_frame # eine Frame warten
health_bar = PlayerHud.health_bar
health_bar.init_health(hp)
update_hp(0)
pass
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
#direction.x = Input.get_action_strength("right") - Input.get_action_strength("left")
#direction.y = Input.get_action_strength("down") - Input.get_action_strength("up")
direction = Vector2(
Input.get_axis("left", "right"),
Input.get_axis("up", "down")
).normalized()
pass
func _physics_process(delta: float) -> void:
move_and_slide()
func set_direction() -> bool:
if direction == Vector2.ZERO:
return false
var direction_id : int = int( round( ( direction + cardinal_direction * 0.1 ).angle() / TAU * DIR_4.size() ) )
var new_dir = DIR_4[ direction_id ]
if new_dir == cardinal_direction:
return false
cardinal_direction = new_dir
DirectionChanged.emit( new_dir)
sprite.scale.x = -1 if cardinal_direction == Vector2.LEFT else 1
return true
func update_animation( state : String ) -> void:
animation_player.play( state + "_" + AnimDirection() )
pass
func AnimDirection() -> String:
if cardinal_direction == Vector2.DOWN:
return "down"
elif cardinal_direction == Vector2.UP:
return "up"
else:
return "side"
func _take_damage( hurt_box : HurtBox) -> void:
if invulnerable == true:
return
update_hp( -hurt_box.damage )
if hp > 0:
player_damaged.emit( hurt_box )
else:
player_damaged.emit( hurt_box )
update_hp(99)
pass
func update_hp ( delta : int ) -> void:
hp = clampi( hp + delta, 0, max_hp)
if health_bar:
health_bar.hp = hp
pass
func make_invulnerable ( _duration : float = 1.0 ) -> void:
invulnerable = true
hit_box.monitoring = false
await get_tree().create_timer( _duration).timeout
invulnerable = false
hit_box.monitoring = true
pass
in my Autoload i have my PlayerManager script which should give acces to my player data.
Playermanager
extends Node
const PLAYER_CHARACTEER = preload("res://scenes/player/PlayerCharacteer.tscn")
var player : Player
signal player_ready(player: Player)
var player_spawned : bool = false
func _ready () -> void:
add_player_instance()
await get_tree().create_timer(0.2).timeout
player_spawned = true
func add_player_instance() -> void:
player = PLAYER_CHARACTEER.instantiate()
add_child( player )
func set_player_position( _new_pos : Vector2) -> void:
player.global_position = _new_pos
func set_as_parent( _p : Node2D ) -> void:
if player.get_parent():
player.get_parent().remove_child( player )
_p.add_child( player )
func unparent_player( _p : Node2D ) -> void:
_p.remove_child( player )
PlayerHud
extends CanvasLayer
@onready var health_bar : HealthBar = $Control/HealthBar
HealthBar
class_name HealthBar
extends ProgressBar
@onready var timer: Timer = $Timer
@onready var damage_bar: ProgressBar = $DamageBar
signal health_depleted
var hp = 0 : set = _set_health
func _set_health(new_health: int) -> void:
var prev_health = hp
hp = min(max_value, new_health)
value = hp
if hp <= 0:
emit_signal("health_depleted")
if hp < prev_health:
timer.start()
else:
damage_bar.value = hp
func init_health(_hp: int) -> void:
hp = _hp
max_value = _hp
value = _hp
damage_bar.max_value = _hp
damage_bar.value = _hp
func _on_timer_timeout() -> void:
damage_bar.value = hp
Most of this code is identical to the youtube tutorial i mentioned. He used a heart system like zelda games and i tried adding this healthbar in a similar fashion.
If i can supply any more info just ask me. In the mean time i will try out your suggestions.
Thank you
It “works” when adding the script because PlayerHud.health_bar is null and the player _ready() is stuck in this loop:
The reason PlayerHud.health_bar is null is because the script doesn’t know anything about the scene. That’s why you need to add the scene itself instead of only the script.
Are you sure you are adding the tscn with the same name as when you added the gd file as the autoload? That error means that PlayerHud is null
I added the Player_HUD.tsc to my globals. I have changed all parts in my code that said Player_Hud to Player_HUD because of casing. My script is called player_hud and is not in my globals. The script should load because it is attached to my Player_HUD scene’s CanvasLayer.
I have found a way to make it work. my player script has a function on change health and i created 2 signals for hp and max hp in that function. my player hud manager connects to those signal with a funktion for health changed and max health changed and sends those values to its progressbar child.
Sorry to join in on this so late, but this does not seem right to me. An autoloaded script is managed by Godot at the root level so it is globally available. You should not then attach that to a node in the scene tree. You may have ‘got away with it’ but I am pretty sure this is going to come back and bite you.
Attach a normal script to your canvas layer, and then you can access the variables in your global script any time from your canvas layer script (or anywhere else). Attaching a global script to a node seems like madness to me. I could be wrong, but this does not seem right in any way to me. (If I have misunderstood then please ignore this.)
Thats something i copied from a youtube tutorial series. So currently my Player HUD scene is a global autoload. Im not adding the playerHUD scene to anything in my project. Its just something that is loaded at all time. It will problably be anoying in the fututre when i make a hub zone where i dont need tio show the player hp but other then that i dont see a big problem.
I mean in an ideal world i would be able to script everything clean an perfect but i just startet a week ago and have no game making and programming experience. without youtube and chatgpt i wouldnt be able to do anything right now