Why aren't all collisions being detected?

This is my first Phaser 3 program. I can’t figure out why the collisions between the characters spawned and the bullets aren’t consistently detected. Sometimes bullets pass through characters with no collision. How can I detect collisions between spawned characters and bullets consistently?
Press ‘W’ to spawn characters.

<!DOCTYPE html>
<head>
<title>Gauging Difficulty for initial Game Design</title>
<script src="/assets/phaser-arcade-physics.min.js"></script>
</head>
<body style="text-align: center;">
<script>

	var config = {
		type: Phaser.WEBGL,
		width: 1000,
		height: 1000,
		physics: {
		  default: 'arcade',
		  arcade: {
			gravity: { y: 0 },
			debug: true,
		  }
		},
		scene: {
			preload: preload,
			create: create,
			update: update,
			extend: {
						player: null,
						healthpoints: null,
						reticle: null,
						moveKeys: null,
						playerBullets: null,
						zombies: null,
						time: 0,
						spawnPoints: null,
						spawnKey: null,
						gun_cooldown: null,
					}
		}
	};
	function preload ()
	{
		var d = new Date();
		var n = d.getTime();
		gun_cooldown = n;
		
    // Load in images and sprites
    this.load.spritesheet('player_handgun', 'assets/character.png',
        { frameWidth: 66, frameHeight: 60 }
    ); // Made by tokkatrain: https://tokkatrain.itch.io/top-down-basic-set
    this.load.image('bullet', 'assets/bullet.png');
    this.load.image('target', 'assets/reticle.png');
    this.load.image('background', 'assets/background.png');
	}

	var game = new Phaser.Game(config);
	
	var Bullet = new Phaser.Class({
			Extends: Phaser.GameObjects.Image,
			initialize:
			// Bullet Constructor
			function Bullet (scene)
			{
				Phaser.GameObjects.Image.call(this, scene, 0, 0, 'bullet');
				this.speed = 1;
				this.born = 0;
				this.direction = 0;
				this.xSpeed = 0;
				this.ySpeed = 0;
				this.setSize(20, 20, true);
			},
		
			// Fires a bullet from the player to the reticle
			fire: function (shooter, target)
			{
				this.setPosition(shooter.x, shooter.y); // Initial position
				this.direction = Math.atan( (target.x-this.x) / (target.y-this.y));
		
				// Calculate X and y velocity of bullet to moves it from shooter to target
				if (target.y >= this.y)
				{
					this.xSpeed = this.speed*Math.sin(this.direction);
					this.ySpeed = this.speed*Math.cos(this.direction);
				}
				else
				{
					this.xSpeed = -this.speed*Math.sin(this.direction);
					this.ySpeed = -this.speed*Math.cos(this.direction);
				}
		
				this.rotation = shooter.rotation; // angle bullet with shooters rotation
				this.born = 0; // Time since new bullet spawned
			},
		
			// Updates the position of the bullet each cycle
			update: function (time, delta)
			{
				this.x += this.xSpeed * delta;
				this.y += this.ySpeed * delta;
				this.born += delta;
				if (this.born > 1800)
				{
					this.setActive(false);
					this.setVisible(false);
				}
			}
	});
	function hitzombie(bullet, zombiehit)
	{
			bullet.destroy();
			zombiehit.destroy();
			console.log("zombieHit");
	}

	function create ()
	{
		spawnKey = 0;
		this.physics.world.setBounds(0, 0, 1000, 1000);
		var circle = new Phaser.Geom.Circle(450, 250, 10);
	
		var graphics = this.add.graphics({ lineStyle: { color: 0x00ff00 } });
		graphics.strokeCircleShape(circle);
		
		spawnPoints = [];
		for(var i = 0; i < 16; i++)
		{
			var angle = i / 16 * Phaser.Math.PI2;
			var xOffset = Math.cos(angle) * 100;
			var yOffset = Math.sin(angle) * 100;
			//turn spawn point into 2d array
			Phaser.Geom.Circle.Offset(circle, xOffset, yOffset);
			graphics.strokeCircleShape(circle);
			spawnPoints[i] = [];
			spawnPoints[i][0] = xOffset*2.8+500;
			spawnPoints[i][1] = yOffset*2.8+500;
		}
/*		spawnPoints.forEach(function(element) {
  			console.log(element);
		});*/

	    playerBullets = this.physics.add.group({ classType: Bullet, runChildUpdate: true });
		zombies = this.physics.add.group();
		//this.physics.add.collider(zombies, playerBullets);
		//new Collider(world, overlapOnly, object1, object2, collideCallback, processCallback, callbackContext)
		this.physics.add.collider(playerBullets, zombies, hitzombie, null, this);

		player = this.physics.add.sprite(500, 500, 'player_handgun');
		player.setOrigin(0.5, 0.5).setDisplaySize(60, 60).setCollideWorldBounds(true).setDrag(500, 500);
	
	    reticle = this.physics.add.sprite(800, 700, 'target');
		// Pointer lock will only work after mousedown
		game.canvas.addEventListener('mousedown', function () {
			game.input.mouse.requestPointerLock();
		});
	
		// Exit pointer lock when Q or escape (by default) is pressed.
		this.input.keyboard.on('keydown_Q', function (event) {
			if (game.input.mouse.locked)
				game.input.mouse.releasePointerLock();
		}, 0, this);
	
		// Move reticle upon locked pointer move
		this.input.on('pointermove', function (pointer) {
			if (this.input.mouse.locked)
			{
				reticle.x += pointer.movementX;
				reticle.y += pointer.movementY;
			}
		}, this);
		   // Fires bullet from player on left click of mouse
		this.input.on('pointerdown', function (pointer, time, lastFired) {
			if (player.active === false)
				return;
	
			// Get bullet from bullets group
			var bullet = playerBullets.get().setActive(true).setVisible(true);
	
			if (bullet)
			{
				bullet.fire(player, reticle);
				this.physics.add.collider(zombies, bullet, hitzombie);
			}
		}, this);
		// Enables movement of player with WASD keys
		this.input.keyboard.on('keydown_W', function (event) {
					//enemyHit.health = enemyHit.health - 1;
					//console.log("Enemy hp: ", enemyHit.health);
					//var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
					if(spawnKey>=16)
					{
						spawnKey=0;
					}
					
					var zombie = zombies.create(spawnPoints[spawnKey][0], spawnPoints[spawnKey++][1], 'player_handgun');
					zombie.setBounce(1);
					zombie.setCollideWorldBounds(true);
					zombie.setVelocity(Phaser.Math.Between(-200, 200), 20);
					zombie.allowGravity = false;
					
					
							//spawnPoints.forEach(function(element) {
  							console.log(spawnKey);
							//});
		
		});

	}

