Merge pull request #129 from thecodingmachine/rework_login_scene

Adding a new SelectCharacterScene
This commit is contained in:
David Négrier 2020-05-27 09:39:47 +02:00 committed by GitHub
commit bab833760d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 300 additions and 154 deletions

3
front/.gitignore vendored
View file

@ -1,3 +1,6 @@
/node_modules/
/dist/bundle.js
/yarn-error.log
/dist/webpack.config.js
/dist/webpack.config.js.map
/dist/src

View file

@ -2,8 +2,9 @@
export class TextInput extends Phaser.GameObjects.BitmapText {
private underLineLength = 10;
private underLine: Phaser.GameObjects.Text;
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number) {
super(scene, x, y, 'main_font', '', 32);
constructor(scene: Phaser.Scene, x: number, y: number, maxLength: number, text: string, onChange: (text: string) => void) {
super(scene, x, y, 'main_font', text, 32);
this.scene.add.existing(this);
this.underLine = this.scene.add.text(x, y+1, '_______', { fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'})
@ -17,6 +18,7 @@ export class TextInput extends Phaser.GameObjects.BitmapText {
} else if ((event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode <= 90)) && this.text.length < maxLength) {
this.addLetter(event.key);
}
onChange(this.text);
});
}
@ -38,6 +40,4 @@ export class TextInput extends Phaser.GameObjects.BitmapText {
getText(): string {
return this.text;
}
}

View 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: "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: "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: "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: "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 {

View file

@ -6,12 +6,11 @@ import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserPositionInterface} from "../../Connection";
import {SelectCharacterSceneInitDataInterface, SelectCharacterSceneName} from "./SelectCharacterScene";
//todo: put this constants in a dedicated file
export const LoginSceneName = "LoginScene";
enum LoginTextures {
//playButton = "play_button",
icon = "icon",
mainFont = "main_font"
}
@ -19,19 +18,18 @@ enum LoginTextures {
export class LoginScene extends Phaser.Scene {
private nameInput: TextInput;
private textField: TextField;
private playButton: ClickButton;
private infoTextField: TextField;
private pressReturnField: TextField;
private logo: Image;
private selectedRectangle: Rectangle;
private selectedPlayer: Phaser.Physics.Arcade.Sprite;
private players: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private name: string;
constructor() {
super({
key: LoginSceneName
});
if (window.localStorage) {
this.name = window.localStorage.getItem('playerName') ?? '';
}
}
preload() {
@ -56,13 +54,16 @@ export class LoginScene extends Phaser.Scene {
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
this.textField.setOrigin(0.5).setCenterAlign()
this.nameInput = new TextInput(this, this.game.renderer.width / 2 - 64, 70, 4);
this.nameInput = new TextInput(this, this.game.renderer.width / 2 - 64, 70, 4, this.name,(text: string) => {
this.name = text;
if (window.localStorage) {
window.localStorage.setItem('playerName', text);
}
});
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Press enter to start');
this.pressReturnField.setOrigin(0.5).setCenterAlign()
this.selectedRectangle = this.add.rectangle(32, 32, 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);
@ -70,100 +71,24 @@ export class LoginScene extends Phaser.Scene {
this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText);
this.input.keyboard.on('keyup-ENTER', () => {
let name = this.nameInput.getText();
if (name === '') {
if (this.name === '') {
return
}
return this.login(name);
this.login(this.name);
});
/*create user*/
this.createCurrentPlayer();
cypressAsserter.initFinished();
}
update(time: number, delta: number): void {
if (this.nameInput.getText() == '') {
if (this.name == '') {
this.pressReturnField.setVisible(false);
} else {
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];
}
Map: Phaser.Tilemaps.Tilemap;
initAnimation(): void {
throw new Error("Method not implemented.");
}
createCurrentPlayer(): void {
for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
let playerResource = PLAYER_RESOURCES[i];
let player = this.physics.add.sprite(playerResource.x, playerResource.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);
private login(name: string): void {
this.scene.start(SelectCharacterSceneName, { name } as SelectCharacterSceneInitDataInterface);
}
}

View file

@ -0,0 +1,214 @@
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"
}
export interface SelectCharacterSceneInitDataInterface {
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 }: SelectCharacterSceneInitDataInterface) {
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();
if (window.localStorage) {
let playerNumberStr: string = window.localStorage.getItem('selectedPlayer') ?? '0';
let playerNumber: number = Number(playerNumberStr);
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;
this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow);
this.updateSelectedPlayer();
}
}
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 col = i % this.nbCharactersPerRow;
let row = Math.floor(i / this.nbCharactersPerRow);
let [x, y] = this.getCharacterPosition(col, row);
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.selectedRectangleXPos = col;
this.selectedRectangleYPos = row;
this.updateSelectedPlayer();
});
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;
if (window.localStorage) {
window.localStorage.setItem('selectedPlayer', String(playerNumber));
}
}
}

View file

@ -1,14 +1,5 @@
import {Textures} from "../Game/GameScene";
interface AnimationData {
key: string;
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
}
export enum PlayerAnimationNames {
WalkDown = 'down',
WalkLeft = 'left',
@ -16,34 +7,4 @@ export enum PlayerAnimationNames {
WalkRight = 'right',
}
export const getPlayerAnimations = (name: string = Textures.Player): AnimationData[] => {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
frameRate: 10,
repeat: -1
}];
};

View file

@ -1,4 +1,4 @@
import {getPlayerAnimations, PlayerAnimationNames} from "./Animation";
import {PlayerAnimationNames} from "./Animation";
import {GameScene, Textures} from "../Game/GameScene";
import {MessageUserPositionInterface, PointInterface} from "../../Connection";
import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
@ -17,6 +17,16 @@ export interface GamerInterface extends PlayableCaracter{
say(text : string) : void;
}
interface AnimationData {
key: string;
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
}
export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface {
userId: string;
userInputManager: UserInputManager;
@ -49,7 +59,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G
}
private initAnimation(): void {
getPlayerAnimations(this.PlayerTexture).forEach(d => {
this.getPlayerAnimations(this.PlayerTexture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
@ -59,6 +69,38 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G
})
}
private getPlayerAnimations(name: string): AnimationData[] {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
frameRate: 10,
repeat: -1
}];
}
moveUser(delta: number): void {
//if user client on shift, camera and player speed
let direction = null;

View 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: [LoginScene, SelectCharacterScene, ReconnectingScene],
zoom: RESOLUTION,
physics: {
default: "arcade",