How to loop a method correctly

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Blissful

Hey everyone!

I’m attempting to make a mechanic where the player (and other things) can fracture a tile. That fractured tile will check the nearby tiles, and if any of those are already fractured, they will break and fracture will travel—checking every tile around those tiles, and so on.

That’s where my issues lies. I have difficulty looping this without the program freezing.

Example code:

func set_tile_effect(tile, effect_name) -> void:

grid[tile.x][tile.y][1] = effect_name

match effect_name:
	"GROUND":
		pass
	"FRACTURE":

		set_cellv(tile, tile_frac)
		
		# we check the surrounding tiles four (no diagonals)
		var spread = reachable_tiles_mapped(tile, 1)
		
		# remove the first member, since it is the orig tile
		spread.pop_front()

		# check if these tiles are already frac'ed and append them
		for available_tile in spread:
			var tile_state = check_tile_state(available_tile)
			if tile_state == "FRACTURE":
				if not spreadable_tiles.has(available_tile):
					spreadable_tiles.append(available_tile)

So this is the basic mechanic. My problem lies in what to do after appending those tiles.
Is there a way I can run this method again on each of the tiles we’ve stored?
So that it checks the exact same logic for each tile, and continuing until there are no more tiles to spread to.

I hope this is enough, but if more info is needed feel free to comment.

:bust_in_silhouette: Reply From: njamster

I hope this is enough [information]

Well, it would certainly help to know when and where set_tile_effect is called, what the reachable_tiles_mapped- or check_tile_state-methods do or if you’re only checking the direct neighbor cells or consider diagonal neighbors ones as well.

That being said, as long as you don’t keep a list of visited tiles you’ll always end up with an endless loop and your game freezing. Here’s an example for direct neighbors:

extends TileMap

const directions = [
		Vector2.UP,
		Vector2.RIGHT,
		Vector2.DOWN,
		Vector2.LEFT
]

# your id's might differ
const BROKEN_TILE_ID = -1
const FRACTURED_TILE_ID = 1

var cells_to_check = []
var cells_already_visited = []
   
func _ready():
	var cell = Vector2(2, 2)
	fracture_tile(cell)
    
func fracture_tile(cell : Vector2):
	set_cellv(cell, FRACTURED_TILE_ID)
	cells_already_visited.push_back(cell)

	spread_to_neighbors(cell)

	while not cells_to_check.empty():
		spread_fracture(cells_to_check.pop_front())

	cells_already_visited = [] # reset in the end
    
func spread_to_neighbors(cell : Vector2):
	for direction in directions:
		var neighbor_cell = cell + direction
		if not neighbor_cell in cells_already_visited:
			cells_to_check.push_back(neighbor_cell)
    
func spread_fracture(cell : Vector2):
	if get_cellv(cell) == FRACTURED_TILE_ID:
		set_cellv(cell, BROKEN_TILE_ID)
		spread_to_neighbors(cell)

Hey Njamster!

I will have to give this code a try! I had come to my own … less than efficient solution in the mean time.

What I got to was this:

func set_tile_effect(map_pos, effect_name) -> void:

grid[map_pos.x][map_pos.y][1] = effect_name

match effect_name:
	"GROUND":
		pass
	"FRACTURE":
		set_cellv(map_pos, tile_type.FRACTURE)

		check_surrounding_tiles(map_pos, 1, "FRACTURE")

So that was very straight forward, just check the effect name given when the method is called and check those again the two (and later more) effects.

Then I call check_surrounding_tiles(), which is how I tried to append things. It … works actually but looking at yours, this is probably far from the best way of resolving this.

func check_surrounding_tiles(tile, reach, tile_eff) -> void:
# we check the surrounding tiles
var spread = reachable_tiles_mapped(tile, reach)

# remove the first member, since it is the orig tile
spread.pop_front()

# check if these tiles are already frac'ed and append them
for available_tile in spread:
	var tile_state = check_tile_state(available_tile)
	if tile_state == tile_eff:
		if not spreadable_tiles.has(available_tile):
			spreadable_tiles.append(available_tile)


for available_tile in spreadable_tiles:
	
	spread = reachable_tiles_mapped(available_tile, reach)
	
	for i in spread:
		var tile_state = check_tile_state(i)
		if tile_state == tile_eff:
			if not spreadable_tiles.has(i):
				spreadable_tiles.append(i)

set_multi_tile_effect(spreadable_tiles, tile_eff)

And then check_surrounding_tiles() calls set_multi_tile_effect which is nearly a carbon copy of the first method set_tile_effect() but one that allows looping and destroys the tiles instead of fracturing them.

func set_multi_tile_effect(tiles, effect_name) -> void:
for tile in tiles:
	
	grid[tile.x][tile.y][1] = effect_name
	
	match effect_name:
		"GROUND":
			pass
		"FRACTURE":
			remove_tile(tile)

result can be seen here:
https://media.giphy.com/media/RNQRRtR4YiFUFlgQ1S/giphy.gif

I’ve run into an entirely new problem, which has to do with my grid array. When I try to target tiles that sit along the left bottom or upper right edge the program crashes saying that my array doesn’t hold a certain number.
My grid code must have some hilarious mistakes in it because it works different based on if my playing field is set on top of the origin point in godot, or to the right of it, or even further to the right and down slightly.

I thought the tilemap methods (and my own) only considered the tilemap as it stood, not it’s location relative to the origin point. Put for some reason it messes greatly with the arrays I generate.

Anyway that’s a totally different problem, it only popped up when I ‘‘fixed’’ my fracturing problem.
Thanks for the help again :slight_smile:

Blissful | 2020-07-21 07:05