function getClosestEnemy() {
	var zombieUnits = zombies.getChildren();
	if(zombieUnits.length === 0)
	return false;
	var closest_zombie = 0;
	var cur_distance = zombieUnits[0].active && Phaser.Math.Distance.Between(player.x, player.y, zombieUnits[0].x, zombieUnits[0].y)
	var min_distance = cur_distance;
	for(var i = 0; i < zombieUnits.length; i++) {       
		cur_distance = zombieUnits[i].active && Phaser.Math.Distance.Between(player.x, player.y, zombieUnits[i].x, zombieUnits[i].y)
		if (cur_distance < min_distance)
		{	
			min_distance = cur_distance;
			closest_zombie = i;
		}
	}
	return zombieUnits[closest_zombie];
}


	
function update (time, delta)
{
	var target = getClosestEnemy();	
    var rotate_to = Phaser.Math.Angle.Between(player.x, player.y, target.x, target.y);
	//target.body.debugBodyColor = target.body.touching.none ? 0x0099ff : 0xff9900;

		if(player.rotation < rotate_to)
		player.rotation += .2;
		if(player.rotation > rotate_to)
		player.rotation -= .2;

		if(player.rotation - rotate_to < .2)
		{
			var d = new Date();
			var now = d.getTime();
			if (now-gun_cooldown > 500)
			{
				gun_cooldown = now;
				var bullet = playerBullets.get().setActive(true).setVisible(true);
				if (bullet)
				{
					bullet.fire(player, target);
					this.physics.add.collider(zombies, bullet, hitzombie);
				}
			}
		}
	//var timer = this.time.addEvent({ delay: 10, callback: aimandshoot, callbackScope: this });
	// Rotates player to face towards reticle
    //player.rotation = Phaser.Math.Angle.Between(player.x, player.y, reticle.x, reticle.y);

    //console.log(getClosestEnemy().x);	


    /*for (var j=10; j--; j>0)
	{
		enemy[j].rotation = Phaser.Math.Angle.Between(enemy[j].x, enemy[j].y, player.x, player.y);
		enemyFire(enemy[j], player, time+(Math.floor(Math.random() * 1000) + 1), this);
		if(enemy[j].x < player.x)
		enemy[j].body.velocity.x = 50;
		if(enemy[j].x > player.x)
		enemy[j].body.velocity.x = -50;
		if(enemy[j].y < player.y)
		enemy[j].body.velocity.y = 50;
		if(enemy[j].y > player.y)
		enemy[j].body.velocity.y = -50;	
	}*/
	// Rotates enemy to face towards player
    //zombies.rotation = Phaser.Math.Angle.Between(enemy.x, enemy.y, player.x, player.y);

    //Make reticle move with player
    //reticle.body.velocity.x = player.body.velocity.x;
    //reticle.body.velocity.y = player.body.velocity.y;

    // Constrain velocity of player
    //constrainVelocity(player, 500);

    // Constrain position of constrainReticle
    //constrainReticle(reticle);

    // Make enemy fire
    //enemyFire(enemy, player, time, this);
}
	
