Pick_random() not working?

Godot Version

4.3

Question

i have setted up an enemy that targets the player and moves via tilemap and astargrid, however i have 1 problem

i want the enemy to move to a random tile (only a walkable tile) when it goes into a certain state, luckily path (a variable i made) had an indentifier called “pick_random”, so i tried it and the enemy isnt going to random tiles, it just freezes until it switches to another state that allows it to chase the player

is it because of something i forgot to type? or something else? here’s where “pick_random()” is located in:

func chase():
	print("CHASE")
	is_moving = true
	var path = astar_grid.get_id_path(
		tile_map.local_to_map(global_position),
		tile_map.local_to_map(player.global_position)
	)
	
	path.pop_front()
	
	if path.size() == 0:
		print("arrived at pacman")
		return
	
	if path.is_empty():
		print("cant find path")
		return
	
	var original_position = Vector2(global_position)
	
	global_position = tile_map.map_to_local(path[0])
	body.global_position = original_position
	
	var movement_direction = global_position - body.global_position
	update_direction(movement_direction)
	
	is_moving = true
	
	
	target.global_position
	
	current_state = GhostState.CHASE
	
	if current_state == GhostState.RUN_AWAY:
		is_moving = true
		path.pick_random()
	elif current_state == GhostState.EATEN:
		pass

and also heres the full code of the enemy:

extends Node2D

class_name Ghost

enum GhostState {CHASE, RUN_AWAY, EATEN}

signal frightened_timeout

@onready var player: Sprite2D = $"../Player" as PacMan
@onready var tile_map: TileMap = $"../TileMap"
@onready var body = $Sprite2D as BodySprite
@onready var target: Sprite2D = $Target
@onready var eyes = $Sprite2D/eyes as EyesSprite
@onready var ghostsiren: AudioStreamPlayer2D = $"../SFX/ghostsiren"
var current_state: GhostState
@onready var frightened_timer: Timer = $"../Frightened_Timer"
@export var color: Color
@onready var area_2d: Area2D = $Sprite2D/Area2D
@onready var frightenedghost: AudioStreamPlayer2D = $"../SFX/frightenedghost"
@onready var gate_tile: Sprite2D = $"../GateTile"
@onready var ghosteaten: AudioStreamPlayer2D = $"../SFX/ghosteaten"






var is_blinking = false



@export var move_speed: float = 1




var direction = null


var astar_grid: AStarGrid2D
var is_moving: bool


func _ready() -> void:
	move_speed = 0.9
	ghostsiren.play()
	is_blinking = false
	area_2d.set_collision_mask_value(1, true)
	body.normal()
	eyes.show()
	astar_grid = AStarGrid2D.new()
	astar_grid.region = tile_map.get_used_rect()
	astar_grid.cell_size = Vector2(8, 8)
	astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
	astar_grid.update()
	
	
	var region_size = astar_grid.region.size
	var region_position = astar_grid.region.position
	
	for x in region_size.x:
		for y in region_size.y:
			var tile_position = Vector2i(
				x + region_position.x,
				y + region_position.y,
			)
			
			var tile_data = tile_map.get_cell_tile_data(0, tile_position)
			
			if tile_data == null or not tile_data.get_custom_data("walk_tiles") or tile_data.get_custom_data("gate"):
				astar_grid.set_point_solid(tile_position)


func _process(_delta: float) -> void:
	if is_moving:
		return
	
	states()
	
	if !frightened_timer.is_stopped() && frightened_timer.time_left < frightened_timer.wait_time / 5 && !is_blinking:
		start_flashing()

func chase():
	print("CHASE")
	is_moving = true
	var path = astar_grid.get_id_path(
		tile_map.local_to_map(global_position),
		tile_map.local_to_map(player.global_position)
	)
	
	path.pop_front()
	
	if path.size() == 0:
		print("arrived at pacman")
		return
	
	if path.is_empty():
		print("cant find path")
		return
	
	var original_position = Vector2(global_position)
	
	global_position = tile_map.map_to_local(path[0])
	body.global_position = original_position
	
	var movement_direction = global_position - body.global_position
	update_direction(movement_direction)
	
	is_moving = true
	
	
	target.global_position
	
	current_state = GhostState.CHASE
	
	if current_state == GhostState.RUN_AWAY:
		is_moving = true
		path.pick_random()
	elif current_state == GhostState.EATEN:
		pass



