@export var: PackedScene always returns null but loaded resource path doesn't

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Pyroka

Problem

I am trying to instantiate a scene which i give to my scriped via an export variable of type PackedScene. I set up the variable and loaded the scene in the inspector as explained in the “first 3D game” tutorial.

But it is null and i can not find out why it wont load the scene through the Inspector.
If i try loading it directly through ia resource path it works. I printed the var in the _ready() and the _process() functions.

Any idea what causes this and how it can be fixed or avoided?

The code can be seen in the image as well as the loaded scene.
Image Link-to-img.

Here the same part as code cell:

@export var weapon0 = PackedScene.new()
@onready var weapon1: PackedScene = preload("res://scenes/Weapons/base_weapon.tscn")

## initialize stast for player ship
func _init():
	var ship = ShipVariables.new(8, 5, 100, 0, 50, 0.2)
	super._init(ship)

func _ready():
	#ingametarget = get_tree().get_root().get_node("main/test")
	# Create Weapon
	print("ready:") 
	print(weapon0)
	print(weapon1)
	print("----------------------------")

Console Output:

ready:
<null>
<PackedScene#-9223372009608575851>
----------------------------
process:
<null>
-------

Both weapon0 and weapon1load the same scene as resource. (I also tested using load instead of preload and without using @onready annotation both works as well only the @export var won’t work.)


Additonal Code in case it is relevant:

Full PlayerShip class:

extends "res://scenes/BaseShip.gd"

var ingametarget

#@export var weapon: PackedScene
@export var ammo = PackedScene.new()
@export var weapon0 = PackedScene.new()
@onready var weapon1: PackedScene = preload("res://scenes/Weapons/base_weapon.tscn")

## initialize stast for player ship
func _init():
	var ship = ShipVariables.new(8, 5, 100, 0, 50, 0.2)
	super._init(ship)

func _ready():
	ingametarget = get_tree().get_root().get_node("main/test")
	# Create Weapon
	print("ready:") 
	print(weapon0)
	print(weapon1)
	var testnode = get_node("weaponslot1")
	print(testnode)
	print("----------------------------")
	#instantiate_weapon(weapon0)
	#set_projectile_for_weapon("weapon0", ammo)

# Get the gravity from project settings 
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

## Get the input from the player based on the four axis (left right top bottom)
## returns it as 2D Vector (note y component is the z component if used in 3D)
## y itself (up) is empty in this vector
func get_player_input() -> Vector2:
	var input = Input.get_vector("move_left", "move_right", "move_top", "move_bottom")
	return input
	
func _physics_process(delta):
	velocity = calc_velocity(get_player_input())
	move_and_slide()
	
func _process(delta):
	## debugging stuff with "b"
	if Input.is_action_just_pressed("debug_btn"):
		print("process:") 
		print(weapon0)
		print("-------")
		#print("shooting")
		#shoot_weapon(ingametarget.position)
		#print(ingametarget.position)
		#transform = transform.looking_at(ingametarget.position)

The BaseShip class:

extends CharacterBody3D

#Stats
var ship_vars : ShipVariables

## init function that can be called by inheriting scenes to set vars
func _init(ship_vars_class):
	self.ship_vars = ship_vars_class
	
## Load the weapon scene
## instanciate the weapon in a weapon slot
func instantiate_weapon(w0: PackedScene) -> void:
	# set in variable class
	self.ship_vars.load_weapon0_scenes(w0)
	# instantiate
	var weapon0 = self.ship_vars.weapon_0.instantiate()
	weapon0.name = "weapon0"
	###instance.transform.looking_at(target)  # or look_at ?
	$weaponslot1.add_child(weapon0)
	## Assign projectile/ammo to weapon
	
## Load the weapon scene
## instanciate the weapon in a weapon slot
func set_projectile_for_weapon(weaponname: String, projectile: PackedScene) -> void:
	get_node(weaponname).set_projectile(projectile)


