Position Chosen being weird and not being where i want it to be D:

For my enemy spawning I have a plane mesh and a script to chose a position somewhere on it. I use that function to spawn enemies and set them to that position. The issue is they are spawning at normal X and Z positions but not Y. Here’s the relevant code:

extends MeshInstance3D

class_name SpawnLocation


var TOP_RIGHT_POS : Vector3
var TOP_LEFT_POS : Vector3
var BOTTOM_LEFT_POS : Vector3
var BOTTOM_RIGHT_POS : Vector3

func _ready() -> void:
	visible = false
	TOP_RIGHT_POS = get_corner_pos(Vector2i(1, 1))
	TOP_LEFT_POS = get_corner_pos(Vector2i(-1, 1))
	BOTTOM_LEFT_POS =get_corner_pos(Vector2i(-1, -1))
	BOTTOM_RIGHT_POS = get_corner_pos(Vector2i(1, -1))

func get_corner_pos(smth : Vector2i) -> Vector3:
	var plane : PlaneMesh = mesh
	return Vector3((global_position.x) + (((plane.size.x/2) * scale.x) * smth.x), global_position.y, (((plane.size.y/2) * scale.z) * smth.y))
## Returns a spawn location in the order: top right, top left, bottom left, bottom right.
func get_spawn_location() -> Array[Vector3]:
	return [
		TOP_RIGHT_POS,
		TOP_LEFT_POS,
		BOTTOM_LEFT_POS,
		BOTTOM_RIGHT_POS,
		]

## Returns a random spawn location somewhere on the plane
func random_spawn_location() -> Vector3:
	var x_pos : float = randf_range(TOP_RIGHT_POS.x, TOP_LEFT_POS.x)
	var z_pos : float = randf_range(TOP_RIGHT_POS.z, BOTTOM_RIGHT_POS.z)
	print("spawn location thing" + str(Vector3(x_pos, global_position.y, z_pos)))
	return Vector3(x_pos, global_position.y, z_pos)

extends Node

class_name spawner

var flyingenemy := preload("res://scenes/enemies/enemy.tscn")
var explosiveguy := preload("res://scenes/enemies/explosive_guy.tscn")
var golem := preload("res://scenes/enemies/golem.tscn")
var bomber := preload("res://scenes/enemies/bomber.tscn")
var basicenemy := preload("res://scenes/enemies/basic_enemy.tscn")
var turret := preload("res://scenes/enemies/turret.tscn")

@export var spawning_locations: Array[SpawnLocation]

@export var Enemies : Array[PackedScene] = [
flyingenemy,
explosiveguy,
golem,
bomber,
basicenemy,
turret,
]

func createenemies() -> void:
	if Enemies.size() > 0:
		for i in range(randi_range(2, 4)):
			var enemyIndex = randi_range(0, Enemies.size() - 1)
			while  enemyIndex == 0 && has_node("enemy"):
				enemyIndex = randi_range(0, Enemies.size() - 1)
			spawnEnemy(enemyIndex)
			if Enemies[enemyIndex] == turret:
				i -= 1

func spawnEnemy(enemyIndex: int):
	var location_thing = spawning_locations[randi_range(0, spawning_locations.size() - 1)]
	
	if Enemies[enemyIndex] == flyingenemy:
		var enemySpawn : Node3D = Enemies[enemyIndex].instantiate()
		add_child(enemySpawn)
		enemySpawn.global_position = location_thing.random_spawn_location()

	elif Enemies[enemyIndex] == explosiveguy:
		var enemySpawn : Node3D = Enemies[enemyIndex].instantiate()
		add_child(enemySpawn)
		enemySpawn.global_position = location_thing.random_spawn_location()

	elif Enemies[enemyIndex] == golem:
		var position_chosen : Vector3 = location_thing.random_spawn_location()
		for i in range(randi_range(1, 3)):
			var enemySpawn : Node3D = Enemies[enemyIndex].instantiate()
			add_child(enemySpawn)
			enemySpawn.global_position = position_chosen + Vector3(randf_range(-7, 7), 0, randf_range(-7, 7))

	elif Enemies[enemyIndex] == bomber:
		var enemySpawn : Node3D = Enemies[enemyIndex].instantiate()
		add_child(enemySpawn)
		enemySpawn.global_position = location_thing.random_spawn_location()

	elif Enemies[enemyIndex] == basicenemy:
		var position_chosen : Vector3 = location_thing.random_spawn_location()
		for i in range(randi_range(3, 6)):
			var enemySpawn : Node3D = Enemies[enemyIndex].instantiate()
			add_child(enemySpawn)
			enemySpawn.global_position = position_chosen + Vector3(randf_range(-7, 7), 0, randf_range(-7, 7))

	elif Enemies[enemyIndex] == turret:
		var enemySpawn : Node3D = Enemies[enemyIndex].instantiate()
		add_child(enemySpawn)
		enemySpawn.global_position = location_thing.random_spawn_location()

Basic enemy is what I’m testing with. When printing the position of the enemy’s global position after spawning it, it seems to be normal. This is the only relevant thing that happenes in the enemie’s ready function:

	process_mode = Node.PROCESS_MODE_DISABLED
	hide()
	await get_tree().process_frame
	if gamemanager.dungeon_main != null:
		gamemanager.dungeon_main.connect('entered_room', actually_spawn)

Sorry if this is to much info, if nobody can find anything I’ll try to narrow it down more. I just want to see if somebody knows a glaring issue with something

