Variables appearing out of nowhere?

extends CharacterBody3D
class_name Player

## How fast the player moves on the ground.
@export var base_speed := 6.0
## How high the player can jump in meters.
@export var jump_height := 1.2
## How fast the player falls after reaching max jump height.
@export var fall_multiplier := 2.5

@export_category("Camera")
## How much moving the mouse moves the camera. Overwritten in settings.
@export var mouse_sensitivity: float = 0.00075
## Limits how low the player can look down.
@export var bottom_clamp: float = -90.0
## Limits how high the player can look up.
@export var top_clamp: float = 90.0
@export var player_camera: PackedScene

@export_category("Third Person")
## Limits how far the player can zoom in.
@export var min_zoom: float = 1.5
## Limits how far the player can zoom out.
@export var max_zoom: float = 6.0
## How quickly to zoom the camera
@export var zoom_sensitivity: float = 0.4

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
# Stores the direction the player is trying to look this frame.
var _look := Vector2.ZERO
var owner_id = 1
var camera_instance

enum VIEW {
	FIRST_PERSON,
	THIRD_PERSON_BACK
}

# Updates the cameras to swap between first and third person.
var view := VIEW.FIRST_PERSON:
	set(value):
		view = value
		match view:
			VIEW.FIRST_PERSON:
				# Get the fov of the current camera and apply it to the target.
				camera_instance.fov = get_viewport().get_camera_3d().fov
				camera_instance.current = true
				#UserInterface.hide_reticle(false)
			VIEW.THIRD_PERSON_BACK:
				# Get the fov of the current camera and apply it to the target.
				third_person_camera.fov = get_viewport().get_camera_3d().fov
				third_person_camera.current = true
				#UserInterface.hide_reticle(true)

# Control the target length of the third person camera arm..
var zoom := min_zoom:
	set(value):
		zoom = clamp(value, min_zoom, max_zoom)
		if value < min_zoom:
			# When the player zooms all the way in swap to first person.
			view = VIEW.FIRST_PERSON
		elif value > min_zoom:
			# When the player zooms out at all swap to third person.
			view = VIEW.THIRD_PERSON_BACK

#@onready var camera: Camera3D = $SmoothCamera
@onready var third_person_camera: Camera3D = camera_instance.get_node("ThirdPersonCamera")
@onready var spring_arm_3d: SpringArm3D = camera_instance.get_node("SpringArm3D")

@onready var camera_target: Node3D = $CameraTarget
@onready var camera_origin = camera_target.position

@onready var animation_tree: AnimationTree = $AnimationTree
@onready var run_particles: GPUParticles3D = $BasePivot/RunParticles
@onready var jump_particles: GPUParticles3D = $BasePivot/JumpParticles

@onready var jump_audio: AudioStreamPlayer3D = %JumpAudio
@onready var run_audio: AudioStreamPlayer3D = %RunAudio


func _ready() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	# Whenever the player loads in, give the autoload ui a reference to itself.
	#UserInterface.update_player(self)

func _enter_tree():
	owner_id = name.to_int()
	set_multiplayer_authority(owner_id)
	if owner_id != multiplayer.get_unique_id():
		return
	
	set_up_camera()
	
	



func _physics_process(delta: float) -> void:
	if owner_id != multiplayer.get_unique_id():
		return
	frame_camera_rotation()
	smooth_camera_zoom(delta)
	
	# Add gravity.
	if not is_on_floor():
		# if holding jump and ascending be floaty.
		if velocity.y >= 0 and Input.is_action_pressed("ui_accept"):
			velocity.y -= gravity * delta
		else:
			# Double fall speed, after peak of jump or release of jump button.
			velocity.y -= gravity * delta * fall_multiplier
		
	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		# Projectile motion to turn jump height into a velocity.
		velocity.y = sqrt(jump_height * 2.0 * gravity)
		jump_particles.restart()
		jump_audio.play()
		run_audio.play()
	
	# Handle movement.
	var direction = get_movement_direction()
	if direction:
		velocity.x = lerp(velocity.x, direction.x * base_speed, base_speed * delta)
		velocity.z =  lerp(velocity.z, direction.z * base_speed, base_speed * delta)
	else:
		velocity.x = move_toward(velocity.x, 0, base_speed * delta * 5.0)
		velocity.z = move_toward(velocity.z, 0, base_speed * delta * 5.0)
	
	# Emit run particles when moving on the floor.
	run_particles.emitting = not direction.is_zero_approx() and is_on_floor()
		
	update_animation_tree()
	move_and_slide()

