How to build keyboard and mouse control UI with rectangles

Hi there,

I’m trying to figure out how to build a cursor and mouse based UI similar to minecraft, but with both mouse and cursor presses.

I had a look to see if any similar questions have been asked, and I came across a thread with this example.

https://codepen.io/yandeu/pen/aMympQ?editors=0010

That works similar to what I’m looking for, except rectangles could be used… Trying to recode TS into JS is a challenge for me.

The example uses Typescript, and I haven’t gotten around to learning it… In my attempt, I used a for loop.

let rect = null

export class GameScene extends Phaser.Scene {

    constructor() {
        super('Game')
    }

    create() {
        let fillColor = Phaser.Display.Color.HexStringToColor('#496A81').color;
        let selectFillColor = Phaser.Display.Color.HexStringToColor('#66999B').color;
        let strokeColor = Phaser.Display.Color.HexStringToColor('#B3AF8F').color;

        rect = []

        let lineWidth = 4

        for (let i = 0; i < 6; i++) {
            rect[i] = this.add.rectangle(40 * i, 200, 30, 30)

            rect[i].setOrigin(0, 0)
            rect[i].setInteractive()
            rect[i].setFillStyle(fillColor, 1)
            rect[i].setStrokeStyle(lineWidth, strokeColor, 1);
        }

        // let selectedTile = null
        // selectedTile = 2

        // rect[selectedTile].setFillStyle(selectFillColor, 1)

        // let keyObj = this.input.keyboard.addKey('D');  // Get key object

        // keyObj.on('down', function (event) {
        //     for (let i = 0; i < rect.length; i++) {
        //         rect[i].setFillStyle(selectFillColor, 1)
        // }

        console.log(rect);
        // rect[0].setFillStyle(selectFillColor, 1)
        // rect = this.add.rectangle(200, 200, 200, 50)
    }

    update(time, delta) {
    }
}

If anyone can lend me help about how I could accomplish this, I would really appreciate it.

I will make sure to make updates about my progress. Thanks.

In the JS (Typescript) view, you can choose ‘View Compiled JS’, no need to recode.

1 Like

Oh thanks, thats very handy. The code, for me, is a bit more complex than I anticipated. So I tried to rework it in Js. Heres what I got so far:

let normalStyle = {
    fontSize: 52,
    color: "#ffffff"

}

let activeStyle = {
    fontSize: 30,
    color: '#ffff00'
}

let isActive = null

export class GameUi extends Phaser.GameObjects.Text {
    constructor(scene, x, y, text, index) {
        super(scene, x, y, text, {}) || this
        this.index = index;

        isActive = true
        scene.add.existing(this)

        this.setStyle(normalStyle)
            .setInteractive()
            .on('pointerover', () => {
                return scene.events.emit('CHANGE_BUTTON', { setIndex: index })
            })
            this.setStyleActive(index === 0)
    }

    setStyleActive(active) {
        if (isActive === active) {
            isActive = active

            this.setStyle(isActive ? activeStyle : normalStyle)
        }
    }
}

import { GameUi } from '../classes/GameUi.js'

let activeText = null
let textGroup = null
let texts = null

export class GameScene extends Phaser.Scene {

    constructor() {
        super('Game')

        activeText = 0
        textGroup = []

        texts = ['Text1', 'Text2', 'Text3', 'Text4']
    }

    create() {
        texts.forEach((text, index) => {
            textGroup.push(new GameUi(
                this,
                this.cameras.main.width / 2,
                100 + 80 * index + 1,
                text,
                index
            ))
        })

        this.input.keyboard.on('keydown', (event) => {
            switch (event.key) {
                case 'ArrowUp':
                    activeText -= 1;
                    console.log(activeText);
                    this.events.emit('CHANGE_BUTTON');
                    break;
                case 'ArrowDown':
                    activeText += 1
                    console.log(activeText);
                    this.events.emit('CHANGE_BUTTON')
                    break;
            }
        })

        this.events.addListener('CHANGE_BUTTON', (payload) => {
            console.log(payload);
            if (activeText < 0) {
                activeText += texts.length
            }

            if (payload && typeof payload.setIndex !== 'undefined') {

                activeText = payload.setIndex;

                textGroup.forEach((text) => {
                    text.setStyleActive(text.index === activeText % texts)

                })
            }
        })

        // let fillColor = Phaser.Display.Color.HexStringToColor('#496A81').color;

        // let selectFillColor = Phaser.Display.Color.HexStringToColor('#66999B').color;

        // let strokeColor = Phaser.Display.Color.HexStringToColor('#B3AF8F').color;

        // rect = []
        // let lineWidth = 4

        // for (let i = 0; i < 6; i++) {
        //     rect[i] = this.add.rectangle(40 * i, 200, 30, 30)
        //     rect[i].setOrigin(0, 0)

        //     rect[i].setInteractive()
        //     rect[i].setFillStyle(fillColor, 1)
        //     rect[i].setStrokeStyle(lineWidth, strokeColor, 1);
        // }
        // let selectedTile = null
        // selectedTile = 2
        // rect[selectedTile].setFillStyle(selectFillColor, 1)
        // let keyObj = this.input.keyboard.addKey('D');  // Get key object

        // keyObj.on('down', function (event) {
        //     for (let i = 0; i < rect.length; i++) {
        //         rect[i].setFillStyle(selectFillColor, 1)
        // }

        // rect[0].setFillStyle(selectFillColor, 1)
        // rect = this.add.rectangle(200, 200, 200, 50)
    }

    update(time, delta) {
    }
}