func _ready() -> void:
    print("hello world")

Looks like the y position is determined by the global_position of your MeshInstance3D object. If you move it up, the spawned things should move up correspondingly.

Yes, moving up the spawner does make the enemies move up. I think some math somewhere is wrong because moving the room away from the world origin causes the issue.

So, I enforced static typing and that caused issues of refactoring everything but I finally got it working with the following spawn position code:

func random_spawn_location() -> Vector3:
	var plane : PlaneMesh = mesh
	var x_pos : float = randf_range(global_position.x - (plane.size.x / 2), global_position.x + (plane.size.x / 2))
	var z_pos : float = randf_range(global_position.z - (plane.size.y / 2), global_position.z + (plane.size.y / 2))
	print("spawn location thing" + str(Vector3(x_pos, global_position.y, z_pos)))
	return Vector3(x_pos, global_position.y, z_pos)

So after more rigorous testing later, It’s become obvious that it doesn’t work : (

I’ve had things spawning outside of walls which is not ideal since you need to defeat all the enemies to progress in the game.
This is the spawn mesh in use inside of a room where things semi frequently spawn out of bounds.

Either the code can generate an position that’s incorrect or something awkward happens because of the following code:

#start is supered during ready
func start() -> void:
	process_mode = Node.PROCESS_MODE_DISABLED
	hide()
	await get_tree().process_frame
	if gamemanager.dungeon_main != null:
		gamemanager.dungeon_main.connect('entered_room', actually_spawn)
func actually_spawn() -> void:
	process_mode = Node.PROCESS_MODE_PAUSABLE
	show()
	spawned = true

I had this problem in a game jam game a couple years ago. Things were spawning outside the room. So I just defined a length and width variable that was slightly smaller than the size of the room.

extends Node3D

signal level_complete

@export var x_width: float = 1.0
@export var z_length: float = 1.0
@export var small_barrels: int
@export var small_boxes: int
@export var large_barrels: int
@export var large_boxes: int
@export var barrel_stacks: int
@export var small_barrel: PackedScene
@export var small_box: PackedScene
@export var large_barrel: PackedScene
@export var large_box: PackedScene
@export var barrel_stack: PackedScene

var rng = RandomNumberGenerator.new()
var level_started = false

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	print_rich("x/z(%s,%s)" % [x_width, z_length])
	propogate(small_barrels, small_barrel)
	propogate(small_boxes, small_box)
	propogate(large_barrels, large_barrel)
	propogate(large_boxes, large_box)
	propogate(barrel_stacks, barrel_stack)
	level_started = true


func propogate(number: int, scene: PackedScene):
	for i in number:
		spawn(scene)


func spawn(destructible: PackedScene) -> void:
	var destructible_copy = destructible.instantiate()
	destructible_copy.position.x = rng.randf_range(-x_width, x_width)
	destructible_copy.position.z = rng.randf_range(-z_length, z_length)
	add_child(destructible_copy)
	destructible_copy.death.connect(_on_item_destroyed)
	Stats.connect_death(destructible_copy)
	destructible_copy.loot_dropped.connect(_on_loot_dropped)


func _on_loot_dropped(loot):
	loot.death.connect(_on_item_destroyed)


func _on_item_destroyed(_arg1 = null):
	var destructibles = get_tree().get_node_count_in_group("destructible")
	var pickups = get_tree().get_node_count_in_group("pickup")
	if destructibles + pickups <= 1:
		level_complete.emit()
		level_started = false

I’d handle it a lot differently now, but I think it was my first or second game jam. Anyway, point is, that worked. I’d recommend expanding your random generation targeting code.

	var x_pos : float = randf_range(global_position.x - (plane.size.x / 2), global_position.x + (plane.size.x / 2))

You’re doing a lot of math in there. So assign each step to a variable, like showing your work in math class. Giving each variable a descriptive name will help you figure out if you’re doing what you think you’re doing.

In my code, I knew the dungeon was centered on (0, 0), so I didn’t have to worry about the global location. What I would do is randomly generate the location inside the dungeon, then try setting the monster’s location instead of global location, and then set it as a child of the dungeon. Then you shouldn’t have to shift the values you’re generating.

So what’s the problem/goal here then? You want things to spawn at random points on a plane mesh?

Yeah, I want enemies to spawn at random locations so I wanted to create a plane mesh that has a function to choose a random position somewhere on top of it

The screenshot I added was of the white plane and it’s distance from the wall, it’s pretty slim, about two meters away from the wall I believe but I’ll check in a moment

That’s doable in one line of code… almost.

  • get plane’s aabb
  • find a random xz that is inside aabb, set y to whatever height
  • transform that position into global space by multiplying by plane’s global transform

Note that this will work even if you rotate the plane. The whole thing can also be easily extended to spawn inside a volume, by adding half a line worth of code :wink:

1775058115385758990678382727548

There (on a rotated plane for show):

spawn

@tool 
extends MeshInstance3D

@export_tool_button("SPAWN") var spawn_button = spawn.bind(load("res://spawned_scene.tscn"))

func spawn(scene: PackedScene) -> void:
	var spawn_instance: Node = scene.instantiate()
	EditorInterface.get_edited_scene_root().add_child(spawn_instance)
	spawn_instance.owner = spawn_instance.get_parent()

	for i in 3:
		spawn_instance.global_position[i] = randf_range(get_aabb().position[i], get_aabb().end[i])
	spawn_instance.global_position = global_transform * spawn_instance.global_position