On my main game class I have a createCar
method, which dynamically creates a car and iterates over other cars and maps raycast between each other (so cars can detect distance between each other):
createCar(scene, x, y, angle, speed, name) {
const car = new Car(scene, x, y, angle, speed);
car.name = name; // for debug
this.carsGroup.children.iterate((c) => {
c.raycaster.mapGameObjects(car, true);
car.raycaster.mapGameObjects(c, true);
});
this.carsGroup.add(car);
}
It works, unless I want to delete a car:
update() {
this.carsGroup.children.iterate((car) => {
// Car is out of screen
if (!this.cameras.main.worldView.contains(car.x, car.y)) {
this.carsGroup.remove(car);
this.carsGroup.children.iterate((c) => {
c.raycaster.removeMappedObjects(car);
car.raycaster.removeMappedObjects(c);
});
car.destroy();
} else {
car.update();
car.debug();
}
});
}
Then it fails here when internally it tries to retrieve the "raycasterMap" and destroy it:
object.data.get('raycasterMap').destroy();
raycaster-core.js:396 Uncaught TypeError: Cannot read properties of undefined (reading 'destroy') at Raycaster.removeMappedObjects (raycaster-core.js:396:48) at game.js:51:23
I can't figure out how to properly remove a car from scene and memory.
If I just call car.destroy()
other cars throw exceptions, because the raycast still has references.
Full codepen sample: https://codepen.io/vvyshko/pen/bGOxpbG
I think the issue is, that the clean up is not optimal. I would suggest, put the cleanup into the destroy
function of the car
- class, and just add validation in the update
function of the scene, checking if the car
- child is a valid object,
Here the alterations:
class Car extends Phaser.Physics.Arcade.Sprite {
// ...
destroy(){
super.destroy();
// Cleanup data
this.ray.destroy();
this.raycaster.destroy();
}
// ...
}
class Example extends Phaser.Scene {
//...
update() {
this.carsGroup.children.iterate((car) => {
// Check if the list item is valid
if (!car) {
return;
}
if (!this.cameras.main.worldView.contains(car.x, car.y)) {
// Remove Car from Group and destroy Object
this.carsGroup.remove(car, true, true);
} else {
car.update();
car.debug();
}
});
}
}
Code copied and adapted from Codepen:
class Car extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y, rotation, speed) {
super(scene, x, y, "car");
this.debugGraphics = scene.add.graphics();
this.speed = speed;
this.initialSpeed = speed;
this.rayLength = 100;
this.rayColor = 0xffffff;
this.rotation = rotation;
this.setDisplaySize(130, 80);
this.debugColor = new Phaser.Display.Color();
// Set the color to a random value
this.debugColor.random();
// Create a raycaster for the car
this.raycaster = scene.raycasterPlugin.createRaycaster();
// Create a ray for the car
this.ray = this.raycaster.createRay();
// Set the ray origin to the car position
this.ray.setOrigin(this.x, this.y);
// Set the ray angle to the car angle
this.ray.setAngle(this.rotation);
scene.physics.world.enable(this); // Enable physics for the car
scene.add.existing(this); // Add the car to the scene
//this.raycaster.mapGameObjects(scene.rectangle);
}
update() {
// console.log('update', this.name, this.speed);
window.lastUpdatedCar = this;
this.raycaster.update();
// Move the car forward
this.scene.physics.velocityFromRotation(
this.rotation,
this.speed,
this.body.velocity
);
// Update the ray origin and angle
this.ray.setOrigin(this.x, this.y);
this.ray.setAngle(this.rotation);
// Cast the ray and get the intersection
// console.log(this.name, 'removed=', this.removed)
let intersection ;
try {
intersection = this.ray.cast()
} catch (e) {}
this.intersection = intersection;
// If there is an intersection, reduce the speed or stop
if (intersection) {
// console.log('!!!', intersection)
}
if (this.crashed) {
this.setTint(0x555888);
this.speed = 0;
} else if (intersection && intersection.object) {
// console.log('!!!', intersection)
let distance = Phaser.Math.Distance.BetweenPoints(this, intersection);
if (distance < this.rayLength) {
// Determine a deceleration rate (you can adjust this value)
const decelerationRate = 1; // Adjust as needed
// Calculate the new speed with deceleration
this.speed -= decelerationRate;
// Ensure the speed doesn't become negative
if (this.speed < 0) {
this.speed = 0;
}
} else {
if (this.speed < this.initialSpeed) {
this.speed++;
}
}
}
}
handleCollision(o1, o2) {
console.log('CRASH', o1, o2)
o1.crashed = true;
o2.crashed = true;
}
debug() {
this.debugGraphics.clear();
this.debugGraphics.setDepth(this.depth + 1); // Set the depth
this.debugGraphics.lineStyle(1, this.debugColor.color);
this.debugGraphics.beginPath();
this.debugGraphics.moveTo(this.x, this.y);
this.debugGraphics.lineTo(
this.x + Math.cos(this.rotation) * this.rayLength,
this.y + Math.sin(this.rotation) * this.rayLength
);
this.debugGraphics.closePath();
this.debugGraphics.strokePath();
if (this.intersection) {
this.debugGraphics.fillStyle(0xff0000, 0.5);
this.debugGraphics.strokeCircle(
this.intersection.x,
this.intersection.y,
5
);
}
if (this.crashed) {
// Draw a red cross
this.debugGraphics.lineStyle(2, 0xff0000);
this.debugGraphics.beginPath();
this.debugGraphics.moveTo(this.x - 10, this.y - 10);
this.debugGraphics.lineTo(this.x + 10, this.y + 10);
this.debugGraphics.moveTo(this.x + 10, this.y - 10);
this.debugGraphics.lineTo(this.x - 10, this.y + 10);
this.debugGraphics.closePath();
this.debugGraphics.strokePath();
}
}
destroy(){
super.destroy();
// Cleanup data
//this.ray.destroy();
//this.raycaster.destroy();
}
}
class Example extends Phaser.Scene {
constructor() {
super();
}
preload() {
this.load.setBaseURL("https://labs.phaser.io");
this.load.image("bg", "assets/skies/space2.png");
this.load.image("car", "assets/sprites/car-yellow.png");
window.scene = this;
}
create() {
const bg = this.add.image(0, 0, "bg");
bg.setOrigin(0, 0); // Set the origin to the top-left corner
bg.displayWidth = this.sys.game.config.width; // Stretch width to match canvas width
bg.displayHeight = this.sys.game.config.height;
// //create game object
// this.rectangle = this.add
// .rectangle(1000, 300, 50, 200)
// .setStrokeStyle(1, 0xff0000);
this.carsGroup = this.physics.add.group();
window.carsGroup = this.carsGroup;
this.createCar(this, 100, 300, 0, 10, "A");
this.createCar(this, 600, 600, -1, 270, "B");
this.createCar(this, 400, 300, -0.44, 10, "C");
this.createCar(this, 600, 700, 0, 170, "D");
// this.createCar(this, 700, 600, -1.44, 120);
}
createCar(scene, x, y, angle, speed, name) {
const car = new Car(scene, x, y, angle, speed);
car.name = name; // for debug
this.carsGroup.children.iterate((c) => {
c.raycaster.mapGameObjects(car, true);
car.raycaster.mapGameObjects(c, true);
c.scene.physics.add.collider(c, car, c.handleCollision);
car.scene.physics.add.collider(car, c, car.handleCollision);
});
this.carsGroup.add(car);
}
update() {
this.carsGroup.children.iterate((car) => {
// Check if the list item is valid
if (!car) {
return;
}
if (!this.cameras.main.worldView.contains(car.x, car.y)) {
// Remove Car from Group and destroy Object
this.carsGroup.remove(car, true, true);
} else {
car.update();
car.debug();
}
});
}
}
const config = {
type: Phaser.WEBGL,
width: 1280,
height: 1024,
parent: "phaser-example",
physics: {
default: "arcade",
},
plugins: {
scene: [
{
key: "PhaserRaycaster",
plugin: PhaserRaycaster,
mapping: "raycasterPlugin",
},
],
},
scene: Example,
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser-arcade-physics.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser-raycaster.js"></script>