It doesn’t work as intended. I wouldn’t know how to rework the compiled JS well enough for it to work, and it be easier for me to grasp.

I haven’t emitted events before, so I’m going to try to break it down so I can understand it better. If anyone has any helpful suggestions, I’d very much appreciate it. I’m going to continue looking over the code and I’ll keep posting updates as I go along.

setStyleActive seems wrong. ‘===’ --> = makes no sense.

I still can’t figure out what the code to that example is doing, and how to implement it into javascript, let alone make it utilize rects instead of text. Seeing the compiled js isn’t really helping me much. If someone could guide me through the process, or make suggestions, I’d really appreciate. I’m stumped.

let normalFill = 0x6666ff;
let activeFill = 0xffffff;

export class GameUi extends Phaser.GameObjects.Rectangle {

    constructor(scene, x, y, width, height, fillColor, alpha, index) {
        super(scene, x, y, width, height, fillColor, alpha, index);
        // ...
        this.isFilled = true

        this.setStrokeStyle(4, 0xefc53f);
        this.setFillStyle(normalFill, 1)
            .setInteractive()
            .on('pointerover', () => {
                scene.events.emit('CHANGE_BUTTON', { setIndex: index})
            })
        scene.add.existing(this);

        // this.setStyleActive(index === 0)
    }

    setStyleActive() {
        // if (this.isActive === active) {
        //     this.isActive = active
        //     this.setStyleActive(this.isActive ? this.setFillStyle(activeFill, 1) : this.setFillStyle(normalFill, 1))

        // }
    }
    // ...
    // preUpdate(time, delta) {}
}

import { GameUi } from '../classes/GameUi.js'

let rects = [1,2,3,4]

export class GameScene extends Phaser.Scene {
    constructor() {
        super('Game')
    }

    create() {

        let activeRect = 0
        let rectGroup = []

        rects.forEach(index => {
            rectGroup.push(new GameUi(this, this.cameras.main.width / 2, 10 + 120 * index + 1, 200, 100, 1, index))
        });

        this.input.keyboard.on('keydown', event => {
            switch (event.key) {
              case 'ArrowUp':
                activeRect -= 1
                this.events.emit('CHANGE_BUTTON')
                break
              case 'ArrowDown':
                activeRect += 1
                this.events.emit('CHANGE_BUTTON')
                break
            }
          })
        this.events.addListener('CHANGE_BUTTON', (payload) => {

            console.log(payload);

            if (activeRect < 0) {
                activeRect += rects.length;
            }

            // if (payload && typeof payload.setIndex != 'undefined') {
            //     activeRect = payload.setIndex;
            //     rectGroup.forEach((rect) => {
            //         rect.setStyleActive(rect.index === activeRect % rects.length)
            //     })
            // }
        })
        console.log(rects);
    }

    update(time, delta) {
    }
}

My lack of knowledge regarding Typescript probably doesnt help either.

Here’s the JS version. Maybe that helps you along a bit for now, I am short on time at the moment…

1 Like

Thanks so much once again, I’ve managed to utilize that code but using rectangles instead of text. I’m going to break down every part of that code so I can understand it all a bit better. Heres what I managed to get:

// let normalStyle = {
//     fontSize: 52,
//     color: "#ffffff"
// }
// let activeStyle = {
//     fontSize: 30,
//     color: '#ffff00'
// }

// let rgb = Phaser.Display.Color.IntegerToRGB({r: 200, g: 100, b: 80});

export class GameUi extends Phaser.GameObjects.Rectangle {


    constructor(scene, x, y, width, height, index) {
        super(scene, x, y, width, height, index);
        // ...

        this.index = index
        this.normalFill = 0x6666ff
        this.activeFill = 0xffffff

        this.isFilled = true

        this.isActive = false
        scene.add.existing(this);

        this.setStrokeStyle(4, 0xefc53f);
        this.setFillStyle(this.normalFill, 1)
            .setInteractive()
            .on('pointerover', () => {
                scene.events.emit('CHANGE_BUTTON', { setIndex: index})
            })

        this.setStyleActive(index === 0)
    }

    setStyleActive(active) {

        if (this.isActive === active)
            return;
        this.isActive = active;
        this.setFillStyle(this.isActive ? this.activeFill : this.normalFill, 1);

    }
    // ...
    // preUpdate(time, delta) {}
}


import { GameUi } from '../classes/GameUi.js'

export class GameScene extends Phaser.Scene {
    constructor() {
        super('Game')
    }
    create() {
        let _this = this
        let activeRect = 0
        let rectGroup = []
        let rects = [0, 1, 2, 3, 4]

        rects.forEach(index => {
            rectGroup.push(new GameUi(_this, this.cameras.main.width / 2, 10 + 120 * index + 1, 200, 100, index))
        });

        this.input.keyboard.on('keydown', event => {
            switch (event.key) {
              case 'ArrowUp':
                activeRect -= 1
                _this.events.emit('CHANGE_BUTTON')
                break
              case 'ArrowDown':
                activeRect += 1
                _this.events.emit('CHANGE_BUTTON')
                break
            }

            console.log(activeRect);
          })

        this.events.addListener('CHANGE_BUTTON', (payload) => {
            if (activeRect < 0) {
                activeRect += rects.length;
            }

            if (payload && typeof payload.setIndex !== 'undefined')
                activeRect = payload.setIndex;
                rectGroup.forEach((rect) => {
                    rect.setStyleActive(rect.index == activeRect % rects.length)
                })
                console.log(payload);
        })

        console.log(rects);

    }
    update(time, delta) {
    }
}

I’ll update this post once I get this functioning how I want to. Thanks so much!