Invalid get index 'playerHealth' (on base: 'Nil')

Godot Version

Godot 4.2.1

Question

Sorry for my bad english

Hi, i’m very new in Godot and I need help. I try to do a little 2d game following to tutorial(I think you don’t need link 'cause tutorial not on english).
In my code I add global.gd for health, but tutorial dude don’t used it, so now I should move healthbar and add stamina bar and I’m kinda confused how I can move healthbar.

Now for stats I create a new scene, add healthbar at player, create script for stats

global.gd:

extends Node

var playerMaxHealth = 100
var playerHealth

# Called when the node enters the scene tree for the first time.
func _ready():
	playerHealth = playerMaxHealth


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass

Player.gd:


enum {
	MOVE,
	ROLL,
	SLIDE,
	ATTACK,
	ATTACK2,
	DAMAGE,
	DEATH
}

const SPEED = 180.0
const JUMP_VELOCITY = -270.0

# For Flip
@onready var playerAnim = $PlayerSprite
# For Signals 
@onready var playerFullAnimation = $AnimationPlayer

@onready var playerCollisionShape = $CollisionShape2D
@onready var playerCollisionShapeAfter = $CollisionShape2D
@onready var playerAttackDirection = $AttackDirectionPlayer

@onready var playerStats = $stats

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

var playerHealth = playerStats.playerHealth
var state = MOVE

# Move It Later #
var playerCurrentDamage
var playerAttackDamage = 10
var playerAttack2Damage = 20

var attackfreez = false
var slidefreez = false

var playerPos

# ---- Player Statement ---- #
# MOVE STATE #
func playerMoveState():
	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var direction = Input.get_axis("left", "right")
	# RUN ANIMATION
	if direction:
		# If Player run add animation and movement
		velocity.x = direction * SPEED
		# If Player Doesnt's jumping:
		if velocity.y == 0:
			playerAnim.play("Run")
		
		if direction == -1:
			# If Player go back (left): reverse playerSprite to left
			playerAnim.flip_h = true
			playerAttackDirection.rotation_degrees = 180
			# Revers Player Collision, for get her a center of Player #
			playerCollisionShape.position.x = 8

			
		else:
			# If Player go front (right): reverse playerSprite to right
			playerAnim.flip_h = false
			playerAttackDirection.rotation_degrees = 0
			playerCollisionShape.position.x = 0
			
	# IDLE ANIMATION
	else:
		# If Player AFK add animation 
		velocity.x = move_toward(velocity.x, 0, SPEED)
		# If Player Doesnt's jumping:
		if velocity.y == 0:
			playerAnim.play("Idle")
	
	
	# --- Swap to Statements --- #
	
	# Swap to ROLL aimation
	if direction and Input.is_action_pressed("roll"):
		state = ROLL
	
	# Swap to SLIDE animation
	if direction and Input.is_action_just_pressed("slide") and slidefreez == false:
		if velocity.y == 0:
			state = SLIDE
		
	# Swap to ATTACK animation
	if Input.is_action_just_pressed("attack") and attackfreez == false:
		if velocity.y == 0:
			state = ATTACK
		
	# Swap to ATTACK2 animation
	if Input.is_action_just_pressed("attack2") and attackfreez == false:
		if velocity.y == 0:
			state = ATTACK2
			
			
# ROLL STATE
func playerRollState():
	if Input.is_action_pressed("roll"):
		if velocity.y == 0:
			playerFullAnimation .play("Roll")
			await playerFullAnimation .animation_finished
			state = MOVE
		else:
			state = MOVE
	
	
# SLIDE STATE #
func playerSlideState():
	if Input.is_action_pressed("slide"):
		if velocity.y == 0:
			playerFullAnimation .play("Slide")
			await  playerFullAnimation .animation_finished
			slideCoolDown()
			state = MOVE
		else:
			state = MOVE
	
	
# ATTACK STATE #
func playerAttackState():
	playerCurrentDamage = playerAttackDamage
	velocity.x = 0
	if velocity.y == 0:
		playerFullAnimation.play("Attack")
		await  playerFullAnimation .animation_finished
		attackCoolDown()
		state = MOVE
	else:
		state = MOVE
		
		
# ATTACK2 STATE #
func playerAttack2State():
	playerCurrentDamage = playerAttack2Damage
	velocity.x = 0
	if velocity.y == 0:
		playerFullAnimation.play("Attack2")
		await  playerFullAnimation .animation_finished
		attack2CoolDown()
		state = MOVE
	else:
		state = MOVE
		
		
# DAMAGE STATE #
func playerDamageState():
	velocity.x = 0
	playerAnim.play("Damage")
	await playerAnim.animation_finished
	state = MOVE
	
	
# DEATH STATE #
func playerDeathState():
	# If Player Died (health <= 0)
	velocity.x = 0
	playerAnim.play("Death")
	await playerAnim.animation_finished
	queue_free()
	get_tree().change_scene_to_file.bind("res://scn/menu/menu.tscn").call_deferred()
	
	
# Attack CoolDown Function #
func attackCoolDown():
	attackfreez = true
	await get_tree().create_timer(0.3).timeout
	attackfreez = false
	
	
func attack2CoolDown():
	attackfreez = true
	await get_tree().create_timer(1).timeout
	attackfreez = false
	
	
func slideCoolDown():
	slidefreez = true
	await get_tree().create_timer(0.5).timeout
	slidefreez = false
	
	
# --- Get Damage from Enemy --- #
func _on_damage_received(enemyDamage):
	if state == ROLL:
		enemyDamage = 0
	elif state ==SLIDE:
		enemyDamage /= 2
	else:
		state = DAMAGE
		
	playerHealth -= enemyDamage
	# Death #
	if playerHealth <= 0:
		playerHealth = 0
		state = DEATH
	
	
func _physics_process(delta):
	# Add the gravity.
	if not is_on_floor():
		velocity.y += gravity * delta
		
	# Handle JUMP.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY
		playerAnim.play("Jump")
		
		
	# FALL
	if velocity.y > 0:
		playerAnim.play("Fall")
		
		
	playerCurrentDamage = playerAttackDamage
		
		
	match state:
		MOVE:
			playerMoveState()
		ROLL:
			playerRollState()
		SLIDE:
			playerSlideState()
		ATTACK:
			playerAttackState()
		ATTACK2:
			playerAttack2State()
		DAMAGE:
			playerDamageState()
		DEATH:
			playerDeathState()
	
	# Sending Signal about Player's position #
	playerPos = self.position
	Signals.emit_signal("playerPositionUpdate", playerPos) 
	
	move_and_slide()
	
	
func _ready():
	Signals.connect("enemyAttack", Callable(self, "_on_damage_received"))
	
	
func _on_hit_box_area_entered(area):
	Signals.emit_signal("playerAttack", playerCurrentDamage)

stats.gd:

extends CanvasLayer

@onready var playerHealthBar = $HealthBar

var playerMaxHealth = Global.playerMaxHealth

var playerHealth = Global.playerHealth:
	set(value):
		playerHealth = value
		playerHealthBar.value = playerHealth

# Called when the node enters the scene tree for the first time.
func _ready():
	playerHealth = playerMaxHealth
	playerHealthBar.max_value = playerHealth
	playerHealthBar.value = playerHealth

When I’m starting game (click on “play button” than scen changing on level) I have error: Invalid get index ‘playerHealth’ (on base: ‘Nil’).

I think it’s easy to fix, but nothing comes to mind.
Please tell me what I need to move/change/add to make the code work. Thank You

Add class_name Global to your global.gd script, before the extends.

I had Global in autoload, now I disable it, add class_name Global in global.gd script before the extends. And now error is - Parser Error: Cannot find member “playerMaxHealth” in base “Global”.
if I pressing “continue” next error is - Invalid get index ‘playerHealth’ (on base: ‘Nil’).

Ok, that doesn’t make much sense to me. Was your autoload named global or Global?

Autoload was Global and I used and now using Global.var_name

Well I am very confused. Mind posting the project so I can look at it myself?

can I post my project on googledisk or dropbox and attach link on them? or I can post him on github. What would be more convenient for you?

github, but whatever is easier for you

At the moment Google Drive is more convenient, I hope this doesn’t bother you

https://drive.google.com/drive/folders/12QgGWEzoV5aAAW8zKwXnQ--1hW-ZN1-N?usp=sharing

Ok, I figured out. Undo the last thing I said. The problem was you had two variables with the same name in global.gd and stats.gd, which is fine, but it made debugging it confusing.
The actual error was from player.gd on line 31. You initialized a variable by using another variable that you had marked as @onready. All you need to do is add @onready to line 31 and your game will load.

I add @onready in 31 line player.gd

but first error - Parser Error: Cannot find member “playerMaxHealth” in base “Global”.
Line 5:Cannot find member “playerMaxHealth” in base “Global”.
Line 7:Cannot find member “playerHealth” in base “Global”.
Line 10:Identifier “playerHealth” not declared in the current scope.

second error - Invalid get index ‘playerHealth’ (on base: ‘CanvasLayer’)., if I “continue” it - game really load, but if player take hit from enemy
third error is - Invalid get index ‘playerHealth’ (on base: ‘CanvasLayer’)., if I “continue” it - game opening, but every time when player take damage - error.

I tried to change some vars (You said they fine, but why not):
player.gd - @onready var healthPlayer = playerStats.health
stats.gd - var maxHealth = Global.playerMaxHealth
var health = Global.playerHealth

actually nothing changed and I return old vars.

It’s a pity that I don’t have a backup copy of the old code, without using global. But I have a oldest version with global, where there is no function to display hp on healthbar(health bar is just empty) and in this version game is load fine.
I’m really don’t understand where where is error in heealthbar function

If it’s doesn’t bother you, here’s link on oldest version - oldest_survival.zip - Google Drive

Did you know you can click on errors to go to the relevant line? =3

Your project, fixed: https://send.vis.ee/download/45fc282790208ddb/#mJ6GuxbZhYgisXeDPwDvMw

1 Like

Thank You! I think I’ll figure out the rest