Godot Version
4.6.3
Question
Are there any sources that I can go to see how to structure an advanced enemy ai and/boss fight for a 2d platformer or will I have to tough it out? (still fairly new to Godot)
Start by making a simple enemy first.
so how do I fix " ‘global_position’ on a base object of type ‘null instance’ " for this code to work?
full error: BossPassiveState._physics_update: Invalid access to property or key ‘global_position’ on a base object of type ‘null instance’.
it extends from a parent class that extends from refcounted
var player: PlayerController
func _enter(_state_name: String = "") -> void:
player = enemy.get_tree().get_first_node_in_group("PlayerController")
enemy.movement.set_rand_values()
func _update(_delta: float) -> void:
if enemy.movement.move_timer > 0:
enemy.movement.move_timer -= _delta
else:
enemy.movement.set_rand_values()
func _physics_update(_delta: float) -> void:
var direction : int = floori(player.global_position.x - enemy.global_position.x)
enemy.movement.enemy_movement()
if enemy.movement.direction != 0:
enemy.animations.play("walk")
else:
enemy.animations.play("idle")
if direction < 30:
state_machine.change_state("pursuing")
You make sure that the reference in question is properly initialized. The error means it’s not, i.e. its value is null.
I’ve been at this for a while now and I can’t get the enemy to follow the player, I can get it to wander but not follow the player
tried:
for overlapping_bodies in enemy.detection.get_overlapping_bodies():
var distance := overlapping_bodies.global_position - enemy.global_position
if distance.length() > 25:
enemy.velocity = distance.normalized() * move_speed
print("following")
else :
enemy.velocity = Vector2()
this one gives an error every time (even when triggering from collisions):
@onready var player: PlayerController = enemy.get_tree().get_first_node_in_group("player")
func enemy_pursuit_movement() -> void:
var move_speed := move_values.move_speed
var distance := player.global_position - enemy.global_position
if distance.length() > 25:
enemy.velocity = distance.normalized() * move_speed
else :
enemy.velocity = Vector2()
I just don’t know what to do
Try to get the player reference inside enemy_pursuit_movement() instead of just initializing it @onready
says the same error:
EnemyMovementComponent.enemy_pursuit_movement: Invalid access to property or key ‘global_position’ on a base object of type ‘null instance’.
but the game scene loads, player controller works and everything so why can’t it get the global position?
Determine which variable is null by printing all relevant variables, and then take care to assign the wanted reference to it before using it.
fixed it was how I declared distance
the fix:
var distance = player.global_position - enemy.global_position
before:
var distance : Vector2 = player.global_position - enemy.global_position
Isn’t distance supposed to be a scalar (single value) and not a vector?
I named I distance instead of direction because I already declared it globally for the wander phase, more likely gonna remove the global declaration for consistency
very good idea is to make diagram about what enemy does or trying to think in your enemy’s shoes
e.g, let’s say we have a sentry gun, as sentry gun you can say:
see, based off that you can pretty much build the code, all of loops, checks, ect.
Building on this, you might want to try commenting sections in your code to explain what is happening. Some devs are against this, but when you are learning it is an excellent way to keep track of what is happening.
func attack(enemy) -> void:
# Check if enemy is close enough
# Look at enemy
# Shoot
here’s a current visual of my code for the enemy for better judgement
Enemy Controller (the character body 2d)
class_name BossController extends CharacterBody2D
@onready var body: Node2D = %Body
@onready var animations: AnimationPlayer = %EnemyAnimations
@onready var detection: Area2D = %Detection
@onready var visionbox: CollisionShape2D = %Visionbox
@onready var detection_ray: RayCast2D = %PlayerDetection
@export var health : HealthComponent
@export var movement: EnemyMovementComponent
@export var state_machine : StateMachine
@export var detection_values: DetectionValues
var has_detected_player : bool = false
func _ready() -> void:
set_up_states()
func _process(delta: float) -> void:
state_machine.update_process(delta)
func _physics_process(delta: float) -> void:
state_machine.update_physics(delta)
movement.update_gravity(delta)
set_sprite_direction()
ray_player_detection()
move_and_slide()
func set_sprite_direction() -> void:
if velocity.x > 0:
body.scale.x = 1.0
elif velocity.x < 0:
body.scale.x = -1.0
func get_sprite_direction() -> float:
return body.scale.x
func set_up_states() -> void:
state_machine.add_state("passive", BossPassiveState.new(self, state_machine))
state_machine.add_state("pursuing", BossPursuingState.new(self, state_machine))
state_machine.starting_state("passive")
func ray_player_detection() -> void:
var player : PlayerController = get_tree().get_first_node_in_group("player")
if detection_ray.enabled:
detection_ray.look_at(player.global_position)
else:
detection_ray.target_position.x = 110.0
detection_ray.target_position.y = 0.0
if detection_ray.is_colliding() and detection_ray.get_collider() == player:
has_detected_player = true
else:
has_detected_player = false
func _on_detection_body_entered(_body: Node2D) -> void:
visionbox.modulate = Color.RED
detection_ray.enabled = true
func _on_detection_body_exited(_body: Node2D) -> void:
visionbox.modulate = Color.WHITE
detection_ray.enabled = false
The enemy passive/wander state (Bossstate extends from State which is refcounted):
class_name BossPassiveState extends BossState
func _enter(_state_name: String = "") -> void:
pass
func _exit() -> void:
enemy.movement.move_timer = 3
func _update(_delta: float) -> void:
if enemy.movement.move_timer <= 0 :
enemy.movement.set_rand_values()
else:
enemy.movement.move_timer -= _delta
func _physics_update(_delta: float) -> void:
if enemy.movement.move_timer > 0:
enemy.movement.enemy_movement()
print(str(enemy.movement.move_timer))
if enemy.movement.direction != 0:
enemy.animations.play("walk")
else:
enemy.animations.play("idle")
if enemy.has_detected_player:
state_machine.change_state("pursuing")
the enemy pursuing state:
class_name BossPursuingState extends BossState
func _enter(_state_name: String = "") -> void:
pass
func _exit() -> void:
enemy.velocity = Vector2.ZERO
enemy.movement.direction = 0
func _physics_update(_delta: float) -> void:
enemy.movement.enemy_pursuit_movement()
if enemy.velocity.x:
enemy.animations.play("walk")
else :
enemy.animations.play("idle")
if enemy.has_detected_player == false:
state_machine.change_state("passive")
enemy_pursuit_movement function:
func enemy_pursuit_movement() -> void:
var move_speed := move_values.move_speed
var player := get_tree().get_first_node_in_group("player")
direction_to_player = player.global_position.x - enemy.global_position.x
#print("Player: " + str(player.global_position))
#print("enemy: " + str(enemy.global_position))
#
if direction_to_player > 25:
enemy.velocity.x = (move_speed * (direction_to_player/direction_to_player))
else:
enemy.velocity = Vector2()
enemy basic movement function and set_rand_values (set_rand_values definitely could use a better name)
func set_rand_values() -> void:
direction = randi_range(-1, 1)
move_timer = randi_range(3, 5)
func enemy_movement() -> void:
var move_speed := move_values.move_speed
if enemy:
enemy.velocity.x = move_speed * direction