So, I coded this 2D game called "Coin Dash" where a fox has to loot coins and powerups to prevent a timer from going out, leveling up when there is no more loot and increasing coins at each level. There are also two cactus obstacles on the map that are deadly if touched.

For that to work, we should prevent Coins (Area2D Nodes) from spawning inside the obstacles, thus being unreachable. So the coins connect to the signal _on_area_entered:

func _on_area_entered(area):
	if area.is_in_group("obstacles"):
		position = Vector2(
			randi_range(20, screensize.x - 20),
			randi_range(20, screensize.y - 20))

As the cactus nodes are in a group called obstacles, they get identified as such, and the coin spawned incorrectly (inside the cactus area) gets repositioned randomly on the map. So apparently this code works sometimes, but eventually the coins are spawned on the obstacles. Let's spawn 1000 coins just to see it happening:

Success! No coin spawned incorrectly. (First attempt)

Let's restart the game... and there it is!

    Canichim If your randomizer positions the coin inside the same area again, the signal area_entered won't be emitted because the coin is already inside the area.

      Canichim From where do you call the function that does the coin spawning?

      Also, you could do it a different way, just as a suggestion:

      • When trying to spawn a coin roll a random position.
      • Check if that position would be inside the dimensions of an obstacle.
      • If it would intersect then try a different position.
      • Only once you found a position actually spawn your coin.

        Toxe from main script, inside new_game function when start button is pressed, and in _process function if it confirms that coins group is empty. I'll try this different approach.

        I came up with a simple solution of 3 added lines.

        func _on_area_entered(area):
        	if area.is_in_group("obstacles"):
        		position = Vector2.ZERO
        		
        		
        func _on_area_exited(area):
        	if area.is_in_group("obstacles"):
        		position = Vector2(
        			randi_range(20, screensize.x - 20),
        			randi_range(20, screensize.y - 20))

        I force the coin to exit the area moving it to Vector2.ZERO, triggering an on_area_exited call that repositions it (instead of repositioning inside on_area_entered), and if it goes wrong again, it will call _on_area_entered again, and continue this cycle until everything is right. I failed to implement other solutions.

        • xyz replied to this.

          Canichim Although this may appear to work, it's not really a "bulletproof" way to do it. It scales badly. If you have a lot of obstacles and many coins to place, the random number generator could theoretically output a long sequence of coordinates that all hit some obstacle, causing a stutter. It also doesn't prevent placement of many coins onto near positions, causing ugly overlaps and clumps, which can affect gameplay.

          This kind of thing is typically done as follows.
          at the start of level:

          • generate a regular grid of coordinates that cover the whole area and store them in an array.
          • nudge each coordinate a bit if you don't want the arrangement to look tiled.
          • remove from the array all coordinates that are inside obstacles.
          • random-shuffle the array.

          at each coin generation:

          • pop a coordinate from the beginning of the array, and spawn a coin at that coordinate.
          • push the coordinate to the back of the array.

          The array can be re-shuffled after all coordinates are cycled, or just occasionally after N coins have been generated.