Randomly Place Group Children & Avoid Platform Overlap?

I’m very new to Phaser and I’m working on a simple platformer to get comfortable with the basics. What I want is a group of 15 stars that the player can collect, and I want them to spawn at random xy coordinates. Each time the player collects all 15, the stars will reset to new xy coordinates. I’ve got it working pretty well, except some of the stars spawn in front of the platforms. I want them to only spawn where there’s no other tiles. Here’s a screenshot illustrating the problem:

I’ve tried various colliders, overlaps, I’ve tried adding a function for relocating any stars that overlap the platform layers, I’ve tried adding a function for removing stars that overlap, but none of those have worked. How can I make this work?
Here is my current code:
import stage1 from '/assets/scenes/stage1.js';

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 480,
	backgroundColor: '#007396',
	scale: {
		scale: 'SHOW_ALL',
		orientation: 'LANDSCAPE'
	},
	resolution: window.devicePixelRatio,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 500 },
			debug: true
        }
    },
	parent: 'gamewindow',
	scene: [stage1]
};

var game = new Phaser.Game(config);

export default class stage1 extends Phaser.Scene {
	constructor() {
		super({
			key: 'stage1'
		});
	}
	
	preload() {
		this.load.spritesheet('dude', '../assets/dude.png', 
			{
				frameWidth: 32,
				frameHeight: 48
			});
		this.load.image('star', '../assets/star.png');
		this.load.image('tileset', '../assets/area01_level_tiles.png');
		this.load.tilemapTiledJSON('map', '../area01.json');
	}
	
	create() {
		//establish scoreboard
		this.scoreText = this.add.text(16, 16, 'Score: ', {
			fontFamily: 'Rationale',
			fontSize: '50px',
			fill: '#fff'
		});
		this.scoreText.setText('Score: ' + 0);
		this.score = 0;
		
		const map = this.make.tilemap({
			key: 'map', //from load
			tileWidth: 32,
			tileHeight: 32						  
		});
		
		//establish level tileset
		const tileset = map.addTilesetImage('area01_level_tiles', 'tileset');
		
		//establish layers
		const backgroundLayer = map.createStaticLayer('background (go in front of)', tileset, 0, 0);
		const foregroundLayer = map.createStaticLayer('foreground (walk on)', tileset, 0, 0);
		
	
		foregroundLayer.setCollisionByExclusion(-1, true);
		
		//establish star group
		this.stars = this.physics.add.group({
			key: 'star',
			allowGravity: false,
			repeat: 14,
			//setXY: {x: 100, y: 0, stepX: 70}
		});
		
		this.stars.children.iterate(function(child){
			for (var i = 0; i < 13; i++){
				var xPos = Phaser.Math.Between(100, map.widthInPixels);
				var yPos = Phaser.Math.Between(0, 200);
				
				child.x = xPos;
				child.y = yPos;
				child.setOrigin(0,0);
				//this.child.setX(xPos);
				//this.child.setY(yPos);
			}
		});
		
		//add player
		this.dude = this.physics.add.sprite(20, 0, 'dude', 4); //the 4 tells phaser to start from the 4th frame
		this.dude.setBounce(0.2);
		
		//put the front layer declaration after adding player sprite so sprite renders between this and platform layer
		const frontLayer = map.createStaticLayer('front (go behind)', tileset, 0, 0);
		
		//add player-platform collisions
		this.dude.setCollideWorldBounds(true);
		this.physics.add.collider(this.dude, foregroundLayer);
		//set world bounds to follow camera bounds
		this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
		
		//add player collision with stars
		this.physics.add.overlap(this.dude, this.stars, this.getStar, null, this);
		
		//establish camera
		this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
		//establish camera follow
		this.cameras.main.startFollow(this.dude);
		
		//establish anim
		this.anims.create({
			key: 'left',
			frames: this.anims.generateFrameNumbers('dude', {start: 0, end: 3}),
			frameRate: 10,
			repeat: -1
		});
		
		this.anims.create({
			key: 'turn',
			frames: [{key: 'dude', frame: 4}],
			frameRate: 20
		});
		
		this.anims.create({
			key: 'right',
			frames: this.anims.generateFrameNumbers('dude', {start: 5, end: 8}),
			frameRate: 10,
			repeat: -1
		});
		
		//adding cursor keys
		this.cursors = this.input.keyboard.createCursorKeys();
		
	}
	
	update(time, delta) {
		this.runBoyRun();
	}
	
