How to spawn collectables or pickups in patterns and at random positions using Phaser 3

Viewed 182

I am Making a 2d endless runner using Phaser 3 and I need to spawn coins and other pickups in random positions and also in different patterns such as diamond shape, square,... I don't really have much code and I do not know how to go about it. I would gladly appreciate any help in any way. Thank you.

1 Answers

It depends on your code-structure, but I would use the builtin function Phaser.Math.Between, to generate random positions/numbers (link to the documentation).

(Sidenote: The first examples are made for arcade physics, the final one with is made for matter.js )

Here is a very simple approach:

In code:

  • The interval in which coins are spawned is random
    setTimeout(_ => this.loadCoins(coins), Phaser.Math.Between(2500, 3000));
  • The position of the pickup is random
    let yCoord = Phaser.Math.Between(20, 180);
    let xCord = 400 + Phaser.Math.Between(0, 100);
  • The pickup type is random
    let coinType = Phaser.Math.Between(0, 2);
  • and te amount of coins to spawn
    let coinsToSpawn = Phaser.Math.Between(1, 5);

class GameScene extends Phaser.Scene {
    constructor() {
      super({ key: 'GameScene' });
    }
    
    loadCoins(coins){
       let coin;
       
       // Generate random amount of coins each time
       let coinsToSpawn = Phaser.Math.Between(1, 5);
       
       for(let i = 0; i < coinsToSpawn; i++){
         
          // Get Random y position (x is always bigger than the scene width)
          let yCoord = Phaser.Math.Between(20, 180);
          let xCord = 400 + Phaser.Math.Between(0, 100);

          // Randomly generate types
          let coinType = Phaser.Math.Between(0, 2);
          
          switch(coinType){
            case 0:
              coin = this.add.rectangle(xCord, yCoord, 15, 15, 0xFFFF00);
              break;
            case 1:
              coin = this.add.circle(xCord, yCoord, 7, 0xFF0000);
              break;
            case 2:
              coin = this.add.star(xCord, yCoord, 5, 5, 15, 0x00FF00);
              break;
          }
       
            coin = this.physics.add.existing(coin);
            coins.add(coin);
        }  
        coins.setVelocityX(-100); 
        
        // Start next Coin loading randomly in 2.5 - 3 Seconds
        setTimeout(_ => this.loadCoins(coins), Phaser.Math.Between(2500, 3000)); 
    }
    
    create() {
       
        this.player = this.add.rectangle(200, 100, 20, 20, 0xffffff);
        this.physics.add.existing(this.player);

        //Add World Physics
        this.physics.world.setBounds(0, 0, 400, 200);
        this.player.body.setCollideWorldBounds(true);
        this.player.body.setImmovable(true);

        let coins = this.physics.add.group({immovable: true, allowGravity: false});
        
        
        this.loadCoins(coins);
        
        this.physics.add.collider(this.player, coins, 
          (player, coin) => { 
            coin.destroy();
        });
  }
}

const config = {
    type: Phaser.AUTO,
    width: 400,
    height: 200,
    scene: [ GameScene ],
    physics: {
       default: 'arcade',
    }
};

const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js"></script>

Update (create shapes with coins):

-> Check out some cool builtin functions in the Phaser.Actions Namespace (like to the documentation). like _(to name a few)):

  • Phaser.Actions.PlaceOnCircle
  • Phaser.Actions.PlaceOnLine
  • Phaser.Actions.PlaceOnTriangle
  • ...

Disclaimer: This code is not optimal is just created like this to proof the point.

UPDATE for spawning:

Sidenotes:

  1. the spawn has to be trigger, so I use setInterval, but you can use events, user input, or simply in the update function, or ...
  2. the cleanup and the saving of the groups could be handled better, but it's a demo.

class GameScene extends Phaser.Scene {
    constructor() {
        super({ key: 'GameScene' });

        //keep reference to the groups
        this.coinGroups = [];
    }
    
    spawnCoins(){

       let coins = this.physics.add.group({immovable: true, allowGravity: false});
        
       var circle = new Phaser.Geom.Circle(440, 80, 40);
       for(let i = 0; i < 10; i++){
            let coin = this.add.circle(0, 0, 8, 0xFFFF00);
            coin = this.physics.add.existing(coin);
            coins.add(coin);
        }  
        coins.setVelocityX(-100); 

        this.coinGroups.push(coins);
        
        Phaser.Actions.PlaceOnCircle(coins.getChildren(), circle);
    }
    
    create() {
        this.add.text(10,10,'Spawing every 2sec')
            .setColor('#ffffff');

        // Spawing ever 2 Sec
        setInterval( _ => {
            this.spawnCoins();
        }, 2000);
    }

    update(){
        // Minor Cleanup
        for(let group of this.coinGroups){
            group.getChildren().forEach(child => {
                if(child.x < 0){
                    group.remove(child, true, true);
                }
            });
        }
        this.coinGroups = this.coinGroups.filter(group => group.length > 0 );  
    }
}

const config = {
    type: Phaser.AUTO,
    width: 400,
    height: 200,
    scene: [ GameScene ],
    physics: {
       default: 'arcade',
    }
};

const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js"></script>

Mini demo with matter.js:

class GameScene extends Phaser.Scene {
    constructor() {
        super({ key: 'GameScene' });
        //keep reference to the groups
        this.coinGroups = [];
    }
    
    spawnCoins(){

       // standart Phaser Group
       let coins = this.add.group();
        
       var circle = new Phaser.Geom.Circle(440, 80, 40);
       for(let i = 0; i < 10; i++){
            let coin = this.matter.add.image(50, 50, 'coin').setOrigin(.5);
            coin.setIgnoreGravity(true);
            coin.setVelocityX(-3); 
            coin.setFrictionAir(0);
            coins.add(coin);
        }  

        this.coinGroups.push(coins);
        
        Phaser.Actions.PlaceOnCircle(
            coins.getChildren(), circle);
    }
    
    create() {
         this.add.text(10, 10, 'Coins spawned every second')
         .setOrigin(0)
         .setColor('#ffffff');
         
         // Just creating a texture/image for matter
         let g = this.make.graphics({x: 0, y: 0, add: false});
         g.fillStyle(0xffff00);
         g.fillCircle(7, 7, 7);
         g.generateTexture('coin', 14, 14);
          
          setInterval( _ => this.spawnCoins(), 1000);
    }

    update(){
      // Clean Up       
        for(let group of this.coinGroups){
            group.getChildren().forEach(child => {
                if(child.x < 0){
                    group.remove(child, true, true);
                }
            });
        }
       
        this.coinGroups = this.coinGroups.filter(group => group.getChildren().length > 0);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 400,
    height: 200,
    scene: [ GameScene ],
     physics: {
        default: 'matter'
    },
};

const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js"></script>

Related