# Turn movent inputs into a locally oriented vector.
func get_movement_direction() -> Vector3:
	var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
	return (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
# Apply the _look variables rotation to the camera.
func frame_camera_rotation() -> void:
	rotate_y(_look.x)
	# The smooth camera orients the camera to align with the target smoothly.
	camera_target.rotate_x(_look.y)
	camera_target.rotation.x = clamp(camera_target.rotation.x, 
		deg_to_rad(bottom_clamp), 
		deg_to_rad(top_clamp)
	)
	# Reset the _look variable so the same offset can't be reapplied.
	_look = Vector2.ZERO


# Blend the walking animation based on movement direction.
func update_animation_tree() -> void:
	# Get the local movement direction.
	var movement_direction = basis.inverse() * velocity / base_speed
	# Convert the direction to a Vector2 to select the correct movement animation.
	var animation_target = Vector2(movement_direction.x, -movement_direction.z)
	animation_tree.set("parameters/blend_position", animation_target)

func _unhandled_input(event: InputEvent) -> void:
	# Update the _look variable to the latest mouse offset.
	if event is InputEventMouseMotion:
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			_look = -event.relative * mouse_sensitivity
	# Capture the mouse if it is uncaptured.
	if event.is_action_pressed("click"):
		if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			
	# Camera controls.
	if event.is_action_pressed("toggle_view"):
		cycle_view()
	if event.is_action_pressed("zoom_in"):
		zoom -= zoom_sensitivity
	elif event.is_action_pressed("zoom_out"):
		zoom += zoom_sensitivity
	
func cycle_view() -> void:
	# Swap from third to first person and vice versa.
	match view:
		VIEW.FIRST_PERSON:
			view = VIEW.THIRD_PERSON_BACK
			# Set the default third person zoom to halfway between min and max.
			zoom = lerp(min_zoom, max_zoom, 0.5)
		VIEW.THIRD_PERSON_BACK:
			view = VIEW.FIRST_PERSON
		_:
			view = VIEW.FIRST_PERSON

# Interpolate the third person distance to the target length.
func smooth_camera_zoom(delta: float) -> void:
	spring_arm_3d.spring_length = lerp(
		spring_arm_3d.spring_length,
		zoom,
		delta * 10.0
	)

func set_up_camera():
	camera_instance = player_camera.instantiate()
	#camera_instance.global_position.y = camera_height
	
	get_parent().add_child.call_deferred(camera_instance)

# Play a footstep sound effect when moving.
func _on_footstep_timer_timeout() -> void:
	if is_on_floor() and get_movement_direction():
		run_audio.play()

I haven’t had any success with this method. I posted my code in a reply below. Maybe it’s a simple thing I am missing there. I am dyslexic so there may be a simple grammatical issue I missed.

have you checked to make sure this actually has a non-null value?

The two variables @group_3_Camera and @group_8_Third Person are dynamically generated by the GDScript interpreter while processing the annotations @export_category("Camera") and @export_category("Third Person"). That explains their “invalid” names (i.e. names that you cannot write in the source code).

Hunting down the reason why these variables are null doesn’t help you solve the problem of why third_person_camera is null.

The stack trace in Variables appearing out of nowhere? - #16 by Techmarine117 shows the following path to the problem:

  • _unhandled_input changes the zoom level
  • @zoom_setter changes the view
  • @view_setter tries to change the third_person_camera.fov, but third_person_camera is null

There is only one place where third_person_camera is set: in the line

@onready var third_person_camera: Camera3D = camera_instance.get_node("ThirdPersonCamera")

Did you check the Debugger / Error tab for any problems?

In my experience when executing that line:

  • if camera_instance is null it would interrupt the game with a stack trace pointing to that line
  • if there is no node named “ThirdPersonCamera” in camera_instance it would just print an error message to the Error tab and continue until your code tries to access third_person_camera

It seems to work fine until the switch happens to the third person camera, then it gives a null error. Is the issue with how I have written the line you cited?

Thank you, I realized I was misunderstanding how that part of the code functioned. When i researched online, I thought using this would be the best solution but I see that I did not clearly understand this.

The debugger does bring the error up once when the switch happens between the camera views. There is a node named " thirdpersoncamera" attached to another camera and it swaps between the two using a spring arm. I was trying to reference it when I was instantiaing it into the scene. I thought it was working however I now think it is not being assigned properly.

Instead of initializing the third_person_camera using the @onready annotation like you do now

you could try to initialize it the moment you create the camera_instance within the set_up_camera() function:

Modified variable declaration (replaces lines 68 and 69):

var third_person_camera: Camera3D
var spring_arm_3d: SpringArm3D

Modified function:

func set_up_camera():
	camera_instance = player_camera.instantiate()
	#camera_instance.global_position.y = camera_height
	
	get_parent().add_child.call_deferred(camera_instance)
	third_person_camera = camera_instance.get_node("ThirdPersonCamera")
	spring_arm_3d = camera_instance.get_node("SpringArm3D")

	print("third_person_camera=", third_person_camera)
	print("spring_arm_3d=", spring_arm_3d)

Thank you for the suggestion. I tried this version and am still getting the same error. I am starting to think I may need to redo portions of the script unless you have any other ideas. I have been researching some options but I am coming up short. Are there any other godot resources I can take a look at? Maybe other tutorials or documentation?