I’m new, and I’m trying to make a 2D platformer. I’ve recently been trying to implement the health bar, but it causes an error. I’m confused as to what is happening.
Here’s my script:
This chunk is in the player node:
var health = 100
func _ready() → void:
health = 100
health_bar._init_health(health)
This chunk is in the healthbar node:
func _init_health(_health) → void:
health = _health
max_value = _health
value = _health
damage_bar.max_value = health
damage_bar.value = health
The error says: Attempt to call function ‘Init Health’ in base ‘null_instance’ on null_intance
I got this code from a tutorial, which says it’s running in Godot 4, so I’m not sure why it’s not working.
If you put three backticks (```) on lines before and after your code, it formats more nicely:
# Player Script
var health = 100
func _ready() -> void:
health = 100
health_bar._init_health(health)
# Healthbar Script
func _init_health(_health) -> void:
health = _health
max_value = _health
value = _health
damage_bar.max_value = health
damage_bar.value = health
I presume the error is that health_bar in the player script isn’t initialized, but it’s not listed in the code you posted. If it were a node, you might be able to refer to it via $ notation, or get access to it via get_node().
Something has to tell health_bar in the player script where to look for the health bar object so it can find the _init_health() function to execute.
Thanks but the healthbar node is already listed in the player script. Here’s my WHOLE player script (I wrote this about a year ago so it may be bad)
Here’s the player code:
extends CharacterBody2D
const SPEED = 125.0
const JUMP_VELOCITY = -300.0
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var hurtbox: CollisionShape2D = $Hurtbox/CollisionShape2D
@onready var killzone: Area2D = $"../Killzone"
@onready var health_bar: ProgressBar = $Hurtbox/HealthBar
const DASH_SPEED = 300
var dashing: bool = false
var can_dash: bool = true
var gravity = 900
var health = 100
func _ready() -> void:
health = 100
health_bar._init_health(health)
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor() or not dashing:
velocity.y += gravity * delta
if Input.is_action_just_pressed("Dash") and can_dash:
dashing = true
can_dash = false
$Timer.start()
$CanDashTimer.start()
# Handle jump.
if Input.is_action_just_pressed("Up"):
if is_on_floor():
velocity.y = JUMP_VELOCITY
else:
if dashing:
velocity.y = DASH_SPEED - DASH_SPEED - DASH_SPEED
var direction := Input.get_axis("Left", "Right")
if direction:
if dashing:
velocity.x = direction * DASH_SPEED
else:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
if is_on_floor():
if direction == 0:
animated_sprite.play("idle")
else:
animated_sprite.play("run")
if dashing:
animated_sprite.play("dash")
if direction > 0:
animated_sprite. flip_h = false
elif direction < 0:
animated_sprite. flip_h = true
#Handles health and death
health_bar.health = health
func _on_hurtbox_area_entered(_killzone) -> void:
health = health - 15
animated_sprite.play("temporary")
velocity.y = -600
#Makes dash stop
func _on_timer_timeout() -> void:
dashing = false
func _on_can_dash_timer_timeout() -> void:
if is_on_floor():
can_dash = true
Here’s the Healthbar code:
extends ProgressBar
@onready var timer: Timer = $Timer
@onready var damage_bar: ProgressBar = $DamageBar
var health = 0 : set = _set_health
func _set_health(new_health):
var prev_health = health
health = min(max_value,new_health)
value = health
if health <= 0:
queue_free()
if health < prev_health:
timer.start()
else:
damage_bar.value = health
func _init_health(_health) -> void:
health = _health
max_value = _health
value = _health
damage_bar.max_value = health
damage_bar.value = health
func _on_timer_timeout() -> void:
damage_bar.value = health
@onready var health_bar: ProgressBar = $Hurtbox/HealthBar
That @onready gets called (I believe) when the player node enters the scene tree, which may occur before the health bar node is instanced or added to the scene tree. Basically, you may be grabbing a reference to something that doesn’t exist yet.
You could try something like:
# Player script.
var health_bar: Node = null
[...]
func _process(delta: float) -> void:
if !health_bar: health_bar = $Hurtbox/HealthBar
[...]
Basically, have _process() check if it’s initialized and if not, fix that. You should only eat the node path lookup cost once, and a single branch won’t cost you much performance.
Thanks, but it still doesn’t work. Please let me know if I put it in the wrong spot (which I think I did). Here’s my player script (that has all the healthbar stuff in it):
const SPEED = 125.0
const JUMP_VELOCITY = -300.0
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var hurtbox: CollisionShape2D = $Hurtbox/CollisionShape2D
@onready var killzone: Area2D = $"../Killzone"
var health_bar: Node = null
const DASH_SPEED = 300
var dashing: bool = false
var can_dash: bool = true
var gravity = 900
var health = 100
func _process(delta: float) -> void:
if !health_bar: health_bar = $Hurtbox/HealthBar
func _ready() -> void:
health = 100
health_bar._init_health(health)
Now the error shows:
Invalid call. Nonexistent function ‘_init_health’ in base ‘Nil’
Let’s walk through what’s likely wrong and how to fix it.
What is health_bar?
You need to make sure that health_bar is a reference to your actual HealthBar node in the scene.
If it’s not assigned before calling _init_health, you’ll get that error.
Fix: Assign the HealthBar node
Make sure to assign health_bar in your Player script before you try to use it.
If HealthBar is a child of the Player node in the scene tree, you can assign it like this:
@onready var health_bar = $HealthBar
Put that above your _ready() function in the Player script. The full code should now look like this:
@onready var health_bar = $HealthBar
var health = 100
func _ready() → void:
health = 100
health_bar._init_health(health)
About the HealthBar Script
Your HealthBar script is mostly fine, just make sure the node it’s attached to (likely a TextureProgressBar or similar) has a child node named damage_bar if you reference it like this:
damage_bar.max_value = health
damage_bar.value = health
Or, also define damage_bar properly at the top:
@onready var damage_bar = $DamageBar
Summary:
Make sure health_bar is initialized with @onready and points to the correct node.
Avoid calling methods on null references.
Check your scene tree structure so node paths match.
Ok, something worth knowing: Things that start with an underscore can’t be called from outside the script. I bet if you rename your _init_health() function to init_health(), it will link up properly.
In that case, try removing the init health call from _ready() and do something like:
# Player script.
var health_bar: Node = null
func _process(delta: float) -> void:
if !health_bar:
health_bar = $Hurtbox/HealthBar
health_bar.init_health(health)
_init_health() can’t be called from outside the script it’s defined in because the leading underscore marks it as internal-only.
Trying to call something on health_bar in the _ready() function runs the risk that the player’s _ready() script is called before the health bar is instantiated. You can’t be sure what the order of instantiation of objects is, and _ready() gets called when an object is instantiated and added to the tree, not when all objects are instantiated and added to the tree.
Is the $Hurtbox/HealthBar path correct? does the health_bar in the scene have a HealthBar name?
If node path is incorrect then get_node("Hurtbox/HealthBar") (and $Hurtbox/HealthBar) will return null and will not rise a warning/error
Try to set health_bar using inspector.
This should ensure that the existing node is selected:
# save changes and set health_bar in the inspector
@export var health_bar: ProgressBar
func _ready() -> void:
health = 100
health_bar._init_health(health)