Issue removing sprite from group

Hi all,

I’m building a small prototype game where I want to remove objects from a SpriteGroup when you touch them. I’ve defined a group and add a sprite to this group each x seconds. When clicking the sprite it should be removed from the group and be destroyed as well. Unfortunately I seem to be running into an issue where the sprite is still being referenced after I’ve destroyed it. Here’s the code:

function create () {
   this.balloons = this.add.group({
   classType: Phaser.GameObjects.Sprite,
   active: true,
 });

 this.spawnBalloonTimer = this.time.addEvent({ delay: 2000, callback: spawnBalloon, callbackScope: this, loop: true });

 this.input.on('pointerdown', function(pointer){
   var touchX = pointer.x;
   var touchY = pointer.y;
   this.balloons.children.iterate(function(child) {
     if (child.getBounds().contains(touchX, touchY)) {
       this.balloons.remove(child, true);
     }
   }, this)
  }, this);
}

spawnBalloon() {
 let balloon = this.physics.add.sprite(randomX, 575, randomBalloon);
 balloon.setInteractive();
 balloon.body.setGravityY(-200);
 balloon.setScale(scaleRatio, scaleRatio);

 this.balloons.add(balloon);
}

This throws the following error Uncaught TypeError: Cannot read property 'getBounds' of undefined, but the balloon is removed from the group and from the screen. I checked with this.balloons.getLength(). Am I doing something wrong?

Something like this?

create() {
  const balloons = this.add.group()

  const spawnBalloon = () => {
    const randomX = this.cameras.main.width * Math.random()
    const randomY = this.cameras.main.height * Math.random()
    const balloon = this.physics.add.sprite(randomX, randomY, 'balloon')
    balloons.add(balloon)
    balloon.body.setAllowGravity(false)
  }

  this.input.on('pointerdown', pointer => {
    const touchX = pointer.x
    const touchY = pointer.y
    balloons.children.iterate(child => {
      if (child && child.getBounds().contains(touchX, touchY)) {
        child.destroy(child, true)
      }
    })
  })

  this.time.addEvent({
    delay: 2000,
    callback: () => {
      spawnBalloon()
      console.log(balloons.children)
    },
    loop: true
  })
}

You should use children.each(…) instead of iterate() if you’re removing group members during the callback.

2 Likes

Ouch… it is right there in the manual:

For when you absolutely know this Set won't be modified during the iteration.

What would be a good reason to use iterate over each? They’re basically doing the same (or is iterate faster than each?)

Thanks a lot! I’m still trying to get into Phaser and find it hard to find my way around in the V3 docs (V2 was more intuitive for me).

I think iterate is just slightly more efficient.

If you prefer simplicity/consistency it’s fine to use each only.

1 Like

Thanks for this.


Are these 3 exactly the same? And can all be used to destroy an object?

balloons.children.each(child => {
})

balloons.children.getArray().forEach(child => {
})

for (let child of balloons.children.getArray()) {
}

I think those should all be safe for remove/destroy.

There are some differences for exiting the loop (break, return false) but I think that’s it.

1 Like

Regarding speed you are probably best with a native for loop (and cached length array):

let l = balloons.children.getArray().length;
for (let x = 0; x < l; x++) {
}

For the case you described you can avoid looping altogether (as well as the hit test) by using the gameobjectdown event and setTopOnly(false).

See input/game object/top only up down events.

1 Like

Hi Samme,

Excellent suggestion. I used the sprite.on(‘pointerdown’) first, but ran into the same issue as I do here. I guess my JS skills are falling short :frowning:

So the issue is, when using either on('pointerdown') or your suggested this.input.on('gameobjectdown') the this context within the callback is either the context where I can I reference the scene or it’s the context of the pointer itself. Aka, if I want to destroy the gameobject in the callback, then I use gameObject.destroy(); but I can’t access the this context of my game to say, set a score. When I bind this to the callback via this.input.on('gameobjectdown', cb, this) I can access the this context, but I lose the reference to the gameobject. There must be a really simple solution to this, but I can’t see it atm…

You can use the second argument passed to the callback, for either event.

Hi, I am new to javascript and Phaser 3.
I am trying to do something similar to Koekenbakker28.
I’ve gotten things to work with yannick’s suggested code, but now I am trying to get the click to add score and can’t get it to even console.log.

can someone one please point me in the right direction?

also, the conversation about using gamobjectdown and using the setTopOnly(false), conversation is useful but I don’t know how to apply it to the code -

  	this.input.on('pointerdown', function(pointer, counter){
		const touchX = pointer.x
		const touchY = pointer.y

		counter++;
		group.children.iterate(child => {
		if (child && child.getBounds().contains(touchX, touchY)) {
			child.destroy(child, true);
			
			}
		})
		 })

this counter is what I’m trying to console log - i’ve tried it in the if and out of the if… just won’t count for me. the balloons with destroy but not count. I also want it to count on a specific color of balloon which I have named the_chosen_one. not sure how do to that with this code - fat arrow functions still don’t always work or make sense to me for some reason.

But my first step before adding the_chosen_one, I need to just get the counter counting on clicks of balloons.

thanks

so, update on the works here.
I got it working and I got score to begin counting:

this.input.on(‘gameobjectdown’, function(pointer, gameobject){
var choice = gameobject.frame.name;
let regExp = new RegExp(balloon_${the_chosen_one});
let regExp2 = new RegExp(${choice});
if (regExp.test(${regExp2})===true) {
counter++;
group.killAndHide(gameobject);
} else {
group.killAndHide(gameobject);
console.log(“wrong”);
}
}, this);

now my current issue is iterating across the group array to find if I have any more balloons of that color left, if not then advance to the next level, but using group.child(choice) --or any other form in the bracket always returns false because it is checking if that particular gameobject that was just killed and hidden is still there and it isn’t so it always goes to the next scene if clicked on the_chosen_one, there are 25 balloons and a few are the correct color. Any help would be appreciated.

so the question is - how do iterate across the array and check from more of the same group.children.entries for the same gameobject.frame.name?

thanks again.