</script>
</body>
</html>

I think Bullet should extend Phaser.Physics.Arcade.Image.

Thanks! I tried that and the issue persists. I don’t understand why it doesn’t work consistently. Any other ideas? Any applicable examples you can point me to?

<!DOCTYPE html>
<head>
<title>Gauging Difficulty for initial Game Design</title>
<script src="/assets/phaser-arcade-physics.min.js"></script>
</head>
<body style="text-align: center;">
<script>

	var config = {
		type: Phaser.WEBGL,
		width: 1000,
		height: 1000,
		physics: {
		  default: 'arcade',
		  arcade: {
			gravity: { y: 0 },
			debug: true,
		  }
		},
		scene: {
			preload: preload,
			create: create,
			update: update,
			extend: {
						player: null,
						healthpoints: null,
						reticle: null,
						moveKeys: null,
						playerBullets: null,
						zombies: null,
						time: 0,
						spawnPoints: null,
						spawnKey: null,
						gun_cooldown: null,
					}
		}
	};
	function preload ()
	{
		var d = new Date();
		var n = d.getTime();
		gun_cooldown = n;
		
    // Load in images and sprites
    this.load.spritesheet('player_handgun', 'asasets/character.png',
        { frameWidth: 66, frameHeight: 60 }
    ); // Made by tokkatrain: https://tokkatrain.itch.io/top-down-basic-set
    this.load.image('bullet', 'assets/bullet.png');
    this.load.image('target', 'assets/reticle.png');
    this.load.image('background', 'assets/background.png');
	}

	var game = new Phaser.Game(config);
	var Bullet = new Phaser.Class({
			Extends: Phaser.Physics.Arcade.Image,
			initialize:
			// Bullet Constructor
			function Bullet (scene)
			{
				Phaser.GameObjects.Image.call(this, scene, 0, 0, 'bullet');
				this.speed = 1;
				this.born = 0;
				this.direction = 0;
				this.xSpeed = 0;
				this.ySpeed = 0;
				//this.setSize(20, 20, true);
			},
		
			// Fires a bullet from the player to the reticle
			fire: function (shooter, target)
			{
				this.setPosition(shooter.x, shooter.y); // Initial position
				this.direction = Math.atan( (target.x-this.x) / (target.y-this.y));
		
				// Calculate X and y velocity of bullet to moves it from shooter to target
				if (target.y >= this.y)
				{
					this.xSpeed = this.speed*Math.sin(this.direction);
					this.ySpeed = this.speed*Math.cos(this.direction);
				}
				else
				{
					this.xSpeed = -this.speed*Math.sin(this.direction);
					this.ySpeed = -this.speed*Math.cos(this.direction);
				}
		
				this.rotation = shooter.rotation; // angle bullet with shooters rotation
				this.born = 0; // Time since new bullet spawned
			},
		
			// Updates the position of the bullet each cycle
			update: function (time, delta)
			{
				this.x += this.xSpeed * delta;
				this.y += this.ySpeed * delta;
				this.born += delta;
				if (this.born > 1800)
				{
					this.setActive(false);
					this.setVisible(false);
				}
			}
	});
	function hitzombie(bullet, zombiehit)
	{
			bullet.destroy();
			zombiehit.destroy();
			console.log("zombieHit");
	}

	function create ()
	{
		spawnKey = 0;
		this.physics.world.setBounds(0, 0, 1000, 1000);
		var circle = new Phaser.Geom.Circle(450, 250, 10);
	
		var graphics = this.add.graphics({ lineStyle: { color: 0x00ff00 } });
		graphics.strokeCircleShape(circle);
		
		spawnPoints = [];
		for(var i = 0; i < 16; i++)
		{
			var angle = i / 16 * Phaser.Math.PI2;
			var xOffset = Math.cos(angle) * 100;
			var yOffset = Math.sin(angle) * 100;
			//turn spawn point into 2d array
			Phaser.Geom.Circle.Offset(circle, xOffset, yOffset);
			graphics.strokeCircleShape(circle);
			spawnPoints[i] = [];
			spawnPoints[i][0] = xOffset*2.8+500;
			spawnPoints[i][1] = yOffset*2.8+500;
		}
/*		spawnPoints.forEach(function(element) {
  			console.log(element);
		});*/

	    playerBullets = this.physics.add.group({ classType: Bullet, runChildUpdate: true });
		zombies = this.physics.add.group();
		//this.physics.add.collider(zombies, playerBullets);
		//new Collider(world, overlapOnly, object1, object2, collideCallback, processCallback, callbackContext)
		this.physics.add.collider(playerBullets, zombies, hitzombie, null, this);

		player = this.physics.add.sprite(500, 500, 'player_handgun');
		player.setOrigin(0.5, 0.5).setDisplaySize(60, 60).setCollideWorldBounds(true).setDrag(500, 500);
	
	    reticle = this.physics.add.sprite(800, 700, 'target');
		// Pointer lock will only work after mousedown
		game.canvas.addEventListener('mousedown', function () {
			game.input.mouse.requestPointerLock();
		});
	
		// Exit pointer lock when Q or escape (by default) is pressed.
		this.input.keyboard.on('keydown_Q', function (event) {
			if (game.input.mouse.locked)
				game.input.mouse.releasePointerLock();
		}, 0, this);
	
		// Move reticle upon locked pointer move
		this.input.on('pointermove', function (pointer) {
			if (this.input.mouse.locked)
			{
				reticle.x += pointer.movementX;
				reticle.y += pointer.movementY;
			}
		}, this);
		   // Fires bullet from player on left click of mouse
		this.input.on('pointerdown', function (pointer, time, lastFired) {
			if (player.active === false)
				return;
	
			// Get bullet from bullets group
			var bullet = playerBullets.get().setActive(true).setVisible(true);
	
			if (bullet)
			{
				bullet.fire(player, reticle);
				this.physics.add.collider(zombies, bullet, hitzombie);
			}
		}, this);
		// Enables movement of player with WASD keys
		this.input.keyboard.on('keydown_W', function (event) {
					//enemyHit.health = enemyHit.health - 1;
					//console.log("Enemy hp: ", enemyHit.health);
					//var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
					if(spawnKey>=16)
					{
						spawnKey=0;
					}
					
					var zombie = zombies.create(spawnPoints[spawnKey][0], spawnPoints[spawnKey++][1], 'player_handgun');
					zombie.setBounce(1);
					zombie.setCollideWorldBounds(true);
					zombie.setVelocity(Phaser.Math.Between(-200, 200), 20);
					zombie.allowGravity = false;
					
					
							//spawnPoints.forEach(function(element) {
  							console.log(spawnKey);
							//});
		
		});

	}

