I’m attempting to create a zombie survival game. I’m having a problem where my game gets more laggy, the more bullets I fire.
My code is structured as follows (documented beneath - may be easier to follow)
import WeaponSystem from "../../../common/modules/WeaponSystem";
import { ExtendedNengiTypes } from "../../../common/types/custom-nengi-types";
import Phaser from "phaser";
import BotGraphicServer from "./BotGraphicServer";
import BulletGraphicServer from "./BulletGraphicServer";
import BulletEntity from "../../../common/entity/BulletEntity";
import ClientHudMessage from "../../../common/message/ClientHudMessage";
export default class PlayerGraphicServer extends Phaser.Physics.Arcade.Sprite{
weaponSystem: WeaponSystem
rotation = 0
speed: number
bulletGraphics: Map<number, BulletGraphicServer>
health = 100
totalBullets = 0
constructor(
scene: Phaser.Scene,
private worldLayer: Phaser.Tilemaps.StaticTilemapLayer,
private nengiInstance: ExtendedNengiTypes.Instance,
private client: ExtendedNengiTypes.Client,
xStart: number,
yStart: number,
public associatedEntityId: number
) {
super(scene, xStart, yStart, "player");
this.bulletGraphics = new Map();
this.speed = 1000;
this.associatedEntityId = associatedEntityId;
scene.add.existing(this);
scene.physics.add.existing(this);
this.setSize(50,50)
this.setDisplaySize(50, 50)
console.log("Setting up collision with world");
scene.physics.add.collider(this, worldLayer);
this.body.immovable = true
setInterval(() => {
this.updateHud()
}, 200)
}
// Sent message to the client who owns this player, with their player information
updateHud() {
this.nengiInstance.message(new ClientHudMessage(
this.health,
"~",
"Shredder",
), this.client);
}
fire(bots: any ) {
// Set on cooldown - will check soon
// this.weaponSystem.fire()
const bulletEntity = new BulletEntity(this.x, this.y, this.rotation + 1.57079633);
this.nengiInstance.addEntity(bulletEntity);
// We now have a bullet created, that has a link to the entity so we can update it easily
const bulletGraphic = new BulletGraphicServer(this.scene, this.worldLayer, bulletEntity.nid, this.x, this.y, Phaser.Math.RadToDeg(this.rotation), bots, this.processBulletHit);
this.bulletGraphics.set(bulletGraphic.associatedEntityId, bulletGraphic);
// Debug bullet creation lag
this.totalBullets++
console.log(this.totalBullets)
setTimeout(() => {
this.deleteBullet(bulletGraphic.associatedEntityId);
}, 3000);
}
deleteBullet = (entityId: number) => {
const bulletEntity = this.nengiInstance.getEntity(entityId);
if (!bulletEntity) {
// console.log("Trying to delete a bullet which doesn't exist any longer (may have already been cleared after collission)");
return;
}
this.nengiInstance.removeEntity(bulletEntity);
// Delete server copy
let bullet = this.bulletGraphics.get(entityId);
// CALL THIS FIRST TO IMPROVE PERFORMANCE
bullet.removeColliders()
bullet.destroy(false);
this.bulletGraphics.delete(entityId);
console.log(this.bulletGraphics.size)
}
processBulletHit = (bullet: any, hitObj: any) => {
// console.log(`Bullet hit an object ${bullet.associatedEntityId} hit zombie ${hitObj.name}`);
if (hitObj.type === "BOT") {
hitObj.takeDamage(bullet.associatedEntityId);
}
this.deleteBullet(bullet.associatedEntityId);
}
processMove = (command: any) => {
// Removed to simplify
}
// Update the entity, with the local positions of all bullets this player has
preUpdate() {
this.bulletGraphics.forEach((bullet) => {
const associatedEntity = this.nengiInstance.getEntity(bullet.associatedEntityId);
if (!associatedEntity) {
console.log("Trying to update positions of bullet graphic, but cannot find an entity");
return;
}
associatedEntity.x = bullet.x;
associatedEntity.y = bullet.y;
associatedEntity.rotation = bullet.rotation;
});
}
public takeDamage(damagerEntityId: number) {
// Removed to simplify
}
}
Bullet
import BotGraphicServer from "./BotGraphicServer";
import Phaser from "phaser";
export default class BulletGraphicServer extends Phaser.Physics.Arcade.Sprite{
// sprite: Phaser.Physics.Arcade.Sprite
rotation: number = 0
colliders:any[] = []
constructor(
scene: Phaser.Scene,
worldLayer: Phaser.Tilemaps.StaticTilemapLayer,
public associatedEntityId: number,
startX: number,
startY: number,
angle: number,
bots: BotGraphicServer[],
cb: any
) {
super(scene, startX, startY, "bullet");
this.type = "BULLET";
this.scene.add.existing(this);
this.scene.physics.add.existing(this);
this.setSize(10, 20)
this.setDisplaySize(10, 20)
this.colliders.push(this.scene.physics.add.collider(this, worldLayer, cb))
this.body.immovable = true
// this.associatedEntityId = associatedEntityId
const vec = scene.physics.velocityFromAngle(angle, 250);
this.setVelocityX(vec.x);
this.setVelocityY(vec.y);
// Hacky way to fix our (rotate extra 90 degree) sprite, until we edit :)
this.rotation = Phaser.Math.DegToRad(angle) + 1.57079633
bots.forEach((bot: any) => {
this.colliders.push(this.scene.physics.add.collider(this, bot, cb))
});
}
preUpdate = () => {
// console.log("Running pre-update")
}
public removeColliders() {
console.log(`Removing ${this.colliders.length} colliders`)
this.colliders.forEach((c) => {
this.scene.physics.world.removeCollider(c)
})
}
}
Here’s an overview of what happens -
-
The fire() method is called with a list of all current bots, when the client clicks on the screen. the
Player
class is responsible for creating a bullet and storing it in thebulletGraphics
map (using the ID of an “entity” we create - used for networking). -
The
Bullet
class accepts this ID we used as a key, a collision callback method, a worldLayer, and a map of all zombies (and a few other bits we don’t need to worry about). It attaches a collider to myworldlayer
(created asthis.map.createStaticLayer("LevelOneWorld", tileset, 0, 0);
in the level code, and also loops every zombie - adding a collider with the callback provided. -
Upon the bullet either hitting a
worldLayer
object (E.G a wall) or a zombie - the callback function I provided into theBullet
class, is invoked, and handled in thePlayer
class -
The
Player
class calls the damage method if the collision was with a zombie, and then callsdeleteBullet()
. This method looks up the bullet from the map we originally created, and callsdestroy()
on the sprite instance
The problem I’m having, is after spawning 200 zombies, and then firing my gun, my game begins lagging hard after 1500 bullets (this test was done firing at a wall, which as previously mentioned will call destroy()
( on the bullet instance when hit).
If I add the following code to my BulletGraphicServer
, where I manually store all collisions, and then remove then every time I delete a bullet - I’ve tested 3000 bullets and the lag doesn’t seem to appear. However my game does lag hard as I run through that loop, so calling removeCollider
isn’t really usable in the real world.
This seems to point to either a misunderstanding on my behalf, on how destroy()
works (I thought it will remove the sprite from the scene physics update), or a bug in my code.
Here’s a screenshot of the Player, Bullets + Zombies if it helps visualize the problem -
Does anyone have any ideas?