0
\$\begingroup\$

I am trying to make a game using Godot 4.5 where enemies spawn off-screen, but I faced this bug where they spawn on-screen instead:

Output screen. The white squares are the spawned enemies and the red is the player

The white squares are the spawned enemies and the red is the player.

Here's the minimal reproducible example:

extends Node2D @export var root: Node2D @export var max_entities: int @export var spawn_spots: PackedVector2Array var _entities: Array[ColorRect] static func spawn_indices_generator(indices_to_append_to: Array, is_y: bool = true, counter: float = 11, limit: float = 1500, step: float = 8): while counter < (DisplayServer.window_get_size().y if is_y else DisplayServer.window_get_size().x) + limit: indices_to_append_to.append(counter) counter += step static func va_pick_random(arr: Variant): return arr[randi() % arr.size()] func external_spawn(): return spawn(va_pick_random(spawn_spots) + %player.position) func spawn(pos: Vector2): var node = preload("res://dummy.tscn").instantiate() as ColorRect %ui.call_deferred("add_child", node) node.position = pos return node func _ready() -> void: var arr_x: Array[float] var arr_y: Array[float] spawn_indices_generator(arr_x, false, -DisplayServer.window_get_size().x, DisplayServer.window_get_size().x, 1) spawn_indices_generator(arr_y, true, -DisplayServer.window_get_size().y, DisplayServer.window_get_size().y, 1) var i := 0 while i < min(arr_x.size(), arr_y.size()): var vector := Vector2(arr_x[i], arr_y[i]) if not Rect2( Vector2.ZERO, Vector2( DisplayServer.window_get_size() ) ).has_point(vector): spawn_spots.append(vector) i += 1 print(spawn_spots) for _j in range(10): external_spawn() external_spawn() external_spawn() 

Scene tree:

  • ┖╴root
    • ┠╴ui
    • ┠╴spawner (code is on above)
    • ┖╴player
      • ┖╴ColorRect

I cannot change the spawn_indices_array() because I extracted it from some part of my code, and I fear it will break things that are not intended here. I need resolution-independent code.

My original code was (you can ignore this):

class_name VirusSpawner extends EntitySpawner func external_spawn() -> Virus: print("Accepted") return spawn( Virus.VirusInfo.new( AlgebraicMath.Objectal.va_pick_random(spawn_spots) + player.position, #Position root, # Root Scene node player, # Player 5 # Health ) ) func spawn(info) -> Virus: var v = preload("res://scenes/virus.scn").instantiate() as Virus root.call_deferred("add_child", v) v.change(info) return v func _ready() -> void: var arr_x: Array[float] var arr_y: Array[float] AlgebraicMath.Objectal.spawn_indices_generator(arr_x, false, -DisplayServer.window_get_size().x, DisplayServer.window_get_size().x, 1) AlgebraicMath.Objectal.spawn_indices_generator(arr_y, true, -DisplayServer.window_get_size().y, DisplayServer.window_get_size().y, 1) var i := 0 while i < min(arr_x.size(), arr_y.size()): var vector := Vector2(arr_x[i], arr_y[i]) if not Rect2( Vector2.ZERO, Vector2( DisplayServer.window_get_size() ) ).has_point(vector): spawn_spots.append(vector) i += 1 print(spawn_spots) for _j in range(10): external_spawn() external_spawn() external_spawn() 
New contributor
Jyle is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
0

1 Answer 1

0
\$\begingroup\$

You are looping through only good screen points diagonally. This is a very non performant way of doing this, you are storing mostly all on-screen point. Get a list of points padded outside the viewable screen area, lets assume 1920x1080

extends Node2D @export var root: Node2D @export var max_entities := 20 @export var spawn_spots: Array[Vector2] = [] var _entities: Array[ColorRect] func _ready(): generate_spawn_spots(200) # padding for i in range(10): external_spawn() func generate_spawn_spots(padding := 200): spawn_spots.clear() var size = DisplayServer.window_get_size() # Top region for x in range(size.x): spawn_spots.append(Vector2(x, -padding)) # Bottom region for x in range(size.x): spawn_spots.append(Vector2(x, size.y + padding)) # Left region for y in range(size.y): spawn_spots.append(Vector2(-padding, y)) # Right region for y in range(size.y): spawn_spots.append(Vector2(size.x + padding, y)) print("Generated:", spawn_spots.size(), " offscreen points") func external_spawn(): return spawn(spawn_spots.pick_random() + %player.position) func spawn(pos: Vector2): var node := preload("res://dummy.tscn").instantiate() %ui.call_deferred("add_child", node) node.position = pos return node 
New contributor
Windex is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
5
  • \$\begingroup\$ The padding is how many pixels you want to look outside the screen resolution. If youw ant them to spawn 100 pixels from a valid screen position then use 100 for the padding \$\endgroup\$ Commented Nov 20 at 0:07
  • \$\begingroup\$ You are we are checking the window size before checking for 'padded' points: var size = DisplayServer.window_get_size() The "regions" are locations that are the padding size away from that side of the screen so top region with give you points that are 200 pixels above the screen, left region.. 200 pixels to the lef side, outside of the main window bounds of 'size'. \$\endgroup\$ Commented Nov 20 at 19:28
  • \$\begingroup\$ The padding is up to you. its just how far the maximum outside the screen bounds you'd like things to spawn.. Set a smaller padding for less spawn points, and spawn points closer to the edge of the screen. Pay close attention to how the padding works, if your displayServer.window_get_size() was 800x600 (just an example) and the padding was 200, looking at the 'top region' function spawn_spots.append(Vector2(x, -padding)) you would get points like (0,-200) or (799,-200), since in godot y increases downward the negative value would be at the top of the screen (and off the screen) \$\endgroup\$ Commented Nov 21 at 2:58
  • \$\begingroup\$ @Jyle - percentages are resolution independent, just calculate some percentage of the screen size (either width or height). The pixel length that is based on a percentage will be bigger if the resolution is bigger. You'll also probably have to scale your characters when drawing them, and account for different screen aspect rations. What you could do is invent some system of units - say the shorter side of the screen is 100 units in your game world (so 1 unit is screen_size_in_pixels / 100.0), then convert between those and actual pixels when drawing (multiply your generic units by that number \$\endgroup\$ Commented 2 days ago
  • \$\begingroup\$ I used size.x for padding but im unsure if it's safe because maybe it can happen 1 in a billion. it can be hard to test sometimes. \$\endgroup\$ Commented 2 days ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.