	runBoyRun() {
		if (this.cursors.left.isDown) {
			this.dude.setVelocityX(-160);
			this.dude.anims.play('left', true);
		} else if (this.cursors.right.isDown) {
			this.dude.setVelocityX(160);
			this.dude.anims.play('right', true);
		} else {
			this.dude.setVelocityX(0);
    		this.dude.anims.play('turn');
		}
		
		if (this.cursors.up.isDown && Phaser.Input.Keyboard.JustDown(this.cursors.up)) {
			this.dude.setVelocityY(-330);
		}
	}
	
	getStar(dude, star) {
		star.disableBody(true, true);
		this.score += 1;
		this.scoreText.setText('Score: ' + this.score);
		
		if (this.stars.countActive(true) === 0)
		{
			this.stars.children.iterate(function (child) {

				child.enableBody(true, child.x, child.y, true, true);

			});
		}			
	}
		
}

You can do overlap checks during create() as long as you call this.physics.world.step(0) first: https://codepen.io/samme/pen/RwwGQRR

But with tilemaps that’s more complicated because you would often have to move the sprite beyond several tiles until you find an edge.

It might be easier to use filterTiles() to find empty tiles, pick them at random, and place the stars there.

Another way is to draw rectangles on an object layer in Tiled and then use those for placement. And that way you can be sure the player can reach them.

Thanks for the reply, I think either filterTiles or the object layer option should help. I’m inclined to use filterTiles but I’m not quite sure how to implement it, are there any examples you could point me towards?

I don’t see it in the official examples but it would be something like

var emptyTiles = tilemapLayer.filterTiles(function (tile) {
  return tile.index === -1;
});

console.assert(emptyTiles.length, 'At least 1 empty tile');

// …

var randomTile = Phaser.Utils.Array.GetRandom(emptyTiles);
    
star.setPosition(randomTile.pixelX, randomTile.pixelY);
star.setOrigin(0, 0);

Thanks, this basically works, although it’s stacking all of the stars in one random spot, rather than assigning each one a random location. Here’s my current code, could you please take a look and perhaps tell me what I’m missing? I have this code in Create between the layer declarations and adding the player sprite:

var emptyTiles = foregroundLayer.filterTiles(function (tile) {
return tile.index === -1;
});
console.log(emptyTiles);
var randomTile = Phaser.Utils.Array.GetRandom(emptyTiles);
console.log(randomTile);

  this.stars = this.physics.add.group({
  	key: 'star',
  	repeat: 14,
  	allowGravity: false,
  		//immovable: true
  });
  
  this.stars.children.iterate(function(child){
  	for(var i=0; i<14; i++){
  		child.setPosition(randomTile.pixelX, randomTile.pixelY);
  		child.setOrigin(0,0);
  		console.log('shiny star');
  	}
  });

You just need to do GetRandom() once for each star:

var emptyTiles = foregroundLayer.filterTiles(function (tile) {
  return tile.index === -1;
});

console.log(emptyTiles);

this.stars = this.physics.add.group({
  key: 'star',
  repeat: 14,
  allowGravity: false
  // immovable: true
});

this.stars.children.iterate(function (child) {
  var randomTile = Phaser.Utils.Array.GetRandom(emptyTiles);

  console.log(randomTile);

  child.setPosition(randomTile.pixelX, randomTile.pixelY);
  child.setOrigin(0, 0);

  console.log('shiny star', star.x, star.y);
});

That worked, thank you!

One more question, if you would: this solution works for each new pageload, but when the player collects all of the stars and getStar() respawns them, they reappear in the same place unless I reload the page. Is there a way I can rewrite getStars() so that the stars respawn in different locations each time the player collects all of them? I have tried passing various arguments into getStar() but I seem to be doing it incorrectly.

You can extract that into a new method.

Also there was a mistake in my earlier post: there was no need for that extra for loop inside iterate().

export default class stage1 extends Phaser.Scene {
  // Abbreviated
  
  create() {
    // …
    this.emptyTiles = foregroundLayer.filterTiles(…);

    this.stars = this.physics.add.group(…);

    this.shuffleStars();
    // …
  }

  getStar(dude, star) {
    // …
    if (this.stars.countActive(true) === 0) {
      this.shuffleStars();
    }
  }
  
  shuffleStars() {
    var emptyTiles = this.emptyTiles;

    this.stars.children.iterate(function (child) {
      var randomTile = Phaser.Utils.Array.GetRandom(emptyTiles);
      // …
    });
  }
}

Thank you so much for all your help!