function getClosestEnemy() {
	var zombieUnits = zombies.getChildren();
	if(zombieUnits.length === 0)
	return false;
	var closest_zombie = 0;
	var cur_distance = zombieUnits[0].active && Phaser.Math.Distance.Between(player.x, player.y, zombieUnits[0].x, zombieUnits[0].y)
	var min_distance = cur_distance;
	for(var i = 0; i < zombieUnits.length; i++) {       
		cur_distance = zombieUnits[i].active && Phaser.Math.Distance.Between(player.x, player.y, zombieUnits[i].x, zombieUnits[i].y)
		if (cur_distance < min_distance)
		{	
			min_distance = cur_distance;
			closest_zombie = i;
		}
	}
	return zombieUnits[closest_zombie];
}


	
function update (time, delta)
{
	var target = getClosestEnemy();	
    var rotate_to = Phaser.Math.Angle.Between(player.x, player.y, target.x, target.y);
	//target.body.debugBodyColor = target.body.touching.none ? 0x0099ff : 0xff9900;

		if(player.rotation < rotate_to)
		player.rotation += .2;
		if(player.rotation > rotate_to)
		player.rotation -= .2;

		if(player.rotation - rotate_to < .2)
		{
			var d = new Date();
			var now = d.getTime();
			if (now-gun_cooldown > 500)
			{
				gun_cooldown = now;
				var bullet = playerBullets.get().setActive(true).setVisible(true);
				if (bullet)
				{
					bullet.fire(player, target);
					this.physics.add.collider(zombies, bullet, hitzombie);
				}
			}
		}
	//var timer = this.time.addEvent({ delay: 10, callback: aimandshoot, callbackScope: this });
	// Rotates player to face towards reticle
    //player.rotation = Phaser.Math.Angle.Between(player.x, player.y, reticle.x, reticle.y);

    //console.log(getClosestEnemy().x);	


    /*for (var j=10; j--; j>0)
	{
		enemy[j].rotation = Phaser.Math.Angle.Between(enemy[j].x, enemy[j].y, player.x, player.y);
		enemyFire(enemy[j], player, time+(Math.floor(Math.random() * 1000) + 1), this);
		if(enemy[j].x < player.x)
		enemy[j].body.velocity.x = 50;
		if(enemy[j].x > player.x)
		enemy[j].body.velocity.x = -50;
		if(enemy[j].y < player.y)
		enemy[j].body.velocity.y = 50;
		if(enemy[j].y > player.y)
		enemy[j].body.velocity.y = -50;	
	}*/
	// Rotates enemy to face towards player
    //zombies.rotation = Phaser.Math.Angle.Between(enemy.x, enemy.y, player.x, player.y);

    //Make reticle move with player
    //reticle.body.velocity.x = player.body.velocity.x;
    //reticle.body.velocity.y = player.body.velocity.y;

    // Constrain velocity of player
    //constrainVelocity(player, 500);

    // Constrain position of constrainReticle
    //constrainReticle(reticle);

    // Make enemy fire
    //enemyFire(enemy, player, time, this);
}
	
