How to send a single signal from the process function?

Godot Version

4.4.1

What I’m trying to do

I have a grid of tiles that slowly goes down by adding 5 pixels to global_position.y
Every time the grid has descended the size of a tile (which is 16 pixels), I want to have a signal that tells the spawner to add a new row of tiles at the top, so that it seems like an endless amount of tiles falling down.

I have a specific scene to signal this, the new_row_signaller. This scene moves down at the same speed as the grid, and when it’s down more or equal to 16, it emits the spawn_new_row signal.

This is all the code in the new_row_signaller:

extends Node2D

var start_position = global_position.y
var current_position = global_position.y
var moving = true
var difference = 0
## This thing goes down to the height of 1 block 
## and then resets itself
## emitting a spawn_new_row signal

func _ready() -> void:
	start_position = global_position.y


func _physics_process(delta: float) -> void:
	if moving:
		global_position.y += GameVars.block_move_speed * delta
	
	current_position = global_position.y
	
	difference = current_position - start_position
	
	if difference >= GameVars.TILE_SIZE:
		SignalManager.spawn_new_row.emit()
		global_position.y = start_position

The problem

The problem is, that the emitted signal is never at exact 16 pixels. It always overshoots a bit, which means that the new_row_signaller is not in sync with the grid after the first spawn, and this gap only increases with every signal and reset. Eventually, a row gets skipped.

Where the solution might be

The new_row_signaller is something that I still had of a previous try at this system. Now I’m working with TileMapLayers that are in a Node2D and the whole Node2D is moving down.
That means that global_position.y is always increasing and I can take my signals from there.

I have two possible solutions, but I don’t know how to implement them correctly.

  1. Using the remainder. When the remainder is on 0, I know I have to spawn in a new row.
  2. Using simply dividing, and then using the int of that. So int(simply_dividing), which gives me an integer that slowly grows as the grid moves down. And every time it changes, there could be a signal that tells to spawn a new row.
func _process(delta: float) -> void:
	remainder_y = int(global_position.y) % 16
	simply_dividing = global_position.y / 16

func _physics_process(delta: float) -> void:
	if moving:
		global_position.y += GameVars.block_move_speed * delta

func spawn_new_row():
	for x in grid_width:
		var pos = Vector2i(x, -simply_dividing)
		# First get a base tile
		set_base_tile(pos, "dirt")

Question

Because I check these solution in the _process function, there are many frames where the remainder is 0 or by dividing the integer stays on the same number.
So, while using any of the possible solutions above, I get many, many ‘hits’ which would send many signals.

Now here comes the question.

  1. The remainder way
    How do I create a function that checks if the remainder is on 0 and then emits only once that a new row needs to be spawned?

  2. The dividing way
    How do I create a function that checks if the integer just changed and then emits only once that a new row needs to be spawned?

The main problem i see is that 2d positions allows for subpixel movement and since you are using the delta time to move, youre going to have a fractional error that requires awareness of where the current tile is relative to its original origin. Otherwise the tiles may drift from the origin if they rely on the moving tile to signal when ~16 pixels, or more, has elapsed.

I dont think either approach is correct as you need to butt the new tile against the existing tile.

func spawn_new_row():
	for x in grid_width:
# this may introduce drift as the current position may not be
# exactly 16 pixels away from the start but there will be no gap.
		var pos = Vector2i(x,  global_position.y - 16) 
		# First get a base tile
		set_base_tile(pos, "dirt")

I want to let you find your own way, but i also provided a alternative solution that i have hidden.

alternative solution

I would suggest a third way. That is, a tile spawner. Its soul purpose is to create a tile, that moves on its own, and save a reference to the tile it spawned, monitoring its position. Once the tile is 16 pixels or greater you place the next tile exactly 16 pixels behind the current tile and update the tile reference to monitor the new tile. Repeating this indefinitely. ( You may want something to clear a tile once its off screen ) .

By introducing the observer spawner system you can prevent the drift by keeping track of the last spawned tile position relative to the spawn origin.

1 Like

I leaned into the coroutines late. And, moved them to the Ready function.
But, it doesn’t stop there.
I don’t know if this helps.
But, the kids code coroutines with last to live line;

return set_base_tile(pos, “dirt”)