func _physics_process(_delta: float) -> void:
	if is_moving:
		body.global_position = body.global_position.move_toward(global_position, move_speed)
		
		if body.global_position == global_position:
			is_moving = false

func update_direction(movement: Vector2) -> void:
	if movement.x > 0:
		direction = Vector2.RIGHT
	elif movement.x < 0:
		direction = Vector2.LEFT
	elif movement.y > 0:
		direction = Vector2.DOWN
	elif movement.y < 0:
		direction = Vector2.UP
	
	# Now, update the sprite texture based on the direction
	eyes.change_texture_based_on_direction(direction)





func _on_area_2d_body_entered(_body = player) -> void:
	if current_state == GhostState.RUN_AWAY:
		print("EATEN")
		get_eaten()
	elif current_state == GhostState.CHASE:
		print("DEATH")
		area_2d.set_collision_mask_value(1, false)
		get_tree().quit()

func frightened_mode():
	print("FRIGHTENED")
	if frightened_timer.is_stopped():
		ghostsiren.stop()
		body.frightened()
		eyes.hide_eyes()
		frightened_timer.start()
		is_blinking = false
		move_speed = 0.6
	current_state = GhostState.RUN_AWAY



func _on_frightened_timer_timeout():
	print("BACK TO CHASE")
	frightened_timeout.emit()
	is_blinking = false
	eyes.show_eyes()
	body.normal()
	chase()
	ghostsiren.play()
	frightenedghost.stop()
	move_speed = 0.9
	current_state = GhostState.CHASE

func get_eaten():
	if current_state != GhostState.EATEN:
		print("EATEN")
		ghosteaten.play()
		body.eaten()
		eyes.show_eyes()
		Global.score += 200
		frightened_timer.stop()
		frightenedghost.stop()
		frightened_timeout.emit()
		current_state = GhostState.EATEN
		$"200Points".show()
		await get_tree().create_timer(1).timeout
		$"200Points".hide()
		is_moving = true
		var eaten_path = astar_grid.get_id_path(
			tile_map.local_to_map(global_position),
			tile_map.local_to_map(gate_tile.global_position)
		)
		
		eaten_path.pop_front()
		move_speed = 5

func start_flashing():
	body.flashing()

func states():
	if current_state == GhostState.CHASE:
		chase()
	elif current_state == GhostState.RUN_AWAY:
		frightened_mode()
	elif current_state == GhostState.EATEN:
		get_eaten()

func start_chase_after_eaten():
	chase()
	body.normal()
	body.show()
	move_speed = 0.9

because current_state = GhostState.CHASE just before, this if statement will never be run.

In addition, pick_random() returns a random element from the array, it does no path finding for you. This seems like a bad use of the path array as it contains IDs of points to the player, not neighboring tiles.

oh, ok

but is there any other way for the enemy to move to random tiles?
@gertkeno

I’m not super familiar with tile maps, my first instinct is to pick a randomized position within the map bounds then clamp it to the navigatable region wth NavigationServer.map_get_closest_point, then navigate to that position.

alright

which function could pick a randomized position within the map bounds? any suggestions?

randi_range with your map bounds for the X and Y axis

understood, but what are map bounds exactly?

A unit in pixels, the size of the map / traversable area

oh okay

i figured out this line:
NavigationServer2D.map_get_closest_point(randi_range())
i looked up some bound methods and i picked these:
is_in_bounds and get_bounds

but neither dont work in randi_range() and it says that they arent defined in the current scope

You could look at the bounds of your map by checking the editor, how far does the map stretch in either direction? Write that down as the min and max values of randi_range, again this will have to be done for the X and Y axis.

var rand_x_pos: int = randi_range(-200, 200)
var rand_y_pos: int = randi_range(-300, 300)

These 200 and 300 values are just an example. You should be able to tell how big your map is, I figure if this is a pacman-like game the map is a fixed size.

If you do not know how to use NavigationServer2D.map_get_closest_point I would omit it and have the ghosts try to path to the random position, even if it’s within a wall. If an issue occurs then look into the navigation server function.

my map has tiles 8x8 pixels wide

btw idk how to check bounds, where is it in the editor?

“bounds” is the map size, there is pixel rulers on the top and left of the 2D viewport, you can estimate the map size by that grid.

alright

i used it and the map is 57 units and 456.0 pixels wide, 29 units and 232.0 pixels high
@gertkeno