Adding a new SelectCharacterScene
authorDavid Négrier <d.negrier@thecodingmachine.com>
Mon, 25 May 2020 21:26:27 +0000 (23:26 +0200)
committerDavid Négrier <d.negrier@thecodingmachine.com>
Mon, 25 May 2020 21:26:27 +0000 (23:26 +0200)
Taking what was done in the LoginScene regarding character selection and putting it in its own scene.
Also, making character selection possible via the keyboard.

front/src/Phaser/Entity/PlayableCaracter.ts
front/src/Phaser/Login/SelectCharacterScene.ts [new file with mode: 0644]
front/src/index.ts

index 826bfc6a91dfa125cf9b8cda73436d46633416d0..e1b774efb1e7741e2e8c926940c6f9537d3bdd0e 100644 (file)
@@ -3,25 +3,25 @@ import {SpeechBubble} from "./SpeechBubble";
 import BitmapText = Phaser.GameObjects.BitmapText;
 
 export const PLAYER_RESOURCES: Array<any> = [
-    {name: "male1", img: "resources/characters/pipoya/Male 01-1.png", x: 32, y: 32},
-    {name: "male2", img: "resources/characters/pipoya/Male 02-2.png", x: 64, y: 32},
-    {name: "male3", img: "resources/characters/pipoya/Male 03-4.png", x: 96, y: 32},
-    {name: "male4", img: "resources/characters/pipoya/Male 09-1.png", x: 128, y: 32},
-
-    {name: "male5", img: "resources/characters/pipoya/Male 10-3.png", x: 32, y: 64},
-    {name: "male6", img: "resources/characters/pipoya/Male 17-2.png", x: 64, y: 64},
-    {name: "male7", img: "resources/characters/pipoya/Male 18-1.png", x: 96, y: 64},
-    {name: "male8", img: "resources/characters/pipoya/Male 16-4.png", x: 128, y: 64},
-
-    {name: "Female1", img: "resources/characters/pipoya/Female 01-1.png", x: 32, y: 96},
-    {name: "Female2", img: "resources/characters/pipoya/Female 02-2.png", x: 64, y: 96},
-    {name: "Female3", img: "resources/characters/pipoya/Female 03-4.png", x: 96, y: 96},
-    {name: "Female4", img: "resources/characters/pipoya/Female 09-1.png", x: 128, y: 96},
-
-    {name: "Female5", img: "resources/characters/pipoya/Female 10-3.png", x: 32, y: 128},
-    {name: "Female6", img: "resources/characters/pipoya/Female 17-2.png", x: 64, y: 128},
-    {name: "Female7", img: "resources/characters/pipoya/Female 18-1.png", x: 96, y: 128},
-    {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png", x: 128, y: 128}
+    {name: "male1", img: "resources/characters/pipoya/Male 01-1.png" /*, x: 32, y: 32*/},
+    {name: "male2", img: "resources/characters/pipoya/Male 02-2.png"/*, x: 64, y: 32*/},
+    {name: "male3", img: "resources/characters/pipoya/Male 03-4.png"/*, x: 96, y: 32*/},
+    {name: "male4", img: "resources/characters/pipoya/Male 09-1.png"/*, x: 128, y: 32*/},
+
+    {name: "male5", img: "resources/characters/pipoya/Male 10-3.png"/*, x: 32, y: 64*/},
+    {name: "male6", img: "resources/characters/pipoya/Male 17-2.png"/*, x: 64, y: 64*/},
+    {name: "male7", img: "resources/characters/pipoya/Male 18-1.png"/*, x: 96, y: 64*/},
+    {name: "male8", img: "resources/characters/pipoya/Male 16-4.png"/*, x: 128, y: 64*/},
+
+    {name: "Female1", img: "resources/characters/pipoya/Female 01-1.png"/*, x: 32, y: 96*/},
+    {name: "Female2", img: "resources/characters/pipoya/Female 02-2.png"/*, x: 64, y: 96*/},
+    {name: "Female3", img: "resources/characters/pipoya/Female 03-4.png"/*, x: 96, y: 96*/},
+    {name: "Female4", img: "resources/characters/pipoya/Female 09-1.png"/*, x: 128, y: 96*/},
+
+    {name: "Female5", img: "resources/characters/pipoya/Female 10-3.png"/*, x: 32, y: 128*/},
+    {name: "Female6", img: "resources/characters/pipoya/Female 17-2.png"/*, x: 64, y: 128*/},
+    {name: "Female7", img: "resources/characters/pipoya/Female 18-1.png"/*, x: 96, y: 128*/},
+    {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"/*, x: 128, y: 128*/}
 ];
 
 export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite {
diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts
new file mode 100644 (file)
index 0000000..56dcb8a
--- /dev/null
@@ -0,0 +1,203 @@
+import {gameManager} from "../Game/GameManager";
+import {TextField} from "../Components/TextField";
+import {ClickButton} from "../Components/ClickButton";
+import Image = Phaser.GameObjects.Image;
+import Rectangle = Phaser.GameObjects.Rectangle;
+import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
+
+//todo: put this constants in a dedicated file
+export const SelectCharacterSceneName = "SelectCharacterScene";
+enum LoginTextures {
+    playButton = "play_button",
+    icon = "icon",
+    mainFont = "main_font"
+}
+
+interface InitDataInterface {
+    name: string
+}
+
+export class SelectCharacterScene extends Phaser.Scene {
+    private readonly nbCharactersPerRow = 4;
+    private textField: TextField;
+    private pressReturnField: TextField;
+    private logo: Image;
+    private loginName: string;
+
+    private selectedRectangle: Rectangle;
+    private selectedRectangleXPos = 0; // Number of the character selected in the rows
+    private selectedRectangleYPos = 0; // Number of the character selected in the columns
+    private selectedPlayer: Phaser.Physics.Arcade.Sprite;
+    private players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
+
+    constructor() {
+        super({
+            key: SelectCharacterSceneName
+        });
+    }
+
+    init({ name }: InitDataInterface) {
+        this.loginName = name;
+    }
+
+    preload() {
+        this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
+        this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
+        // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
+        this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
+        //add player png
+        PLAYER_RESOURCES.forEach((playerResource: any) => {
+            this.load.spritesheet(
+                playerResource.name,
+                playerResource.img,
+                {frameWidth: 32, frameHeight: 32}
+            );
+        });
+    }
+
+    create() {
+        this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character');
+        this.textField.setOrigin(0.5).setCenterAlign()
+
+        this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 230, 'Press enter to start');
+        this.pressReturnField.setOrigin(0.5).setCenterAlign()
+
+        let rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
+
+        this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
+
+        this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
+        this.add.existing(this.logo);
+
+        this.input.keyboard.on('keyup-ENTER', () => {
+            return this.login(this.loginName);
+        });
+
+        this.input.keyboard.on('keydown-RIGHT', () => {
+            if (this.selectedRectangleXPos < this.nbCharactersPerRow - 1) {
+                this.selectedRectangleXPos++;
+            }
+            this.updateSelectedPlayer();
+        });
+        this.input.keyboard.on('keydown-LEFT', () => {
+            if (this.selectedRectangleXPos > 0) {
+                this.selectedRectangleXPos--;
+            }
+            this.updateSelectedPlayer();
+        });
+        this.input.keyboard.on('keydown-DOWN', () => {
+            if (this.selectedRectangleYPos < Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow) - 1) {
+                this.selectedRectangleYPos++;
+            }
+            this.updateSelectedPlayer();
+        });
+        this.input.keyboard.on('keydown-UP', () => {
+            if (this.selectedRectangleYPos > 0) {
+                this.selectedRectangleYPos--;
+            }
+            this.updateSelectedPlayer();
+        });
+
+        /*create user*/
+        this.createCurrentPlayer();
+    }
+
+    update(time: number, delta: number): void {
+        this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
+    }
+
+    private async login(name: string) {
+        return gameManager.connect(name, this.selectedPlayer.texture.key).then(() => {
+            // Do we have a start URL in the address bar? If so, let's redirect to this address
+            let instanceAndMapUrl = this.findMapUrl();
+            if (instanceAndMapUrl !== null) {
+                let [mapUrl, instance] = instanceAndMapUrl;
+                let key = gameManager.loadMap(mapUrl, this.scene, instance);
+                this.scene.start(key);
+                return mapUrl;
+            } else {
+                // If we do not have a map address in the URL, let's ask the server for a start map.
+                return gameManager.loadStartMap().then((scene : any) => {
+                    if (!scene) {
+                        return;
+                    }
+                    let key = gameManager.loadMap(window.location.protocol + "//" + scene.mapUrlStart, this.scene, scene.startInstance);
+                    this.scene.start(key);
+                    return scene;
+                }).catch((err) => {
+                    console.error(err);
+                    throw err;
+                });
+            }
+        }).catch((err) => {
+            console.error(err);
+            throw err;
+        });
+    }
+
+    /**
+     * Returns the map URL and the instance from the current URL
+     */
+    private findMapUrl(): [string, string]|null {
+        let path = window.location.pathname;
+        if (!path.startsWith('/_/')) {
+            return null;
+        }
+        let instanceAndMap = path.substr(3);
+        let firstSlash = instanceAndMap.indexOf('/');
+        if (firstSlash === -1) {
+            return null;
+        }
+        let instance = instanceAndMap.substr(0, firstSlash);
+        return [window.location.protocol+'//'+instanceAndMap.substr(firstSlash+1), instance];
+    }
+
+    createCurrentPlayer(): void {
+        for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
+            let playerResource = PLAYER_RESOURCES[i];
+
+
+            let [x, y] = this.getCharacterPosition(i % this.nbCharactersPerRow, Math.floor(i / this.nbCharactersPerRow));
+            let player = this.physics.add.sprite(x, y, playerResource.name, playerResource.name);
+            player.setBounce(0.2);
+            player.setCollideWorldBounds(true);
+            this.anims.create({
+                key: playerResource.name,
+                frames: this.anims.generateFrameNumbers(playerResource.name, {start: 0, end: 2,}),
+                frameRate: 10,
+                repeat: -1
+            });
+            player.setInteractive().on("pointerdown", () => {
+                this.selectedPlayer.anims.pause();
+                this.selectedRectangle.setY(player.y);
+                this.selectedRectangle.setX(player.x);
+                player.play(playerResource.name);
+                this.selectedPlayer = player;
+            });
+            this.players.push(player);
+        }
+        this.selectedPlayer = this.players[0];
+        this.selectedPlayer.play(PLAYER_RESOURCES[0].name);
+    }
+
+    /**
+     * Returns pixel position by on column and row number
+     */
+    private getCharacterPosition(x: number, y: number): [number, number] {
+        return [
+            this.game.renderer.width / 2 + 16 + (x - this.nbCharactersPerRow / 2) * 32,
+            y * 32 + 90
+        ];
+    }
+
+    private updateSelectedPlayer(): void {
+        this.selectedPlayer.anims.pause();
+        let [x, y] = this.getCharacterPosition(this.selectedRectangleXPos, this.selectedRectangleYPos);
+        this.selectedRectangle.setX(x);
+        this.selectedRectangle.setY(y);
+        let playerNumber = this.selectedRectangleXPos + this.selectedRectangleYPos * this.nbCharactersPerRow;
+        let player = this.players[playerNumber];
+        player.play(PLAYER_RESOURCES[playerNumber].name);
+        this.selectedPlayer = player;
+    }
+}
index 5ece7d56fad74d0c2f2893974c2c1acf8d1edb44..ab374c2ff93a10463ee713ba29590e6a7b60c303 100644 (file)
@@ -5,13 +5,14 @@ import {cypressAsserter} from "./Cypress/CypressAsserter";
 import {LoginScene} from "./Phaser/Login/LoginScene";
 import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
 import {gameManager} from "./Phaser/Game/GameManager";
+import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
 
 const config: GameConfig = {
     title: "Office game",
     width: window.innerWidth / RESOLUTION,
     height: window.innerHeight / RESOLUTION,
     parent: "game",
-    scene: [LoginScene, ReconnectingScene],
+    scene: [SelectCharacterScene, LoginScene, ReconnectingScene],
     zoom: RESOLUTION,
     physics: {
         default: "arcade",