Entity selection logic need help

Hi there. I’ve been working on a project. I have a restaurant/cafe type setting, and employees and machines that I can spawn into the game. The customers are spawned at different intervals.

One of the features is being able to select an employee, and order it to use a machine, such as an espresso machine (labeled as coffee maker). fohZone zone object is what I’m using to handle the input. (clicking on the employee)

Heres the code I am using for the employee:

export class FOHEntity extends Entity {
    constructor(scene, x, y, texture) {
        super(scene, x, y, texture)
        this.scene = scene
        this.setOrigin(0.16, 0.5);
        this.setFrame(7)

        this.finder = new EasyStar.js()
        this.setupFinder()
        this.name = null

        this.hasReachedCashRegister = false
        this.hasReachedCoffeeMaker = false

        this.idNumber = 0
        this.crIDInUse = 0

        this.randomMakeCoffeeSpeedValue = 0
        this.coffeeID = null
        this.isMakingCoffee = false

        this.markerSelect = this.scene.add.sprite(0, 0, "marker").setFrame(2)
        this.markerSelect.setVisible(false)
        this.markerSelect.setDepth(9000)

        this.fohStateMachine = new StateMachine('followPointer', {
            followPointer: new FOHFollowPointerState(),
            idle: new FOHIdleState(),
            cashRegister: new FOHHandleCashRegisterState(),
            coffeeMaker: new FOHHandleCoffeeMakerState(),
            makeCoffee: new FOHMakeCoffeeState(),
            moveToPlace: new FOHMoveToPlaceState(),
            placeCoffee: new FOHPlaceCoffeeState()
        }, [this, scene])

        this.fohZone = this.scene.add.zone(0, 0, 16, 20);
        this.fohZone.setOrigin(0, 0)
        this.fohZone.setInteractive()
        this.fohZone.name = "FohZone"
        this.scene.physics.world.enable(this.fohZone);
        this.fohZone.idNumber = this.idNumber

        this.isSelected = false

        this.cmIDInUse = 0

        this.debugScene = this.scene.scene.get("DebugScene")

        // Handling employee selection
        this.fohZone.on("pointerover", () => {
            this.scene.input.off("pointerdown")

            this.scene.input.on("pointerdown", (pointer, obj) => {
                this.isSelected = true
            })
        })
        this.fohZone.on("pointerout", () => {
            this.scene.input.off("pointerdown")
            this.scene.input.on("pointerdown", () => {
                for (let index = 0; index < this.scene.fohEntities.getChildren().length; index++) {
                    this.scene.fohEntities.getChildren()[index].isSelected = false
                }
            })
        })
    }
    setupFinder() {
        // We create the 2D array representing all the tiles of our map
        let grid = [];
        for (let y = 0; y < this.scene.map.height; y++) {
            let col = [];
            for (let x = 0; x < this.scene.map.width; x++) {
                // In each cell we store the ID of the tile, which corresponds
                // to its index in the tileset of the map ("ID" field in Tiled)
                col.push(this.getTileID(x, y));
            }
            grid.push(col);
        }

        this.finder.setGrid(grid);

        let tileset = this.scene.map.tilesets[0]
        let properties = tileset.tileProperties
        let acceptableTiles = []

        //     // We need to list all the tile IDs that can be walked on. Let's iterate over all of them
        //     // and see what properties have been entered in Tiled.
        for (let i = tileset.firstgid - 1; i < this.scene.tiles.total; i++) { // firstgid and total are fields from Tiled that indicate the range of IDs that the tiles can take in that tileset
            if (!properties.hasOwnProperty(i)) {
                // If there is no property indicated at all, it means it's a walkable tile
                acceptableTiles.push(i + 1);
                continue;
            }
            if (!properties[i].collide) acceptableTiles.push(i + 1);
            if (properties[i].cost) this.finder.setTileCost(i + 1, properties[i].cost); // If there is a cost attached to the tile, let's register it
        }
        this.finder.setAcceptableTiles(acceptableTiles)
    }
    handleMove(a, b) {
        let x = a
        let y = b
        let tileXY = this.scene.map.tileToWorldXY(x, y)
        let toX = Math.floor(tileXY.x / 16);
        let toY = Math.floor(tileXY.y / 16);
        let fromX = Math.floor(this.getEntity().x / 16);
        let fromY = Math.floor(this.getEntity().y / 16);

        let currentThis = this

        this.finder.findPath(fromX, fromY, toX, toY, function (path) {
            if (path === null) {
            } else {
                currentThis.moveCharacter(path)
            }
        });
        this.finder.calculate(); // don't forget, otherwise nothing happens
    }
    moveCharacter(path) {
        let tweens = []
        for (let i = 0; i < path.length - 1; i++) {
            let ex = path[i + 1].x
            let ey = path[i + 1].y
            tweens.push({
                targets: this,
                x: { value: ex * this.scene.map.tileWidth, duration: 200 },
                y: { value: ey * this.scene.map.tileHeight, duration: 200 }
            })
        }
        this.scene.tweens.timeline({
            tweens: tweens
        })
    }
    getTileID(x, y) {
        let tile = this.scene.map.getTileAt(x, y, true, "groundLayer")
        return tile.index
    }
    getEntity() {
        return this
    }

    update() {
        // handling coffee selection
        if (this.isSelected == true) {
            this.markerSelect.setVisible(true)
            for (let index = 0; index < this.scene.coffeeMakerEntities.getChildren().length; index++) {
                this.scene.coffeeMakerEntities.getChildren()[index].configZone.off("pointerdown")
                this.scene.coffeeMakerEntities.getChildren()[index].configZone.on("pointerdown", () => {
                    console.log("use coffee maker")
                    let obj = { index: index }
                    this.fohStateMachine.transition("coffeeMaker", obj)
                })
            }
            // for (let index = 0; index < this.scene.cashRegisterEntities.getChildren().length; index++) {
            //     this.scene.cashRegisterEntities.getChildren()[index].zone.off("pointerdown")
            //     this.scene.cashRegisterEntities.getChildren()[index].zone.on("pointerdown", () => {
            //         console.log("use")
            //         let obj = { index: index }
            //         this.fohStateMachine.transition("cashRegister", obj)
            //     })
            // }
        } else {
            for (let index = 0; index < this.scene.coffeeMakerEntities.getChildren().length; index++) {
                this.scene.coffeeMakerEntities.getChildren()[index].configZone.off("pointerdown")
                this.scene.coffeeMakerEntities.getChildren()[index].configZone.on("pointerdown", () => {
                    console.log("config")
                    let obj = { gameScene: this.scene, coffeeIndex: index }
                    this.scene.scene.launch("CoffeeMenuScene", obj)
                })
            }
            this.markerSelect.setVisible(false)
        }

        this.fohZone.x = this.x
        this.fohZone.y = this.y - 8

        this.markerSelect.x = this.x + 8
        this.markerSelect.y = this.y

        this.fohStateMachine.step()
    }
}

The entity class for the employee has a isSelected boolean, and is changed depending if the pointerout or pointerdown is triggered.

Then I have another listener with the coffee machine in the update function. You can click on the coffee maker, which result will depend on if you have selected an employee. Clicking on the machine without an employee selected will bring up a menu for that coffee machine.

So far, this works for one employee, but does not work when more than one employee has spawned. Only the newest employee that has spawned will continue to work properly, the other employees will load the menu and use the coffee machine at the same time, which is incorrect behaviour.

Something is faulty with the logic I’m using, and I’m having difficulty solving this. It can be rather confusing. Heh. :woozy_face:

I’m an intermediate amateur at best when it comes to coding and js, so there are some concepts that I have not being able to fully grasp yet, but I hope to in the near future. I would much appreciate any suggestions or ideas.

I’m going to upload my code to github some point soon, so people can check it out if they want.

EDIT: Oops made some mistake posting this twice

Thanks again.

it seems like you define the “eventlistenrs” for the coffeemaschines in the update method!?
Thats not good, because so you create each update-loop a new handler…

You need to make sure you definie all eventlisteners only once. Passing the correct item reference to it on demand.

You should store the employee and machines in scene groups. so you can access them easyle.
And if an employee or machine is selected, check the current scene status.

for example:

// psydo-code, not working nor correct syntax
class Employe{

	constructor( scene, x, y ){
	
		this.selected = false;
		this.targetMachine = null;
		
		// make this object interactive
		this.setInteractive(
			hitZone,
			this.clicked
		}
		// add it to scene, or group or whatever you need
		scene.add.existing( this )
		scene.employee.add( this );
	}
	
	// make a click => tell it the scene
	clicked(){
		this.scene.selectEmp( this );
	}
	
	// set a reference to the target machine this employee should use
	setMachine( machine ) {
		this.targetMachine = machine;
		return this;
	}
}

/// the coffee machine
class CoffeeMachine{

	constructor( scene, x, y ){
	
		this.setInteractive(
			hitZone,
			this.clicked
		);
		
		scene.machines.add( this );
	}
	
	clicked(){
		// check if an employee is selected
		let empSelected = this.scene.getSelectedEmp();
		if( empSelected ) ){
			empSelected.setMachine( this );
		}
	}
	
}

// your scene:

class CoffeeShop{

	create(){
		// keep track of your employee and machines
		this.employees = new Group({ childUpdate:true} );
		this.machines = new Group( {childUpdate: true} );

	}
	
	update(){
	
	}
	
	selectEmp( emp ){
		
		// keep an reference to current selected worker for faster access
		this.selectedWorker = null;
		// update the selected worker, remove selection from all others
		this.employee.children.forEach( e => {
			e.selected = e==emp;
		});
	}
	
	getSelectedEmp(){
		return this.employee.children.find( e => e.selected );
	}
}

1 Like

Thank you for the reply, it worked indeed. I had groups located in the main scene. If there are any concepts you think might be worth studying, please let me know. Im guessing the example you gave could be described as a design pattern? Once again, thanks for the help.