Merge branch 'master' into feature/back-players-proximity

# Conflicts:
#	back/src/Model/Websocket/MessageUserPosition.ts
This commit is contained in:
gparant 2020-04-26 23:31:40 +02:00
commit 9730df2295
47 changed files with 3083 additions and 254 deletions

1
.env.template Normal file
View File

@ -0,0 +1 @@
DEBUG_MODE=false

102
.github/workflows/build-and-deploy.yml vendored Normal file
View File

@ -0,0 +1,102 @@
name: Build, push and deploy Docker image
on:
- push
# Enables BuildKit
env:
DOCKER_BUILDKIT: 1
jobs:
build-front:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@master
- name: "Build and push front image"
uses: docker/build-push-action@v1
with:
dockerfile: front/Dockerfile
path: front/
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-front
tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true
build-back:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@master
- name: "Build and push back image"
uses: docker/build-push-action@v1
with:
dockerfile: back/Dockerfile
path: back/
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-back
tags: ${{ env.GITHUB_REF_SLUG }}
add_git_labels: true
deeploy:
needs:
- build-front
- build-back
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@1.1.0
- name: Deploy
uses: thecodingmachine/deeployer@master
env:
KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }}
with:
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
- name: Add a comment in PR
uses: unsplash/comment-on-pr@v1.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
check_for_duplicate_msg: true
- name: Run Cypress tests
uses: cypress-io/github-action@v1
env:
CYPRESS_BASE_URL: https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
with:
env: host=${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80
spec: cypress/integration/spec.js
wait-on: https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
working-directory: e2e
- name: "Upload the screenshot on test failure"
uses: actions/upload-artifact@v1
if: failure()
with:
name: "screenshot"
path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png"

View File

@ -65,3 +65,4 @@ jobs:
- name: "Jasmine"
run: yarn test
working-directory: "back"

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.env
.idea
.vagrant
Vagrantfile

View File

@ -32,6 +32,19 @@ Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
workadventure.localhost 127.0.0.1
```
## Designing a map
If you want to design your own map, you can use [Tiled](https://www.mapeditor.org/).
A few things to notice:
- your map can have as many layers as your want
- your map MUST contain a layer named "floorLayer" of type "objectgroup" that represents the layer on which characters will be drawn.
- the tilesets in your map MUST be embedded. You can refer to an external typeset in a TSX file. Click the "embed tileset" button in the tileset tab to embed tileset data.
- your map MUST be exported in JSON format. You need to use a recent version of Tiled to get JSON format export (1.3+)
![](doc/images/tiled_screenshot_1.png)
### MacOS developers, your environment with Vagrant
If you are using MacOS, you can increase Docker performance using Vagrant. If you want more explanations, you can read [this medium article](https://medium.com/better-programming/vagrant-to-increase-docker-performance-with-macos-25b354b0c65c).
@ -98,4 +111,4 @@ Vagrant destroy
* `Vagrant destroy`: delete your VM Vagrant.
## Features developed
You have more details of features developed in back [README.md](./back/README.md).
You have more details of features developed in back [README.md](./back/README.md).

5
back/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
/dist/
/node_modules/
/dist/bundle.js
/yarn-error.log
/Dockerfile

9
back/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM thecodingmachine/nodejs:12
COPY --chown=docker:docker . .
RUN yarn install
ENV NODE_ENV=production
CMD ["yarn", "run", "prod"]

View File

@ -25,13 +25,15 @@
"@types/http-status-codes": "^1.2.0",
"@types/jsonwebtoken": "^8.3.8",
"@types/socket.io": "^2.1.4",
"@types/uuidv4": "^5.0.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"http-status-codes": "^1.4.0",
"jsonwebtoken": "^8.5.1",
"socket.io": "^2.3.0",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.8.3"
"typescript": "^3.8.3",
"uuidv4": "^6.0.7"
},
"devDependencies": {
"@types/jasmine": "^3.5.10",

View File

@ -2,6 +2,7 @@ import {Application, Request, Response} from "express";
import Jwt from "jsonwebtoken";
import {BAD_REQUEST, OK} from "http-status-codes";
import {SECRET_KEY, ROOM} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import { uuid } from 'uuidv4';
export class AuthenticateController{
App : Application;
@ -21,8 +22,13 @@ export class AuthenticateController{
});
}
//TODO check user email for The Coding Machine game
let token = Jwt.sign({email: param.email, roomId: ROOM}, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({token: token, roomId: ROOM});
let userId = uuid();
let token = Jwt.sign({email: param.email, roomId: ROOM, userId: userId}, SECRET_KEY, {expiresIn: '24h'});
return res.status(OK).send({
token: token,
roomId: ROOM,
userId: userId
});
});
}
}

View File

@ -104,7 +104,8 @@ export class IoSocketController{
roomId: <string>,
position: {
x : <number>,
y : <number>
y : <number>,
direction: <string>
}
},
...
@ -125,8 +126,7 @@ export class IoSocketController{
}
arrayMap.forEach((value : any) => {
let roomId = value[0];
let data = value[1];
this.Io.in(roomId).emit('user-position', JSON.stringify(data));
this.Io.in(roomId).emit('user-position', JSON.stringify(arrayMap));
});
this.seTimeOutInProgress = setTimeout(() => {
this.shareUsersPosition();

View File

@ -4,19 +4,22 @@ import {PointInterface} from "./PointInterface";
export class Point implements PointInterface{
x: number;
y: number;
direction: string;
constructor(x : number, y : number) {
constructor(x : number, y : number, direction : string = "none") {
if(x === null || y === null){
throw Error("position x and y cannot be null");
}
this.x = x;
this.y = y;
this.direction = direction;
}
toJson(){
return {
x : this.x,
y: this.y
y: this.y,
direction: this.direction
}
}
}
@ -24,9 +27,10 @@ export class Point implements PointInterface{
export class MessageUserPosition extends Message{
position: PointInterface;
constructor(data: any) {
super(data);
this.position = new Point(data.position.x, data.position.y);
constructor(message: string) {
super(message);
let data = JSON.parse(message);
this.position = new Point(data.position.x, data.position.y, data.position.direction);
}
toString() {

View File

@ -1,5 +1,6 @@
export interface PointInterface {
x: number;
y: number;
direction: string;
toJson() : object;
}

View File

@ -135,6 +135,13 @@
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
"@types/uuidv4@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/uuidv4/-/uuidv4-5.0.0.tgz#2c94e67b0c06d5adb28fb7ced1a1b5f0866ecd50"
integrity sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A==
dependencies:
uuidv4 "*"
"@typescript-eslint/eslint-plugin@^2.26.0":
version "2.26.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.26.0.tgz#04c96560c8981421e5a9caad8394192363cc423f"
@ -2094,6 +2101,18 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
uuidv4@*, uuidv4@^6.0.7:
version "6.0.7"
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.0.7.tgz#15e920848e1afbbd97b4919bc50f4f2f2278f880"
integrity sha512-4mpYRFNqO22EckzxPSJ/+xjn9GgO6SAqEJ33yt23Y+HZZoZOt/6l4U4iIjc86ZfxSN2fSCGGmHNb3kiACFNd1g==
dependencies:
uuid "7.0.3"
v8-compile-cache@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"

35
deeployer.libsonnet Normal file
View File

@ -0,0 +1,35 @@
{
local env = std.extVar("env"),
local namespace = env.GITHUB_REF_SLUG,
local tag = if namespace == "master" then "latest" else namespace,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"containers": {
"back": {
"image": "thecodingmachine/workadventure-back:"+tag,
"host": {
"url": "api."+namespace+".workadventure.test.thecodingmachine.com",
"https": "enable"
},
"ports": [8080],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange"
}
},
"front": {
"image": "thecodingmachine/workadventure-front:"+tag,
"host": {
"url": namespace+".workadventure.test.thecodingmachine.com",
"https": "enable"
},
"ports": [80],
"env": {
"API_URL": "https://api."+namespace+".workadventure.test.thecodingmachine.com"
}
}
},
"config": {
"https": {
"mail": "d.negrier@thecodingmachine.com"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

20
docker-compose.ci.yml Normal file
View File

@ -0,0 +1,20 @@
version: '3'
services:
wait_app:
image: dadarek/wait-for-dependencies
depends_on:
- reverse-proxy
command: front:8080
cypress:
# the Docker image to use from https://github.com/cypress-io/cypress-docker-images
image: "cypress/included:3.8.3"
depends_on:
- reverse-proxy
environment:
# pass base url to test pointing at the web application
- CYPRESS_baseUrl=http://front:8080
working_dir: /e2e
volumes:
- ./e2e/:/e2e

View File

@ -7,12 +7,16 @@ services:
- "80:80"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
depends_on:
- back
- front
volumes:
- /var/run/docker.sock:/var/run/docker.sock
front:
image: thecodingmachine/nodejs:12
environment:
DEBUG_MODE: "$DEBUG_MODE"
HOST: "0.0.0.0"
NODE_ENV: development
API_URL: http://api.workadventure.localhost

3
e2e/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
screenshots/
videos/
node_modules/

36
e2e/CYPRESS.md Normal file
View File

@ -0,0 +1,36 @@
# Testing with cypress
This project use [cypress](https://www.cypress.io/) to do functional testing of the website.
Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc.
## Getting Started
You will need to install theses dependancies on linux (don't know about mac):
```bash
sudo apt update
sudo apt install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
```
Cypress can be installed locally in the e2e directory
```bash
cd e2e
npm install
```
How to use:
```bash
npm run cy:run
npm run cy:open
```
The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow
[More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server)
## How to test a game
Cypress cannot "see" and so cannot directly manipulate the canva created by Phaser.
This means we have to do workarounds such as exposing core objects in the window so that cypress can manipulate them or doing console that cypress can catch.

7
e2e/cypress.json Normal file
View File

@ -0,0 +1,7 @@
{
"baseUrl": "http://workadventure.localhost",
"video": false,
"defaultCommandTimeout": 20000,
"pluginsFile": false,
"supportFile": false
}

View File

@ -0,0 +1,25 @@
Cypress.on('window:before:load', (win) => {
// because this is called before any scripts
// have loaded - the ga function is undefined
// so we need to create it.
win.cypressAsserter = cy.stub().as('ca')
})
describe('WorkAdventureGame', () => {
beforeEach(() => {
cy.visit('/', {
onBeforeLoad (win) {
cy.spy(win.console, 'log').as('console.log')
},
})
});
it('loads', () => {
cy.get('@console.log').should('be.calledWith', 'Started the game')
cy.get('@console.log').should('be.calledWith', 'Preloading')
cy.get('@console.log').should('be.calledWith', 'Preloading done')
cy.get('@console.log').should('be.calledWith', 'startInit')
cy.get('@console.log').should('be.calledWith', 'startInit done')
});
});

1406
e2e/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
e2e/package.json Normal file
View File

@ -0,0 +1,9 @@
{
"dependencies": {
"cypress": "^3.8.3"
},
"scripts": {
"cy:run": "cypress run",
"cy:open": "cypress open"
}
}

4
front/.dockerignore Normal file
View File

@ -0,0 +1,4 @@
/node_modules/
/dist/bundle.js
/yarn-error.log
/Dockerfile

9
front/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
# we are rebuilding on each deploy to cope with the API_URL environment URL
FROM thecodingmachine/nodejs:12-apache
COPY --chown=docker:docker . .
RUN yarn install
ENV NODE_ENV=production
ENV STARTUP_COMMAND_1="yarn run build"
ENV APACHE_DOCUMENT_ROOT=dist/

BIN
front/dist/maps/floortileset.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -3,33 +3,45 @@
{
"export":
{
"target":"."
"format":"json",
"target":"map.json"
}
},
"height":20,
"height":18,
"infinite":false,
"layers":[
{
"data":[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 1, 2, 2, 2, 2, 2, 2, 3, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 17, 18, 18, 18, 18, 18, 18, 19, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 17, 18, 18, 18, 18, 18, 18, 19, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 17, 18, 18, 18, 18, 18, 18, 19, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 33, 34, 34, 34, 34, 34, 34, 35, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178],
"height":20,
"id":1,
"name":"Calque 1",
"data":[294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 294, 294, 294, 294, 294, 294, 294, 294, 294],
"height":18,
"id":2,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":20,
"width":46,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 62, 62, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 94, 94, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 110, 110, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 209, 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 193, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 195, 164, 209, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 211],
"height":20,
"draworder":"topdown",
"id":3,
"name":"Calque 2",
"name":"floorLayer",
"objects":[],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"data":[0, 0, 115, 51, 52, 116, 115, 51, 52, 116, 115, 51, 52, 116, 0, 0, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 245, 246, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 67, 68, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 245, 246, 247, 0, 0, 153, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 169, 171, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 29, 30, 29, 30, 0, 0, 0, 0, 185, 187, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 30, 0, 0, 0, 0, 0, 98, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 116, 0, 0, 51, 52, 116, 0, 0, 0, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 181, 0, 0, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 0, 0, 115, 67, 68, 0, 0, 0, 0, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 45, 46, 197, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 52, 0, 0, 0, 113, 0, 113, 0, 113, 0, 0, 51, 49, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 50, 0, 0, 0, 0, 0, 51, 49, 50, 49, 50, 49, 50, 0, 115, 67, 68, 116, 0, 49, 50, 49, 50, 49, 50, 0, 115, 67, 65, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 0, 0, 0, 0, 115, 67, 65, 66, 65, 66, 65, 66, 0, 0, 51, 52, 0, 0, 65, 66, 65, 66, 65, 66, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 67, 68, 116, 0, 0, 0, 0, 0, 114, 0, 114, 0, 114, 0, 0, 115, 67, 68, 116, 0, 114, 0, 114, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":18,
"id":1,
"name":"top",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":20,
"width":46,
"x":0,
"y":0
}],
@ -41,20 +53,34 @@
"tileheight":32,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles.png",
"imageheight":512,
"imagewidth":512,
"source":"office_1.tsx"
},
{
"columns":8,
"firstgid":257,
"image":"floortileset.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"tiles",
"name":"floortileset",
"spacing":0,
"tilecount":256,
"tilecount":64,
"tileheight":32,
"tiles":[
{
"id":37,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.2,
"width":20
"width":46
}

File diff suppressed because one or more lines are too long

254
front/dist/maps/office_1.tsx vendored Normal file
View File

@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.2" tiledversion="1.3.3" name="office_1" tilewidth="32" tileheight="32" tilecount="256" columns="16">
<image source="tilesets_deviant_milkian_1.png" width="512" height="512"/>
<tile id="7">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="12">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="13">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="14">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="15">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="23">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="28">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="29">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="30">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="31">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="39">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="44">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="45">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="48">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="49">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="50">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="51">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="52">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="56">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="57">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="64">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="65">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="66">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="67">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="68">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="72">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="73">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="84">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="152">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="153">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="154">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="155">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="156">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="157">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="161">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="162">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="168">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="169">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="170">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="171">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="172">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="173">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="177">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="178">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="184">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="185">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="186">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="196">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="198">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
<tile id="214">
<properties>
<property name="collides" type="bool" value="true"/>
</properties>
</tile>
</tileset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,14 +1,10 @@
import {GameManagerInterface} from "./Phaser/Game/GameManager";
const SocketIo = require('socket.io-client');
import Axios from "axios";
import {API_URL} from "./Enum/EnvironmentVariable";
export interface PointInterface {
x: number;
y: number;
toJson() : object;
}
export class Message {
class Message {
userId: string;
roomId: string;
@ -25,27 +21,42 @@ export class Message {
}
}
export class Point implements PointInterface{
export interface PointInterface {
x: number;
y: number;
direction : string;
toJson() : object;
}
constructor(x : number, y : number) {
class Point implements PointInterface{
x: number;
y: number;
direction : string;
constructor(x : number, y : number, direction : string = "none") {
if(x === null || y === null){
throw Error("position x and y cannot be null");
}
this.x = x;
this.y = y;
this.direction = direction;
}
toJson(){
return {
x : this.x,
y: this.y
y: this.y,
direction: this.direction
}
}
}
export class MessageUserPosition extends Message{
export interface MessageUserPositionInterface {
userId: string;
roomId: string;
position: PointInterface;
}
class MessageUserPosition extends Message implements MessageUserPositionInterface{
position: PointInterface;
constructor(userId : string, roomId : string, point : Point) {
@ -64,18 +75,61 @@ export class MessageUserPosition extends Message{
}
}
export class Connexion {
export interface ListMessageUserPositionInterface {
roomId : string;
listUsersPosition: Array<MessageUserPosition>;
}
class ListMessageUserPosition{
roomId : string;
listUsersPosition: Array<MessageUserPosition>;
constructor(roomId : string, data : any) {
this.roomId = roomId;
this.listUsersPosition = new Array<MessageUserPosition>();
data.forEach((userPosition: any) => {
this.listUsersPosition.push(new MessageUserPosition(
userPosition.userId,
userPosition.roomId,
new Point(
userPosition.position.x,
userPosition.position.y,
userPosition.position.direction
)
));
});
}
}
export interface ConnexionInterface {
socket : any;
token : string;
email : string;
userId: string;
startedRoom : string;
createConnexion() : Promise<any>;
joinARoom(roomId : string) : void;
sharePosition(roomId : string, x : number, y : number, direction : string) : void;
positionOfAllUser() : void;
}
export class Connexion implements ConnexionInterface{
socket : any;
token : string;
email : string;
userId: string;
startedRoom : string;
constructor(email : string) {
GameManager: GameManagerInterface;
constructor(email : string, GameManager: GameManagerInterface) {
this.email = email;
Axios.post(`${API_URL}/login`, {email: email})
this.GameManager = GameManager;
}
createConnexion() : Promise<ConnexionInterface>{
return Axios.post(`${API_URL}/login`, {email: this.email})
.then((res) => {
this.token = res.data.token;
this.startedRoom = res.data.roomId;
this.userId = res.data.userId;
this.socket = SocketIo(`${API_URL}`, {
query: {
@ -87,12 +141,13 @@ export class Connexion {
this.joinARoom(this.startedRoom);
//share your first position
this.sharePosition(0, 0);
this.sharePosition(this.startedRoom, 0, 0);
//create listen event to get all data user shared by the back
this.positionOfAllUser();
this.errorMessage();
return this;
})
.catch((err) => {
console.error(err);
@ -104,18 +159,23 @@ export class Connexion {
* Permit to join a room
* @param roomId
*/
joinARoom(roomId : string){
let messageUserPosition = new MessageUserPosition(this.email, this.startedRoom, new Point(0, 0));
joinARoom(roomId : string) : void {
let messageUserPosition = new MessageUserPosition(this.userId, this.startedRoom, new Point(0, 0));
this.socket.emit('join-room', messageUserPosition.toString());
}
/**
* Permit to share your position in map
*
* @param roomId
* @param x
* @param y
* @param direction
*/
sharePosition(x : number, y : number){
let messageUserPosition = new MessageUserPosition(this.email, this.startedRoom, new Point(x, y));
sharePosition(roomId : string, x : number, y : number, direction : string = "none") : void{
if(!this.socket){
return;
}
let messageUserPosition = new MessageUserPosition(this.userId, roomId, new Point(x, y, direction));
this.socket.emit('user-position', messageUserPosition.toString());
}
@ -127,20 +187,24 @@ export class Connexion {
* roomId: <string>,
* position: {
* x : <number>,
* y : <number>
* y : <number>,
* direction: <string>
* }
* },
* ...
* ]
**/
positionOfAllUser(){
this.socket.on("user-position", (message : string) => {
//TODO show all user in map
console.info("user-position", message);
positionOfAllUser() : void {
this.socket.on("user-position", (message: string) => {
let dataList = JSON.parse(message);
dataList.forEach((UserPositions: any) => {
let listMessageUserPosition = new ListMessageUserPosition(UserPositions[0], UserPositions[1]);
this.GameManager.shareUserPosition(listMessageUserPosition);
});
});
}
errorMessage(){
errorMessage() : void {
this.socket.on('message-error', (message : string) => {
console.error("message-error", message);
})

View File

@ -0,0 +1,32 @@
declare let window:any;
//this class is used to communicate with cypress, our e2e testing client
//Since cypress cannot manipulate canvas, we notified it with console logs
class CypressAsserter {
constructor() {
window.cypressAsserter = this
}
gameStarted() {
console.log('Started the game')
}
preloadStarted() {
console.log('Preloading')
}
preloadFinished() {
console.log('Preloading done')
}
initStarted() {
console.log('startInit')
}
initFinished() {
console.log('startInit done')
}
}
export const cypressAsserter = new CypressAsserter()

View File

@ -1,7 +1,13 @@
const DEBUG_MODE: boolean = !!process.env.DEBUG_MODE || false;
const API_URL = process.env.API_URL || "http://api.workadventure.localhost";
const ROOM = [process.env.ROOM || "THECODINGMACHINE"];
const RESOLUTION = 2;
const ZOOM_LEVEL = 3/4;
export {
DEBUG_MODE,
API_URL,
RESOLUTION
RESOLUTION,
ZOOM_LEVEL,
ROOM
}

View File

@ -1,185 +0,0 @@
import {RESOLUTION} from "./Enum/EnvironmentVariable";
export class GameScene extends Phaser.Scene {
private player: Phaser.GameObjects.Sprite;
private keyZ: Phaser.Input.Keyboard.Key;
private keyQ: Phaser.Input.Keyboard.Key;
private keyS: Phaser.Input.Keyboard.Key;
private keyD: Phaser.Input.Keyboard.Key;
private keyRight: Phaser.Input.Keyboard.Key;
private keyLeft: Phaser.Input.Keyboard.Key;
private keyUp: Phaser.Input.Keyboard.Key;
private keyDown: Phaser.Input.Keyboard.Key;
private keyShift: Phaser.Input.Keyboard.Key;
private Mappy : Phaser.Tilemaps.Tilemap;
private startX = ((window.innerWidth / 2) / RESOLUTION);
private startY = ((window.innerHeight / 2) / RESOLUTION);
constructor() {
super({
key: "GameScene"
});
}
preload(): void {
this.load.image('tiles', 'maps/tiles.png');
this.load.tilemapTiledJSON('map', 'maps/map2.json');
this.load.spritesheet('player',
'resources/characters/pipoya/Male 01-1.png',
{ frameWidth: 32, frameHeight: 32 }
);
}
init(): void {
this.keyShift = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT);
this.keyZ = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z);
this.keyQ = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q);
this.keyS = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S);
this.keyD = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);
this.keyUp = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP);
this.keyLeft = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT);
this.keyDown = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN);
this.keyRight = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT);
}
private moveCamera(x:number, y:number, speedMultiplier: number): void {
this.cameras.main.scrollX += speedMultiplier * 2 * x;
this.cameras.main.scrollY += speedMultiplier * 2 * y;
}
create(): void {
this.Mappy = this.add.tilemap("map");
let terrain = this.Mappy.addTilesetImage("tiles", "tiles");
let bottomLayer = this.Mappy.createStaticLayer("Calque 1", [terrain], 0, 0);
let topLayer = this.Mappy.createStaticLayer("Calque 2", [terrain], 0, 0);
// Let's manage animations of the player
this.anims.create({
key: 'down',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 2 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('player', { start: 3, end: 5 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('player', { start: 6, end: 8 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'up',
frames: this.anims.generateFrameNumbers('player', { start: 9, end: 11 }),
frameRate: 10,
repeat: -1
});
//let player = this.add.sprite(450, 450, 'player');
//player.anims.play('down');
//player.setBounce(0.2);
//player.setCollideWorldBounds(true);
this.player = this.add.sprite(this.startX, this.startY, 'player');
}
private angle: number = 0;
update(dt: number): void {
let xCameraPosition = this.cameras.main.scrollX;
let yCameraPosition = this.cameras.main.scrollY;
let speedMultiplier = this.keyShift.isDown ? 5 : 1;
if (this.keyZ.isDown || this.keyUp.isDown) {
this.managePlayerAnimation('up');
if (this.player.y > 0) {
this.player.setY(this.player.y - 2);
} else {
this.player.setY(0);
}
if (yCameraPosition > 0) {
if (this.player.y < (this.Mappy.widthInPixels - this.startY)) {
this.moveCamera(0, -1, speedMultiplier);
}
} else {
this.cameras.main.scrollY = 0;
}
} else if (this.keyQ.isDown || this.keyLeft.isDown) {
this.managePlayerAnimation('left');
if (this.player.x > 0) {
this.player.setX(this.player.x - 2);
} else {
this.player.setX(0);
}
if (xCameraPosition > 0) {
if (this.player.x < (this.Mappy.heightInPixels - this.startX)) {
this.moveCamera(-1, 0, speedMultiplier);
}
} else {
this.cameras.main.scrollX = 0;
}
} else if (this.keyS.isDown || this.keyDown.isDown) {
this.managePlayerAnimation('down');
if (this.Mappy.heightInPixels > this.player.y) {
this.player.setY(this.player.y + 2);
} else {
this.player.setY(this.Mappy.heightInPixels);
}
if (this.Mappy.heightInPixels > (yCameraPosition + (window.innerHeight / RESOLUTION))) {
if (this.player.y > this.startY) {
this.moveCamera(0, 1, speedMultiplier);
}
} else {
this.cameras.main.scrollY = (this.Mappy.heightInPixels - (window.innerHeight / RESOLUTION));
}
} else if (this.keyD.isDown || this.keyRight.isDown) {
this.managePlayerAnimation('right');
if (this.Mappy.widthInPixels > this.player.x) {
this.player.setX(this.player.x + 2)
} else {
this.player.setX(this.Mappy.widthInPixels)
}
if (this.Mappy.widthInPixels > (xCameraPosition + (window.innerWidth / RESOLUTION))) {
if (this.player.x > this.startX) {
this.moveCamera(1, 0, speedMultiplier);
}
} else {
this.cameras.main.scrollX = (this.Mappy.widthInPixels - (window.innerWidth / RESOLUTION));
}
} else {
this.managePlayerAnimation('none');
}
/*this.cameras.main.scrollX = Math.floor(300 + 300 * Math.cos(this.angle));
this.cameras.main.scrollY = Math.floor(300 + 300 * Math.sin(this.angle));
this.angle = dt * 0.0001;*/
}
managePlayerAnimation(direction: string) {
if (!this.player.anims.currentAnim || this.player.anims.currentAnim.key !== direction) {
this.player.anims.play(direction);
} else if (direction === 'none' && this.player.anims.currentAnim) {
this.player.anims.currentAnim.destroy();
}
}
}

View File

@ -0,0 +1,58 @@
import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "../Player/Animation";
import {ActiveEventList, UserInputEvent} from "../UserInput/UserInputManager";
import {SpeechBubble} from "./SpeechBubble";
export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite {
private bubble: SpeechBubble;
constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: string | number) {
super(scene, x, y, texture, frame);
this.scene.sys.updateList.add(this);
this.scene.sys.displayList.add(this);
this.setScale(2);
this.scene.physics.world.enableBody(this);
this.setImmovable(true);
this.setCollideWorldBounds(true);
this.setSize(16, 16); //edit the hitbox to better match the caracter model
this.setOffset(8, 16);
}
move(x: number, y: number){
this.setVelocity(x, y);
//todo improve animations to better account for diagonal movement
if (this.body.velocity.x > 0) { //moving right
this.play(PlayerAnimationNames.WalkRight, true);
}
if (this.body.velocity.x < 0) { //moving left
this.anims.playReverse(PlayerAnimationNames.WalkLeft, true);
}
if (this.body.velocity.y < 0) { //moving up
this.play(PlayerAnimationNames.WalkUp, true);
}
if (this.body.velocity.y > 0) { //moving down
this.play(PlayerAnimationNames.WalkDown, true);
}
if(this.bubble) {
this.bubble.moveBubble(this.x, this.y);
}
}
stop(){
this.setVelocity(0, 0);
this.play(PlayerAnimationNames.None, true);
}
say(text: string) {
if (this.bubble) return;
this.bubble = new SpeechBubble(this.scene, this, text)
//todo make the buble destroy on player movement?
setTimeout(() => {
this.bubble.destroy();
this.bubble = null;
}, 3000)
}
}

View File

@ -0,0 +1,88 @@
import Scene = Phaser.Scene;
import {PlayableCaracter} from "./PlayableCaracter";
export class SpeechBubble {
private bubble: Phaser.GameObjects.Graphics;
private content: Phaser.GameObjects.Text;
/**
*
* @param scene
* @param player
* @param text
*/
constructor(scene: Scene, player: PlayableCaracter, text: string = "") {
let bubbleHeight = 50;
let bubblePadding = 10;
let bubbleWidth = bubblePadding * 2 + text.length * 10;
let arrowHeight = bubbleHeight / 4;
this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 });
// Bubble shadow
this.bubble.fillStyle(0x222222, 0.5);
this.bubble.fillRoundedRect(6, 6, bubbleWidth, bubbleHeight, 16);
// this.bubble color
this.bubble.fillStyle(0xffffff, 1);
// this.bubble outline line style
this.bubble.lineStyle(4, 0x565656, 1);
// this.bubble shape and outline
this.bubble.strokeRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16);
this.bubble.fillRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16);
// Calculate arrow coordinates
let point1X = Math.floor(bubbleWidth / 7);
let point1Y = bubbleHeight;
let point2X = Math.floor((bubbleWidth / 7) * 2);
let point2Y = bubbleHeight;
let point3X = Math.floor(bubbleWidth / 7);
let point3Y = Math.floor(bubbleHeight + arrowHeight);
// bubble arrow shadow
this.bubble.lineStyle(4, 0x222222, 0.5);
this.bubble.lineBetween(point2X - 1, point2Y + 6, point3X + 2, point3Y);
// bubble arrow fill
this.bubble.fillTriangle(point1X, point1Y, point2X, point2Y, point3X, point3Y);
this.bubble.lineStyle(2, 0x565656, 1);
this.bubble.lineBetween(point2X, point2Y, point3X, point3Y);
this.bubble.lineBetween(point1X, point1Y, point3X, point3Y);
this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: 20, color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } });
let bounds = this.content.getBounds();
this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2));
}
/**
*
* @param x
* @param y
*/
moveBubble(x : number, y : number) {
if (this.bubble) {
this.bubble.setPosition((x + 16), (y - 80));
}
if (this.content) {
let bubbleHeight = 50;
let bubblePadding = 10;
let bubbleWidth = bubblePadding * 2 + this.content.text.length * 10;
let bounds = this.content.getBounds();
//this.content.setPosition(x, y);
this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2));
}
}
destroy(): void {
this.bubble.setVisible(false) //todo find a better way
this.bubble.destroy();
this.bubble = null;
this.content.destroy();
this.content = null;
}
}

View File

@ -0,0 +1,8 @@
export class Sprite extends Phaser.GameObjects.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: number | string) {
super(scene, x, y, texture, frame);
scene.sys.updateList.add(this);
scene.sys.displayList.add(this);
}
}

View File

@ -0,0 +1,77 @@
import {GameSceneInterface, GameScene} from "./GameScene";
import {ROOM} from "../../Enum/EnvironmentVariable"
import {Connexion, ConnexionInterface, ListMessageUserPositionInterface} from "../../Connexion";
export enum StatusGameManagerEnum {
IN_PROGRESS = 1,
CURRENT_USER_CREATED = 2
}
export let ConnexionInstance : ConnexionInterface;
export interface GameManagerInterface {
GameScenes: Array<GameSceneInterface>;
status : number;
createCurrentPlayer() : void;
shareUserPosition(ListMessageUserPosition : ListMessageUserPositionInterface): void;
}
export class GameManager implements GameManagerInterface {
GameScenes: Array<GameSceneInterface> = [];
status: number;
constructor() {
this.status = StatusGameManagerEnum.IN_PROGRESS;
ConnexionInstance = new Connexion("test@gmail.com", this);
}
createGame(){
return ConnexionInstance.createConnexion().then(() => {
this.configureGame();
/** TODO add loader in the page **/
}).catch((err) => {
console.error(err);
throw err;
});
}
/**
* permit to config rooms
*/
configureGame() {
ROOM.forEach((roomId) => {
let newGame = new GameScene(roomId, this);
this.GameScenes.push((newGame as GameSceneInterface));
});
}
/**
* Permit to create player in started room
* @param RoomId
* @param UserId
*/
createCurrentPlayer(): void {
//Get started room send by the backend
let game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === ConnexionInstance.startedRoom);
game.createCurrentPlayer(ConnexionInstance.userId);
this.status = StatusGameManagerEnum.CURRENT_USER_CREATED;
}
/**
* Share position in game
* @param ListMessageUserPosition
*/
shareUserPosition(ListMessageUserPosition: ListMessageUserPositionInterface): void {
if (this.status === StatusGameManagerEnum.IN_PROGRESS) {
return;
}
try {
let Game: GameSceneInterface = this.GameScenes.find((Game: GameSceneInterface) => Game.RoomId === ListMessageUserPosition.roomId);
if (!Game) {
return;
}
Game.shareUserPosition(ListMessageUserPosition.listUsersPosition)
} catch (e) {
console.error(e);
}
}
}

View File

@ -0,0 +1,266 @@
import {GameManagerInterface, StatusGameManagerEnum} from "./GameManager";
import {MessageUserPositionInterface} from "../../Connexion";
import {CurrentGamerInterface, GamerInterface, Player} from "../Player/Player";
import {DEBUG_MODE, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
import Tile = Phaser.Tilemaps.Tile;
import {ITiledMap, ITiledTileSet} from "../Map/ITiledMap";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
export enum Textures {
Rock = 'rock',
Player = 'playerModel',
Map = 'map'
}
export interface GameSceneInterface extends Phaser.Scene {
RoomId : string;
Map: Phaser.Tilemaps.Tilemap;
createCurrentPlayer(UserId : string) : void;
shareUserPosition(UsersPosition : Array<MessageUserPositionInterface>): void;
}
export class GameScene extends Phaser.Scene implements GameSceneInterface{
GameManager : GameManagerInterface;
RoomId : string;
Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer: CurrentGamerInterface;
MapPlayers : Phaser.Physics.Arcade.Group;
Map: Phaser.Tilemaps.Tilemap;
Layers : Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects : Array<Phaser.Physics.Arcade.Sprite>;
map: ITiledMap;
startX = (window.innerWidth / 2) / RESOLUTION;
startY = (window.innerHeight / 2) / RESOLUTION;
constructor(RoomId : string, GameManager : GameManagerInterface) {
super({
key: "GameScene"
});
this.RoomId = RoomId;
this.GameManager = GameManager;
this.Terrains = [];
}
//hook preload scene
preload(): void {
cypressAsserter.preloadStarted();
let mapUrl = 'maps/map.json';
this.load.on('filecomplete-tilemapJSON-'+Textures.Map, (key: string, type: string, data: any) => {
// Triggered when the map is loaded
// Load tiles attached to the map recursively
this.map = data.data;
this.map.tilesets.forEach((tileset) => {
if (typeof tileset.name === 'undefined' || typeof tileset.image === 'undefined') {
console.warn("Don't know how to handle tileset ", tileset)
return;
}
let path = mapUrl.substr(0, mapUrl.lastIndexOf('/'));
this.load.image(tileset.name, path + '/' + tileset.image);
})
});
this.load.tilemapTiledJSON(Textures.Map, mapUrl);
this.load.image(Textures.Rock, 'resources/objects/rockSprite.png');
this.load.spritesheet(Textures.Player,
'resources/characters/pipoya/Male 01-1.png',
{ frameWidth: 32, frameHeight: 32 }
);
cypressAsserter.preloadFinished();
}
//hook initialisation
init(){}
//hook create scene
create(): void {
cypressAsserter.initStarted();
//initalise map
this.Map = this.add.tilemap("map");
this.map.tilesets.forEach((tileset: ITiledTileSet) => {
this.Terrains.push(this.Map.addTilesetImage(tileset.name, tileset.name));
});
//permit to set bound collision
this.physics.world.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels);
//add layer on map
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2;
this.map.layers.forEach((layer) => {
if (layer.type === 'tilelayer') {
this.addLayer( this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth) );
} else if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
depth = -1;
}
});
if (depth === -2) {
throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.');
}
//add entities
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
this.addSpite(this.physics.add.sprite(200, 400, Textures.Rock, 26));
//init event click
this.EventToClickOnTile();
//initialise list of other player
this.MapPlayers = this.physics.add.group({ immovable: true });
//notify game manager can to create currentUser in map
this.GameManager.createCurrentPlayer();
//initialise camera
this.initCamera();
cypressAsserter.initFinished();
}
//todo: in a dedicated class/function?
initCamera() {
this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels);
this.cameras.main.startFollow(this.CurrentPlayer);
this.cameras.main.setZoom(ZOOM_LEVEL);
}
addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){
this.Layers.push(Layer);
}
createCollisionWithPlayer() {
//add collision layer
this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => {
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: any, object2: any) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
});
Layer.setCollisionByProperty({collides: true});
if (DEBUG_MODE) {
//debug code to see the collision hitbox of the object in the top layer
Layer.renderDebug(this.add.graphics(), {
tileColor: null, //non-colliding tiles
collidingTileColor: new Phaser.Display.Color(243, 134, 48, 200), // Colliding tiles,
faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Colliding face edges
});
}
});
}
addSpite(Object : Phaser.Physics.Arcade.Sprite){
Object.setImmovable(true);
this.Objects.push(Object);
}
createCollisionObject(){
this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => {
this.physics.add.collider(this.CurrentPlayer, Object, (object1: any, object2: any) => {
//this.CurrentPlayer.say("Collision with object : " + (object2 as Phaser.Physics.Arcade.Sprite).texture.key)
});
})
}
createCurrentPlayer(UserId : string){
//initialise player
this.CurrentPlayer = new Player(
UserId,
this,
this.startX,
this.startY,
);
this.CurrentPlayer.initAnimation();
//create collision
this.createCollisionWithPlayer();
this.createCollisionObject();
}
EventToClickOnTile(){
// debug code to get a tile properties by clicking on it
this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{
//pixel position toz tile position
let tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY));
if(tile){
console.log(tile)
this.CurrentPlayer.say("Your touch " + tile.layer.name);
}
});
}
/**
* @param time
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
*/
update(time: number, delta: number) : void {
this.CurrentPlayer.moveUser(delta);
}
/**
* Share position in scene
* @param UsersPosition
*/
shareUserPosition(UsersPosition : Array<MessageUserPositionInterface>): void {
this.updateOrCreateMapPlayer(UsersPosition);
}
/**
* Create new player and clean the player on the map
* @param UsersPosition
*/
updateOrCreateMapPlayer(UsersPosition : Array<MessageUserPositionInterface>){
if(!this.CurrentPlayer){
return;
}
//add or create new user
UsersPosition.forEach((userPosition : MessageUserPositionInterface) => {
if(userPosition.userId === this.CurrentPlayer.userId){
return;
}
let player = this.findPlayerInMap(userPosition.userId);
if(!player){
this.addPlayer(userPosition);
}else{
player.updatePosition(userPosition);
}
});
//clean map
this.MapPlayers.getChildren().forEach((player: GamerInterface) => {
if(UsersPosition.find((message : MessageUserPositionInterface) => message.userId === player.userId)){
return;
}
player.destroy();
this.MapPlayers.remove(player);
});
}
private findPlayerInMap(UserId : string) : GamerInterface | null{
let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId);
if(!player){
return null;
}
return (player as GamerInterface);
}
/**
* Create new player
* @param MessageUserPosition
*/
addPlayer(MessageUserPosition : MessageUserPositionInterface){
//initialise player
let player = new Player(
MessageUserPosition.userId,
this,
MessageUserPosition.position.x,
MessageUserPosition.position.y,
);
player.initAnimation();
this.MapPlayers.add(player);
player.updatePosition(MessageUserPosition);
//init colision
this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => {
CurrentPlayer.say("Hello, how are you ? ");
});
}
}

View File

@ -0,0 +1,113 @@
/**
* Tiled Map Interface
*
* Represents the interface for the Tiled exported data structure (JSON). Used
* when loading resources via Resource loader.
*/
export interface ITiledMap {
width: number;
height: number;
layers: ITiledMapLayer[];
nextobjectid: number;
/**
* Map orientation (orthogonal)
*/
orientation: string;
properties: {[key: string]: string};
/**
* Render order (right-down)
*/
renderorder: string;
tileheight: number;
tilewidth: number;
tilesets: ITiledTileSet[];
version: number;
}
export interface ITiledMapLayer {
data: number[]|string;
height: number;
name: string;
opacity: number;
properties: {[key: string]: string};
encoding: string;
compression?: string;
/**
* Type of layer (tilelayer, objectgroup)
*/
type: string;
visible: boolean;
width: number;
x: number;
y: number;
/**
* Draw order (topdown (default), index)
*/
draworder: string;
objects: ITiledMapObject[];
}
export interface ITiledMapObject {
id: number;
/**
* Tile object id
*/
gid: number;
height: number;
name: string;
properties: {[key: string]: string};
rotation: number;
type: string;
visible: boolean;
width: number;
x: number;
y: number;
/**
* Whether or not object is an ellipse
*/
ellipse: boolean;
/**
* Polygon points
*/
polygon: {x: number, y: number}[];
/**
* Polyline points
*/
polyline: {x: number, y: number}[];
}
export interface ITiledTileSet {
firstgid: number;
image: string;
imageheight: number;
imagewidth: number;
margin: number;
name: string;
properties: {[key: string]: string};
spacing: number;
tilecount: number;
tileheight: number;
tilewidth: number;
transparentcolor: string;
terrains: ITiledMapTerrain[];
tiles: {[key: string]: { terrain: number[] }};
/**
* Refers to external tileset file (should be JSON)
*/
source: string;
}
export interface ITiledMapTerrain {
name: string;
tile: number;
}

View File

@ -0,0 +1,40 @@
import {PlayableCaracter} from "../Entity/PlayableCaracter";
import {Textures} from "../Game/GameScene";
import {UserInputEvent} from "../UserInput/UserInputManager";
import {Player} from "../Player/Player";
import {MessageUserPositionInterface} from "../../Connexion";
import {playAnimation} from "../Player/Animation";
export class NonPlayer extends PlayableCaracter {
isFleeing: boolean = false;
fleeingDirection:any = null //todo create a vector class
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, Textures.Player, 1);
this.setSize(32, 32); //edit the hitbox to better match the caracter model
}
updatePosition(MessageUserPosition : MessageUserPositionInterface){
playAnimation(this, MessageUserPosition.position.direction);
this.setX(MessageUserPosition.position.x);
this.setY(MessageUserPosition.position.y);
}
fleeFrom(player:Player) {
if (this.isFleeing) return;
this.say("Don't touch me!");
this.isFleeing = true;
setTimeout(() => {
this.say("Feww, I escaped.");
this.isFleeing = false
this.fleeingDirection = null
}, 3000);
let vectorX = this.x - player.x;
let vectorY = this.y - player.y;
this.fleeingDirection = {x: vectorX, y: vectorY}
}
}

View File

@ -0,0 +1,58 @@
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',
WalkUp = 'up',
WalkRight = 'right',
None = 'none',
}
export const getPlayerAnimations = (): AnimationData[] => {
return [{
key: PlayerAnimationNames.WalkDown,
frameModel: Textures.Player,
frameStart: 0,
frameEnd: 2,
frameRate: 10,
repeat: -1
}, {
key: PlayerAnimationNames.WalkLeft,
frameModel: Textures.Player,
frameStart: 3,
frameEnd: 5,
frameRate: 10,
repeat: -1
}, {
key: PlayerAnimationNames.WalkRight,
frameModel: Textures.Player,
frameStart: 6,
frameEnd: 8,
frameRate: 10,
repeat: -1
}, {
key: PlayerAnimationNames.WalkUp,
frameModel: Textures.Player,
frameStart: 9,
frameEnd: 11,
frameRate: 10,
repeat: -1
}];
};
export const playAnimation = (Player : Phaser.GameObjects.Sprite, direction : string) => {
if (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction) {
Player.anims.play(direction);
} else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) {
Player.anims.currentAnim.destroy();
}
}

View File

@ -0,0 +1,107 @@
import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation";
import {GameSceneInterface, Textures} from "../Game/GameScene";
import {ConnexionInstance} from "../Game/GameManager";
import {MessageUserPositionInterface} from "../../Connexion";
import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {PlayableCaracter} from "../Entity/PlayableCaracter";
export interface CurrentGamerInterface extends PlayableCaracter{
userId : string;
PlayerValue : string;
initAnimation() : void;
moveUser(delta: number) : void;
say(text : string) : void;
}
export interface GamerInterface extends PlayableCaracter{
userId : string;
PlayerValue : string;
initAnimation() : void;
updatePosition(MessageUserPosition : MessageUserPositionInterface) : void;
say(text : string) : void;
}
export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface {
userId: string;
PlayerValue: string;
userInputManager: UserInputManager;
constructor(
userId: string,
Scene: GameSceneInterface,
x: number,
y: number,
PlayerValue: string = Textures.Player
) {
super(Scene, x, y, PlayerValue, 1);
//create input to move
this.userInputManager = new UserInputManager(Scene);
//set data
this.userId = userId;
this.PlayerValue = PlayerValue;
//the current player model should be push away by other players to prevent conflict
this.setImmovable(false);
}
initAnimation(): void {
getPlayerAnimations().forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frameRate: d.frameRate,
repeat: d.repeat
});
})
}
moveUser(delta: number): void {
//if user client on shift, camera and player speed
let haveMove = false;
let direction = null;
let activeEvents = this.userInputManager.getEventListForGameTick();
let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9;
let moveAmount = speedMultiplier * delta;
if (activeEvents.get(UserInputEvent.MoveUp)) {
this.move(0, -moveAmount);
haveMove = true;
direction = PlayerAnimationNames.WalkUp;
}
if (activeEvents.get(UserInputEvent.MoveLeft)) {
this.move(-moveAmount, 0);
haveMove = true;
direction = PlayerAnimationNames.WalkLeft;
}
if (activeEvents.get(UserInputEvent.MoveDown)) {
this.move(0, moveAmount);
haveMove = true;
direction = PlayerAnimationNames.WalkDown;
}
if (activeEvents.get(UserInputEvent.MoveRight)) {
this.move(moveAmount, 0);
haveMove = true;
direction = PlayerAnimationNames.WalkRight;
}
if (!haveMove) {
direction = PlayerAnimationNames.None;
this.stop();
}
this.sharePosition(direction);
}
private sharePosition(direction: string) {
if (ConnexionInstance) {
ConnexionInstance.sharePosition((this.scene as GameSceneInterface).RoomId, this.x, this.y, direction);
}
}
updatePosition(MessageUserPosition: MessageUserPositionInterface) {
playAnimation(this, MessageUserPosition.position.direction);
this.setX(MessageUserPosition.position.x);
this.setY(MessageUserPosition.position.y);
}
}

View File

@ -0,0 +1,68 @@
import Map = Phaser.Structs.Map;
import {GameSceneInterface} from "../Game/GameScene";
interface UserInputManagerDatum {
keyCode: number;
keyInstance: Phaser.Input.Keyboard.Key;
event: UserInputEvent
}
export enum UserInputEvent {
MoveLeft = 1,
MoveUp,
MoveRight,
MoveDown,
SpeedUp,
Interact,
Shout,
}
//we cannot the map structure so we have to create a replacment
export class ActiveEventList {
private KeysCode : any;
constructor() {
this.KeysCode = {};
}
get(event: UserInputEvent): boolean {
return this.KeysCode[event] || false;
}
set(event: UserInputEvent, value: boolean): boolean {
return this.KeysCode[event] = true;
}
}
//this class is responsible for catching user inputs and listing all active user actions at every game tick events.
export class UserInputManager {
private KeysCode: UserInputManagerDatum[] = [
{keyCode: Phaser.Input.Keyboard.KeyCodes.Z, event: UserInputEvent.MoveUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.Q, event: UserInputEvent.MoveLeft, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.S, event: UserInputEvent.MoveDown, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.D, event: UserInputEvent.MoveRight, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.UP, event: UserInputEvent.MoveUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.LEFT, event: UserInputEvent.MoveLeft, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.DOWN, event: UserInputEvent.MoveDown, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.RIGHT, event: UserInputEvent.MoveRight, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.E, event: UserInputEvent.Interact, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.F, event: UserInputEvent.Shout, keyInstance: null},
];
constructor(Scene : GameSceneInterface) {
this.KeysCode.forEach(d => {
d.keyInstance = Scene.input.keyboard.addKey(d.keyCode);
});
}
getEventListForGameTick(): ActiveEventList {
let eventsMap = new ActiveEventList();
this.KeysCode.forEach(d => {
if (d. keyInstance.isDown) {
eventsMap.set(d.event, true);
}
});
return eventsMap;
}
}

View File

@ -1,22 +1,32 @@
import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig;
import {GameScene} from "./GameScene";
import {Connexion} from "./Connexion";
import {RESOLUTION} from "./Enum/EnvironmentVariable";
import {GameManager} from "./Phaser/Game/GameManager";
import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable";
import {cypressAsserter} from "./Cypress/CypressAsserter";
let gameManager = new GameManager();
const config: GameConfig = {
title: "Office game",
width: window.innerWidth / RESOLUTION,
height: window.innerHeight / RESOLUTION,
parent: "game",
scene: [GameScene],
scene: gameManager.GameScenes,
zoom: RESOLUTION,
physics: {
default: "arcade",
arcade: {
debug: DEBUG_MODE
}
}
};
let game = new Phaser.Game(config);
cypressAsserter.gameStarted();
window.addEventListener('resize', function (event) {
game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION);
gameManager.createGame().then(() => {
let game = new Phaser.Game(config);
window.addEventListener('resize', function (event) {
game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION);
});
});
const connexion = new Connexion("test@gmail.com");

View File

@ -29,6 +29,6 @@ module.exports = {
new webpack.ProvidePlugin({
Phaser: 'phaser'
}),
new webpack.EnvironmentPlugin(['API_URL'])
new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE'])
]
};