</script>
</body>
</html>

You said collision is inconsistent. So, sometimes it works. strange.
Code looks really messed up. Right now you have 3 colliders. 2 of them pass zombies as first object and other one pass bullets. Can’t figure out which one is working and which one is not.

Anyways these might help you.
Extending Arcade Sprite
Arcade Physics Examples

Here’s a live demo with cleaner code with same issue. I will read up more to figure out what I’m missing any help is appreciated. Thank you!

Click game screen to spawn enemy:
https://game-hainesdev783224.codeanyapp.com/Index.html

Some of the shots do genuinely miss because the targets move out of the way.

Apart from those, it seems that the issue is caused by manually updating the bullet’s position. If you remove that from the bullet’s update function and give it a velocity on firing instead, collisions are detected just fine.

fire: function (shooter, target)
{
    this.setPosition(shooter.x, shooter.y); // Initial position

    // Calculate X and y velocity of bullet to moves it from shooter to target
    ...

    // set bullet's velocity
    // a factor of 1000 seems to be similar to the example you gave
    // you should probably omit that and edit this.speed in the constructor instead
    this.setVelocity(this.xSpeed * 1000, this.ySpeed * 1000);

    this.rotation = shooter.rotation; // angle bullet with shooters rotation
    this.born = 0; // Time since new bullet spawned
}
2 Likes

I think you’ll want to avoid creating a collider for each bullet, also.

Thanks lime that resolved the issue!

So Samme how would you add the collider?

this.physics.add.collider(playerBullets, zombies, hitzombie, null, this);

That should work.

With that, you could use

playerBullets.remove(bullet, true, true)

instead of

bullet.destroy();

but even if you didn’t, I think the collider would still work fine.

2 Likes

So would you add the collider to one object or a group of the same object??