## Caluclate vector to be applied with move_and_slide
## Using normalized target_position or inputs and speed
func calc_velocity(input_dir) -> Vector3:
	# transform.basis: local coordinate system traveling with the object
	# Multiplying with transform.basis, means that the local system is used
	# therefor pressing forward moves in the look direction instead of global z
	var direction = (self.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	# apply velocity based in input dir
	# for other direction apply slow
	if direction.x:
		velocity.x += direction.x * ship_vars.move_speed * ship_vars.acceleration
		velocity.x = clamp(velocity.x, -ship_vars.move_speed, ship_vars.move_speed)
	else:
		velocity.x = move_toward(velocity.x, 0, ship_vars.break_speed)
	if direction.z:
		velocity.z += direction.z * ship_vars.move_speed * ship_vars.acceleration
		velocity.z = clamp(velocity.z, -ship_vars.move_speed, ship_vars.move_speed)
	else:
		velocity.z = move_toward(velocity.z, 0, ship_vars.break_speed)
	return velocity

## rotate ship to face the target
func rotate_to(target: Vector3) -> void:
	return

## shoot the loaded weapon with given projectile
func shoot_weapon(target: Vector3) -> void:
	# shoot without target (add dummy target)
	self.ship_vars.weapon_0.shoot(target)

## apply taken damage
func take_damage(damage: float) -> void:
	ship_vars.health -= damage
	
## delete character upon death and do effects
func die() -> void:
	return
	
## handle things like collision with other ships 
## calculate dmg based on armor and mass from both ships
## emitted from this ships CollisionShape if its a ship
func on_collision_enter():
	return

## handle hits from projectiles 
## signal emitted from this ships CollisonShape if its a projectile
func on_projectile_recieved():
	return

The BaseWeapon class:

extends StaticBody3D

#Stats
var weapon_vars : WeaponVariables

## init function that can be called by inheriting scenes to set vars
func _init():  #weapon_vars_class
	self.weapon_vars = WeaponVariables.new(1, 50, 1, 0, 0, false)
	
func set_projectile(_projectile):
	self.weapon_vars.load_projectile_scenes(_projectile)

## shoot the assigned projectile with the set variables
func shoot(target: Vector3):
	if not self.weapon_vars.auto_aim:
		# forward is (0,0,-1)
		target = ($BulletSpawn.transform.basis * Vector3(0,0,-1)).normalized()
	
	var bullet = self.weapon_vars.projectile  # preload("res://MyScene.tscn")
	var instance = bullet.instantiate()
	instance.transform.looking_at(target)  # or look_at ?
	get_tree().get_root().add_child(instance)
	
	"""
	self.atk_speed = _atk_speed
	self.range = _range
	self.projectile_count = _projectile_count
	self.projectile_spread = _projectile_spread
	self.projectile_delay = _projectile_delay
	self.auto_aim = _auto_aim
	"""
	

The ShipVariables class:

class_name ShipVariables #, "res://assets/image.png"

#class ShipVariables:
var move_speed : float
var turn_speed : float
var health : float
var armor : float
var mass : float
var break_speed : float
## in percentage of speed
var acceleration : float

# scenes to load #@export 
var weapon_0 : PackedScene

## Init a new ship variabel class containing:
## move_speed
## turn_speed
## health
## armor
## mass
## acceleration
func _init(_move_speed: float, _turn_speed: float,_health: float, _armor: float, _mass: float,
 _acceleration: float):
	self.move_speed = _move_speed
	self.turn_speed = _turn_speed
	self.health = _health
	self.armor = _armor
	self.mass = _mass
	self.break_speed = _move_speed * 0.05
	self.acceleration = _acceleration

func load_weapon0_scenes(weapon: PackedScene):
	self.weapon_0 = weapon

The WeaponVariables class:

class_name WeaponVariables #, "res://assets/image.png"

#class ShipVariables:
var atk_speed : float
var range : float # m
var projectile_count: int
var projectile_spread : float # random spray in percentage 0% = laser
var projectile_delay : float # How fast consecutive projectiles shoot  in s
var auto_aim : bool # aim the weapon
#var homing_projectile  # doesnt make sense here add to projectile

# scenes to load #@export 
var projectile : PackedScene

## Init a new weapon variabel class containing:
func _init(_atk_speed: float, _range: float, _projectile_count: int, _projectile_spread: float, 
_projectile_delay: float, _auto_aim: bool):
	self.atk_speed = _atk_speed
	self.range = _range
	self.projectile_count = _projectile_count
	self.projectile_spread = _projectile_spread
	self.projectile_delay = _projectile_delay
	self.auto_aim = _auto_aim

func load_projectile_scenes(_projectile: PackedScene):
	self.projectile = _projectile

(there are similar questions:
here and here here
but none gives a solution)

and how your ship look in inspector in main scene?

Moreus | 2023-04-04 09:53

I suspect the interplay between inherited scenes/scripts here as at play, simply because a simple scene with just the weapon0 assignment works fine. There’s also a weapon0 local in the BaseShip.gd script that, while local, I don’t know the flow of how your game executes here so wonder if something is overwriting something else.

There are errors in the debugger, worth checking those too.

spaceyjase | 2023-04-04 14:18

Oh wow you are right it was not loaded in the instance in the main scene. Somehow i though it would update automatically.

So i guess i should just always create the player by code to avoid such careless mistakes.

Pyroka | 2023-04-07 17:17

Is there a special interplay between inheriting scenes and scripts? I always assume they are the same and inherete the scene and then the script from that scene as a new script.

And the flow should simply be creating a player_ship then saving the scene for the weapon in an intern variable (in the base class) to later instance the weapon.

Pyroka | 2023-04-07 17:20

It really depends on the project; a simple scene tree is usually quite predictable but anything complex may have timing or ordering issues, perhaps complicated by a class hierarchy in scripts. It’s not unique to godot either, sometimes it’s just the way it is!

Glad you found the issue!

spaceyjase | 2023-04-07 18:12