Skip to content
Snippets Groups Projects
Commit 9de54feb authored by jeremy.gobet's avatar jeremy.gobet
Browse files

Initial commit

parents
Branches
Tags
No related merge requests found
# ignore all files starting with . or ~
.*
~*
# ignore node/grunt dependency directories
node_modules/
# ignore packaged files
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# ignore private/secret files
*.der
*.key
*.pem
# track these files, if they exist
!.gitignore
dist/
# Breakout
Jeu du casse brique réalisé sur Phaser.js.
## Sources
Les fichiers sources du programme se trouve dans le dossier **src**.
## Installation
Installer les dépendances du projet avec NPM. La commande crée un dossier **node_modules** contenant les dépendances du projet:
`npm install`
## Exécution en local
La commande ci-dessous transpile le projet et exécute automatiquement le jeu dans un onglet de votre navigateur :
`npm run start`
Pour modifier l'exécution du programme en local voir le fichier **webpack.dev.js**.
## Build
Le build du projet est effectué dans le dossier **dist** créé à la première exécution de la commande ci-dessous:
`npm run build`
Pour modifier la transilation du programme pour la production, voir le fichier **webpack.prod.js**
## Test
exécutez les tests unitaires présent dans le dossier **test** avec la commande suivante:
`npm run test`
## Lint
Exécuter ESLint pour vérifier les règles sur le code source en TypeScript:
`npx eslint src/**/**.ts`
## Deploy
Pour déployer sur Firebase il faut au préalable faire la configuration et créer un nouveau projet Firebase. Toutes les étapes sont décritent dans le fichier **firebase_setup.md**
Déployer le projet sur Firebase:
`firebase deploy`
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};
Pour setup Firebase.
1. Créer un projet firebase depuis [la console firebase](https://console.firebase.google.com/u/0/)
2. Installer sur votre machine les outils `firebase` avec la commande :
```bash
npm install -g firebase-tools
```
3. Log in depuis la `CLI` en utilisant le __même compte__ que celui utilisé pour créer le projet `firebase` en utilisant la commande
```bash
firebase login
```
4. Puis utiliser cette commande pour initialiser `firebase` depuis le répertoire contenant le frontend.
```
firebase init
```
Dans la `CLI` interative, sélectionner :
Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
Choisir "Use an existing project" et choisir le projet créé préalablement.
Le `public directory` indique le sous-répertoire où se trouveront les fichiers à déployer. Par convention web, nous utiliserons `/dist/<nom_du_projet>` : C'est ici que le framework angular génère des fichiers lors du build.
Indiquer "yes" à l'option de génération sous forme de `single-page app`.
Puis ne pas automatiquement générer des `GitHub Action deploys` puisque nous utiliserons GitLab.
Cette commande crée alors un fichier `.json` qui doit se trouver à la racine de votre projet frontend. Ce dernier contient les détails du hosting pour firebase.
5. Pour push depuis une pipeline CI/CD, nous avons besoin de commandes non interactives, or la commande `firebase login` est interactive et ne fonctionnera pas depuis la pipeline. Nous allons alors générer un `token` que nous pourrons utiliser pour déployer le projet en nous authentifiant, sans passer par la commande `login`. Utiliser la commande suivante pour générer un token :
```bash
firebase login:ci
```
Le `token` retourné doit être noté puisqu'il sera nécessaire pour le déploiement.
6. Finallement, pour vérifier votre installation : `build` le projet angular en utilisant `ng build --aot`, et déployer le contenu en utilisant la commande :
```bash
firebase deploy --token <token>
```
En remplaçant `<token>` par le votre, obtenu à l'étape 5. `Firebase` déploie alors le contenu du dossier indiqué lors de la commande `init`, en s'authentifiant à l'aide de votre `token`. La `CLI` vous retourne l'adresse à laquelle votre projet est disponible.
Il est désormait possible d'utiliser cette commande `deploy` dans votre CI/CD pour mettre à jour le frontend à l'exécution de votre pipeline, après avoir installé les outils `firebase` sur le runner. Attention à ne pas hardcoder votre `token` !!! (très mauvaise pratique !), pensez bien à la variabiliser depuis l'interface graphique de `gitlab` et à limiter son accès. Dans votre `.gitlab-ci.yml` remplacez la valeur du `token` par votre variable.
{
"name": "breakout",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "jest",
"start": "webpack-dev-server --open --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
"author": "",
"license": "",
"dependencies": {
"firebase": "^9.0.1",
"phaser": "^3.55.2"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@typescript-eslint/eslint-plugin": "^5.45.1",
"@typescript-eslint/parser": "^5.45.1",
"babel-jest": "^29.3.1",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.0.2",
"eslint": "^8.29.0",
"jest": "^29.3.1",
"ts-loader": "^7.0.5",
"typescript": "^3.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "^4.2.2"
}
}
{"frames": {
"ball1":
{
"frame": {"x":452,"y":2,"w":22,"h":22},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":22,"h":22},
"sourceSize": {"w":22,"h":22}
},
"ball2":
{
"frame": {"x":476,"y":2,"w":22,"h":22},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":22,"h":22},
"sourceSize": {"w":22,"h":22}
},
"blue1":
{
"frame": {"x":386,"y":2,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"blue2":
{
"frame": {"x":108,"y":53,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"button":
{
"frame": {"x":2,"y":2,"w":190,"h":49},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":190,"h":49},
"sourceSize": {"w":190,"h":49}
},
"buttonOver":
{
"frame": {"x":194,"y":2,"w":190,"h":49},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":190,"h":49},
"sourceSize": {"w":190,"h":49}
},
"green1":
{
"frame": {"x":2,"y":79,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"green2":
{
"frame": {"x":174,"y":53,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"paddle1":
{
"frame": {"x":386,"y":36,"w":104,"h":24},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":104,"h":24},
"sourceSize": {"w":104,"h":24}
},
"paddle2":
{
"frame": {"x":2,"y":53,"w":104,"h":24},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":104,"h":24},
"sourceSize": {"w":104,"h":24}
},
"particle1":
{
"frame": {"x":492,"y":26,"w":10,"h":10},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":10,"h":10},
"sourceSize": {"w":10,"h":10}
},
"particle2":
{
"frame": {"x":492,"y":38,"w":10,"h":10},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":10,"h":10},
"sourceSize": {"w":10,"h":10}
},
"particle3":
{
"frame": {"x":68,"y":79,"w":20,"h":19},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":1,"w":20,"h":19},
"sourceSize": {"w":20,"h":21}
},
"purple1":
{
"frame": {"x":240,"y":53,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"purple2":
{
"frame": {"x":306,"y":53,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"red1":
{
"frame": {"x":372,"y":62,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"red2":
{
"frame": {"x":438,"y":62,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"silver1":
{
"frame": {"x":90,"y":87,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"silver2":
{
"frame": {"x":156,"y":87,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"yellow1":
{
"frame": {"x":222,"y":87,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
},
"yellow2":
{
"frame": {"x":288,"y":87,"w":64,"h":32},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":64,"h":32},
"sourceSize": {"w":64,"h":32}
}},
"meta": {
"app": "http://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "breakout.png",
"format": "RGBA8888",
"size": {"w":504,"h":121},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:001f51c9029c40bfd47dffb21081606f:e188858ff10f5876d9b056ceae3435f7:aa94e37632703bccd6a813800bf3159a$"
}
}
src/assets/breakout.png

8.4 KiB

import Ball from './scripts/ball';
import Bricks from './scripts/bricks';
import Paddle from './scripts/paddle';
export default class Breakout extends Phaser.Scene {
bricks: Bricks;
paddle: Paddle;
ball: Ball;
constructor(config: Phaser.Types.Core.GameConfig) {
super(config);
}
// Function de PhaserJS permettant de charger toutes les ressources nécessaires
preload() {
this.load.atlas('assets', 'assets/breakout.png', 'assets/breakout.json');
}
// Function de PhaserJS pour définir l'état initial de la scène
create() {
this.bricks = new Bricks(this);
this.paddle = new Paddle(this);
this.ball = new Ball(this);
// Collisions
// https://photonstorm.github.io/phaser3-docs/Phaser.Physics.Arcade.Factory.html#collider__anchor
this.physics.add.collider(this.ball.ballImage, this.bricks.group, this.bricks.hitBrick.bind(this.bricks), null, this);
this.physics.add.collider(this.ball.ballImage, this.paddle.image, this.ball.hitPaddle.bind(this.paddle), null, this);
// Active les collisions sur la bordure sauf au sol
this.physics.world.setBoundsCollision(true, true, true, false);
// Evénements
this.bricks.on("allBricksDestroyedEvent", this.resetLevel.bind(this));
this.paddle.on("paddleMovedEvent", this.ball.paddleMoved.bind(this.ball));
this.ball.on("outOfBoundsEvent", this.outOfBounds.bind(this));
}
// Function de PhaserJS pour la mise à jour de la scène (Boucle de gameplay)
update() {
this.ball.update();
}
resetLevel() {
this.ball.resetPosition(this.paddle.image);
this.bricks.reset();
}
outOfBounds() {
this.ball.resetPosition(this.paddle.image);
}
};
<!DOCTYPE html>
<html lang="fr">
<head>
<title>Phaser3 - Typescript</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta charset="utf-8">
</head>
<body>
<div id="phaser-canvas"></div>
<script src="bundle.js"></script>
</body>
</html>
import 'phaser';
import Breakout from './breakout';
/*
Configuration de PhaserJS
https://photonstorm.github.io/phaser3-docs/Phaser.Types.Core.html#.GameConfig
*/
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO, // AUTO, WEBGL ou CANVAS
width: 800, height: 600, // Taille du canvas
parent: 'phaser-canvas', // élement HTML qui contiendra le canvas
scene: [ Breakout ], // JS Classe contenant les fonctions: preload, create, update
physics: { // Configuration de la physique
default: 'arcade' // arcade ou
}
};
var game = new Phaser.Game(config);
const VELOCITY_FACTOR = 10;
// https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.GameObject.html
export default class Ball extends Phaser.GameObjects.GameObject {
ballImage: Phaser.Physics.Arcade.Image;
onPaddle: Boolean;
constructor(scene: Phaser.Scene) {
super(scene, "ball");
this.create();
}
create() {
const canvas = this.scene.game.canvas;
this.ballImage = this.scene.physics.add.image(canvas.width / 2, canvas.height - 100, 'assets', 'ball1')
.setCollideWorldBounds(true)
.setBounce(1);
this.onPaddle = true;
this.scene.input.on('pointerup', () => {
if (this.onPaddle)
{
this.ballImage.setVelocity(-75, -300);
this.onPaddle = false;
}
}, this);
}
resetPosition(paddleImage: Phaser.Physics.Arcade.Image) {
this.ballImage.setVelocity(0);
this.ballImage.setPosition(paddleImage.x, this.scene.game.canvas.height - 100);
this.onPaddle = true;
}
hitPaddle(ballImage: Phaser.Physics.Arcade.Image, paddleImage: Phaser.Physics.Arcade.Image) {
var diff = 0;
if (ballImage.x < paddleImage.x)
{
// Ball is on the left-hand side of the paddle
diff = paddleImage.x - ballImage.x;
ballImage.setVelocityX(-VELOCITY_FACTOR * diff);
}
else if (ballImage.x > paddleImage.x)
{
// Ball is on the right-hand side of the paddle
diff = ballImage.x -paddleImage.x;
ballImage.setVelocityX(VELOCITY_FACTOR * diff);
}
else
{
// Ball is perfectly in the middle
// Add a little random X to stop it bouncing straight up!
ballImage.setVelocityX(2 + Math.random() * 8);
}
}
paddleMoved(paddleImage: Phaser.Physics.Arcade.Image) {
if (this.onPaddle)
{
this.ballImage.x = paddleImage.x;
}
}
update() {
if (this.ballImage.y > this.scene.game.canvas.height)
{
this.emit("outOfBoundsEvent");
}
}
};
// https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.GameObject.html
export default class Bricks extends Phaser.GameObjects.GameObject {
group: Phaser.Physics.Arcade.StaticGroup;
constructor(scene: Phaser.Scene) {
super(scene, "bricks");
this.create();
}
create() {
// Création des briques dans une grille de 10x6
this.group = this.scene.physics.add.staticGroup({
key: 'assets', frame: [ 'blue1', 'red1', 'green1', 'yellow1', 'silver1', 'purple1' ],
frameQuantity: 10,
gridAlign: { width: 10, height: 6, cellWidth: 64, cellHeight: 32, x: 112, y: 100 }
});
}
reset() {
this.group.children.each((brick: Phaser.Physics.Arcade.Image) => {
https://photonstorm.github.io/phaser3-docs/Phaser.Physics.Arcade.Sprite.html#enableBody__anchor
brick.enableBody(false, 0, 0, true, true);
});
}
hitBrick(ball: Phaser.Physics.Arcade.Image, brick: Phaser.Physics.Arcade.Image) {
// https://photonstorm.github.io/phaser3-docs/Phaser.Physics.Arcade.Sprite.html#disableBody__anchor
brick.disableBody(true, true);
if (this.group.countActive() === 0)
{
this.emit("allBricksDestroyedEvent");
}
}
};
// https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.GameObject.html
export default class Paddle extends Phaser.GameObjects.GameObject {
image: Phaser.Physics.Arcade.Image;
constructor(scene: Phaser.Scene) {
super(scene, "paddle");
this.create();
}
create() {
const canvas = this.scene.game.canvas;
this.image = this.scene.physics.add.image(canvas.width / 2, canvas.height - 50, 'assets', 'paddle1').setImmovable();
this.scene.input.on('pointermove', function (pointer) {
// Limite le mouvement du plateau aux extrémités gauches et droites
this.image.x = Phaser.Math.Clamp(pointer.x, 52, canvas.width - 52);
this.emit("paddleMovedEvent", this.image);
}, this);
}
};
test('very usefull test', () => {
expect(true).toBe(true);
})
{
"compilerOptions": {
"typeRoots": [
"./node_modules/phaser/types"
],
"types": [
"Phaser"
]
}
}
\ No newline at end of file
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/main.ts',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: [ '.ts', '.tsx', '.js' ]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
new CopyPlugin({
patterns: [
{ from: 'index.html', context: 'src/' },
{ from: 'assets/*', context: 'src/' },
],
}),
],
};
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
static: './dist',
},
});
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment