toggle menu

My Blogs

MY Profile

Stephen G. Vinuya
Feb 05,2020

PWA Minesweeper game using Web components, Workbox & Webpack


Recently I learned this amazing concept of Web Component!


It's amazing because you can create your very own component using Javascript!



And you might be thinking, that creating components is really common in today's web development through the use of Frontend Frameworks such as: Angular, Vue, and React.


But what if I tell you that you don't need any Frontend Frameworks to achieve similar results by using pure Javascript? 



Here's an example of a Web Component: 


<tile-mine value="1revealed="false"></tile-mine>


tile-mine is a custom HTML element, where you can bind values in its attribute.

Now, let's try to look at the Javascript codes you need in order to create this tile-mine Web Component.



export class Tile extends HTMLElement {
  constructor() {
    super();
    this.root = this.attachShadow({ mode: 'open' });
    this.prop = {
      value: '1',
      isRevealed: false
    };
    this.updateDOM();
  }

  set revealed(revealed) {
    this.prop.isRevealed = revealed;
    this.updateDOM();
  }

  get revealed() {
    return this.prop.isRevealed;
  }

  set value(value) {
    this.prop.value = value;
    this.updateDOM();
  }

  get value() {
    return this.prop.value;
  }

 

   updateDOM() {
    this.root.innerHTML = `
    ${componentStyle}
    
${this.prop.isRevealed ? 'tile-opened' : ''}">
 class="tile 
      ${this.prop.isRevealed ? this.tileValue() : ''}
    
    `;
  }

  static get observedAttributes() {
    return ['value', 'revealed'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'value':
        this.value = newValue;
        break;
      case 'revealed':
        this.revelead = newValue;
        break;
    }
  }
}


customElements.define('tile-mine', Tile);

 

Now wait..... After seeing the Javascript codes, You might be thinking that I lied about not using any Javascript Frameworks.


Nope, it is certain that we will not use any Javascript Frameworks, but we are going to use a module bundler which is Webpack.

Now let's jump into coding, we are going to utilize the concept of Web Component by creating a Minesweeper Game.



1. Project Setup

 - Open your command prompt, change directory to any folder location you want and enter the following commands

mkdir minesweeper-wc && cd minesweeper-wc && npm init -y

This will initialize our npm project.


- Install webpack, webpack-cli and webpack-dev-server


  npm install webpack webpack-cli webpack-dev-server --save-dev

 

- Install the necessary plugins for Webpack, to learn more click here 

  
npm install html-webpack-plugin copy-webpack-plugin --save-dev


Now, let's open the created project using Visual Studio Code


- Webpack configuration

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CopyPlugin([
      { from: './src/assets', to: 'assets' }
    ])
  ],
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 8000
  }
};


devServer - contains the development server config when running Webpack for development.

output - contains the configuration for the build result, by default we are using dist as the folder that will hold our build files, and main.js to bundle and hold all our Javascript files

HTMLWebpackPlugin - will move the HTML file to build folder

CopyPlugin - will move folders to the build folder.


Let's update the package.json to use our custom build commands.

{
  "name": "minesweeper-wc",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "serve": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^4.41.5"
  },
  "devDependencies": {
    "copy-webpack-plugin": "^5.1.1",
    "html-webpack-plugin": "^3.2.0",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.2",
  }
}


build - will run using npm run build that will bundle all the codes and put it inside dist folder ready for deployment.

serve - will run using npm run serve that will serve our project in http://localhost:8000  


2. Web components  

Create the following files and folders.



src - will contain all the files and folders of our project

assets - all CSS, images and other none HTML and js files would be here

components - all of our web components would be located here.

index.html - HTML entry point 

index.js - the file that would load and bootstrap all our javascript codes


Let's now add code to our index.html:

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewportcontent="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatiblecontent="ie=edge" />
    <title>Webcomponent Minesweeper</title>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>



We will wrap everything on main tag since we are creating a PWA app.

 Now let's create our very first Web Component:


Since we are creating a Minesweeper game, let's create a tile component.

Create tile.js file under src > components > tile.js


export class Tile extends HTMLElement {
  constructor() {
    super();
  }

}

customElements.define('tile-mine', Tile);


We have extended the HTMLElement native class of Javascript, which will allow us to extend it and create our own HTML element.

customElements.define('tile-mine', Tile);

This line of code lets us define our desired tag name and its class definition.


Next is let's assign styles and adjust the HTML content of our tile-mine element.

const componentStyle = `
    
.tile {
  background:#6BBBFD;
height:100%;
width:100%;
display:flex;
justify-content:center;
align-items:center;
cursor:pointer;
transition:0.2slinear;
}

.tile-opened{
background-color:#f0f0f0;
}

.tile:hover{
opacity:0.8;
}
  
 .tile {
  background: #6BBBFD;
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  transition: 0.2s linear;
}

 .tile-opened {
   background-color: #f0f0f0;
 }

 .tile:hover {
   opacity: 0.8;
 }
    
`;

export class Tile extends HTMLElement {
  constructor() {
    super();
  }

   updateDOM() {
    this.innerHTML = `
     ${componentStyle}
     
"></div>`;
 class="tile
   }
}


customElements.define('tile-mine', Tile);


componentStyle - a variable that holds our component styles.

this.innerHTML - since we are extending HTMLElement we can now access its properties and methods, innerHTML allows us to put content in our web component.


Now let's import our Tile class inside the index.js 


import './components/tile';


Next is to add the tile component inside our index.html 

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewportcontent="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatiblecontent="ie=edge" />
    <title>Webcomponent Minesweeper</title>
  </head>
  <body>
    <main>
      <tile-mine></tile-mine>
    </main>
  </body>
</html>


Now let's run and see what we did so far:

  
npm run serve


This will run our webpack-dev-server to run our site locally, and after that let's open it on http://localhost:8000

We now see our single tile on the screen.

Now, let's update our tile component to listen to attribute changes.

Inside our src/index.html, let's create multiple tile-mine and add attributes to it. 

    <main>
      <tile-mine value="1revealed="false"></tile-mine>
      <tile-mine value="2revealed="false"></tile-mine>
      <tile-mine value="3revealed="true"></tile-mine>
      <tile-mine value="4revealed="false"></tile-mine>
      <tile-mine value="*revealed="true"></tile-mine>
      <tile-mine value="6revealed="false"></tile-mine>
      <tile-mine value="7revealed="false"></tile-mine>
      <tile-mine value="*revealed="false"></tile-mine>
    </main>



- value - refers to the value of the tile in minesweeper that ranges from 0 - 8, this value indicates how many bombs around a particular tile.

- revealed - state of the tile whether it's opened or closed.


Now let's update our src/components/tile.js


export class Tile extends HTMLElement {
  constructor() {
    super();
    this.updateDOM();
  }

  set value(value) {
    this.prop.value = value;
    this.updateDOM();
  }

  set revealed(revealed) {
    this.prop.revealed = revealed;
    this.updateDOM();
  }

  get revealed() {
    return this.prop.revealed;
  }

  get value() {
    return this.prop.value;
  }

   updateDOM() {
    const revealed = this.prop.revealed;
    const tileValue = revealed ? this.prop.value : '';
    const tileRevealedClass = revealed ? 'tile-opened' : '';

    this.root.innerHTML = `
      ${componentStyle}
      <div class="tile ${tileRevealedClass}">
        ${tileValue}
      </div>`;
  }

 static get observedAttributes() {
    return ['value', 'revealed'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'value':
        this.value = newValue;
        break;
      case 'revealed':
        this.revealed = newValue;
        break;
    }
  }
}


observedAttributes() - is a method that registers what our custom element attributes should listen when there's a change in it.

attributeChangeCallback()-  from the name itself it's a callback function that triggers whenever the property of the custom element has changed.

We also updated our updateDom() method to change the innerHTML value when there's a change in the attributes.


Reload and we'll have this output:



Before moving forward, please download the necessary assets for the game, click here to download

Put the downloaded zip inside src and extract and overwrite the existing assets folder.


Now let's write the code another component named game-manager that will hold all the tiles and manage the state of our game.

Create a new file src/components/game-manager.js



const componentStyle = `
    
    .game-manager {
        display: flex;
        flex-direction: column;
        width: 100%;
        max-width: 700px;
        height: 100%;
        align-items: center;
        padding: 3px;
    }

    .play-button,
    .pause-button {
        background-color: #4A8AF4;
        color: #ffffff;
        cursor: pointer;
        font-size: 16px;
        padding: 8px 10px;
        width: 100px;
        text-align: center;
    }

    .pause-button {
      background-color: #DD5347;
      display: none;
    }

    .game-header {
        width: 100%;
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 10px;
    }

    .time-text {
        font-size: 24px;
        color: #ffffff;
    }

    .time {
        display: flex;
        align-items: center;
    }

    .tile-container {
        height: 100%;
        width: 100%;
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
    }

    tile-mine {
      margin: 1px;
    }
    
`;

export class GameManager extends HTMLElement {
  constructor() {
    super();
    this.init();
    this.updateDOM();
    setTimeout(() => {
      this.initTiles();
    }, 100);
  }

  init() {
    this.root = this.attachShadow({ mode: 'open' });
    this.time = 0; // Time counter
    this.timeInterval = null; // For holding the time interval
    this.isPlaying = false;
    this.tilesRowCount = 10; // Tile row count
    this.tilesColCount = 10; // Tile col count
    this.tiles = []; // Tiles web component array
  }

  generateTilesContentAndBombs() {
    const tiles = Array(10)
      .fill(0)
      .map(arr => [...Array(10).fill(0)]); // creates 10 x 10 Array

    return tiles;
  }

  initTiles() {
    this.tileContainerEl = this.root.querySelector('.tile-container');
    this.tileContainerEl.innerHTML = '';

    const tileValues = this.generateTilesContentAndBombs();
    const tileWidth = this.tileContainerEl.offsetWidth / this.tilesColCount; // make tiles responsive
    const tileHeight = this.tileContainerEl.offsetHeight / this.tilesRowCount; // make tiles responsive

    this.tiles = tileValues.map((tileRow, row) => {
      return tileRow.map((tile, col) => {
        const tileEl = document.createElement('tile-mine'); // create our custom tile Element

        tileEl.style.width = `${tileWidth - 2}px`; // Put a little bit of margin
        tileEl.style.height = `${tileHeight - 2}px`;

        this.tileContainerEl.appendChild(tileEl); // Appends tile inside the .tile-container

        return tileEl;
      });
    });
  }

updateDOM() {
    this.root.innerHTML = `
        ${componentStyle}
        <div class="game-manager">
            <div class="game-header">
                <div class="time">
                    <img src="./assets/images/time.png" height="40">
                    <span id="timeText" class="time-text"></span>
                </div>
                <div class="time">
                    <img src="./assets/images/bomb.png" height="40">
                    <span class="time-text">15</span>
                </div>
                <div id="playButton" class="play-button">Play</div>
                <div id="pauseButton" class="pause-button">Pause</div>
            </div>
            <div class="tile-container">
              
            </div>
        </div>
      `;
  }
}

customElements.define('game-manager', GameManager);

 updateDom() - sets the element innerHTML.

 init() - initializes component's variable and shadowDom

 initTiles() - creates a 10 x 10 tile grid using our Tile web component 

 generateTilesAndBomb() - a function that will hold the generation of bombs and counting of bombs per tile


Now let's update our src/index.html file, to add our game-manager web component

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewportcontent="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatiblecontent="ie=edge" />
    <link rel="stylesheethref="assets/css/style.css" />
    <title>Webcomponent Minesweeper</title>
 </head>
  <body>
    <main>
      <game-manager></game-manager>
    </main>
  </body>
</html>


If we refresh the page we will see this screen:




If we click the tile nothing will happen for now, so now let's add functionality.

The first functionality that we are going to add is a counting time.

Add this function inside the GameManager class.


  initTimeInterval() {
    clearInterval(this.timeInterval); // clear time interval if there's any existing one
    this.timeInterval = setInterval(() => {
      if (this.isPlaying) {
        this.time++; // Increments the time per second
        this.root.querySelector('#timeText').innerHTML = this.time;
      }
    }, 1000);
  }


- this.timeInterval -  will hold the setInterval function, that will execute the codes inside every 1 second or 1000 ms

A minesweeper game wouldn't be one if there's no mines or bomb, so now let's add a function that will add the random position of bombs to our game.

Add these two functions inside the GameManager class.

  addBombToTiles(tiles) {
    const takenCells = [];
    const numberOfBombs = 15;

    for (let i = 0; i < numberOfBombs; i++{
      let r;
      let c;
      do {
        r = Math.floor(Math.random() * 10);
        c = Math.floor(Math.random() * 10);
      } while (takenCells.includes(`${r}-${c}`));
      takenCells.push(`${r}-${c}`);
      tiles[r][c] = '*';
    }
    return tiles;
  }


countTilesBomb(tiles) {
    const lastRowIndex = this.tilesRowCount - 1;
    const lastColIndex = this.tilesColCount - 1;
    for (let r = 0; r < this.tilesRowCount; r++{
      for (let c = 0; c < this.tilesColCount; c++{
        if (tiles[r][c] === 0{
          let bombCount = 0;
          const minR = r === 0 ? 0 : r - 1;
          const maxR = r === lastRowIndex ? lastRowIndex : r + 1;
          const minC = c === 0 ? 0 : c - 1;
          const maxC = c === lastColIndex ? lastColIndex : c + 1;
          for (let ir = minR; ir <= maxR; ir++{
            for (let ic = minC; ic <= maxC; ic++{
              bombCount += +(tiles[ir][ic] === '*');
            }
          }
          tiles[r][c] = bombCount;
        }
      }
    }
    return tiles;
  }

addBombToTiles()  - will add bombs in random positions to our tiles array

countTilesBomb() - will put value from 0 - 8 to a non-bomb tile, that indicates the number of bombs around it

Let's update the generateTilesContentAndBombs() to implement addBombToTiles() and countTilesBomb()

  generateTilesContentAndBombs() {
    const tiles = Array(10)
      .fill(0)
      .map(arr => [...Array(10).fill(0)]);

    return this.countTilesBomb(this.addBombToTiles(tiles)); // Modified part
  }

Next is to add the opening of tiles functionality.

Add these 3 functions to our GameManager class

  clickTile(row, col) {
    if (this.isPlaying) {
      this.openTile(row, col);
    }
  }

  openTile(r, c) {
    const tile = this.tiles[r][c];
    if (tile.value === '*'{
      tile.revealed = true;
      this.isPlaying = false;
      setTimeout(() => {
        this.gameOver();
      }, 1000);
    } else {
      this.recursiveOpenTiles(r, c);
      this.checkWin();
    }
  }

  recursiveOpenTiles(r, c) {
    const tile = this.tiles[r][c];
    if (tile.revealed) {
      return;
    }
    if (tile.value !== 0 && tile.value !== '*'{
      tile.revealed = true;
      return;
    }
    tile.revealed = true;
    // up
    if (r > 0this.recursiveOpenTiles(r - 1, c);
    // Down
    if (r < 9this.recursiveOpenTiles(r + 1, c);
    // left
    if (c > 0this.recursiveOpenTiles(r, c - 1);
    // Down
    if (c < 9this.recursiveOpenTiles(r, c + 1);
  }


clickTile() -  will open a tile base on a given row and column

openTile() - will open a tile and checks if its value is * which indicates a mine and it will trigger the gameOver function that we will create later 

recursiveOpenTiles() - opens tiles recursively when the tile opened has other tiles around it whose value is 0, it will cause a chain effect until it opened a tile with a number greater than 0

Next, let's update the initTiles() function to add click event in every tile

 initTiles() {
    this.tileContainerEl = this.root.querySelector('.tile-container');
    this.tileContainerEl.innerHTML = '';

    const tileValues = this.generateTilesContentAndBombs();
    const tileWidth = this.tileContainerEl.offsetWidth / this.tilesColCount;
    const tileHeight = this.tileContainerEl.offsetHeight / this.tilesRowCount;

    this.tiles = tileValues.map((tileRow, row) => {
      return tileRow.map((tile, col) => {
        const tileEl = document.createElement('tile-mine');

        tileEl.style.width = `${tileWidth - 2}px`;
        tileEl.style.height = `${tileHeight - 2}px`;
        tileEl.value = tile;
        tileEl.addEventListener('click', () => this.clickTile(row, col)); // Add this line

        this.tileContainerEl.appendChild(tileEl);

        return tileEl;
      });
    });
  }


Next is let's update our Tile Web component, to display bomb image instead of * and other visual related codes.

Update the existing updateDom()  to use the tileValue() function which maps its value in a presented manner.


updateDOM() {
    const revealed = this.prop.revealed;
    const tileValue = revealed ? this.tileValue() : '';
    const tileRevealedClass = revealed ? 'tile-opened' : '';

    this.root.innerHTML = `
      ${componentStyle}
      <div class="tile ${tileRevealedClass}">
        ${tileValue}
      </div>`;
  }

  tileValue() {
    const value = this.prop.value;
    const numberColors = [
      '#18E54C',
      '#1FA363',
      '#4A8AF4',
      '#18B1E5',
      '#FFCD42',
      '#FF8F6B',
      '#E0683D',
      '#DD5347'
    ];
    if (value === '*'{
      return `<img src="./assets/images/bomb.png" height="20">`;
    } else if (+value > 0{
      return `<span style="color: ${numberColors[+value - 1]};">${value}<span>`;
    }

    return '';
  }

  

Now let's wrap our game functionality by adding gameOver, checkWin and other necessary functionalities

Before that let's add some fancy alert Javascript Library using SweetAlert


npm install sweetalert2 --save


Import it inside the game-manager.js file 

import Swal from 'sweetalert2';


Now let's add these functions to our GameManager 

welcomeMessage() {
    Swal.fire({
      title: 'Welcome',
      html: 'Created by Stephen Vinuya',
      icon: 'success',
      onClose: () => this.play()
    });
  }

  initButtons() {
    this.playBtnEl = this.root.querySelector('#playButton');
    this.pauseBtnEl = this.root.querySelector('#pauseButton');

    this.playBtnEl.addEventListener('click', () => this.play());
    this.pauseBtnEl.addEventListener('click', () => this.pause());
  }

  pause() {
    this.isPlaying = false;
    this.playBtnEl.style.display = 'block';
    this.pauseBtnEl.style.display = 'none';
  }

  play() {
    this.isPlaying = true;
    this.playBtnEl.style.display = 'none';
    this.pauseBtnEl.style.display = 'block';
  }

  gameOver() {
    Swal.fire('Game over', 'Restart the game', 'error');
    this.resetGame();
  }

  resetGame() {
    this.isPlaying = false;
    this.time = 0;
    this.root.querySelector('#timeText').innerHTML = this.time;
    this.initGame();
  }

  checkWin() {
    const tilesWithoutBomb = this.tiles
      .reduce((acc, tilesRow) => [...acc, ...tilesRow], [])
      .filter(tile => tile.value !== '*'); // Gets all tiles which are not bomb
    const isAllTilesOpen = tilesWithoutBomb.every(tiles => tiles.revealed); // checks it all tiles that are not bomb is revealed

    if (isAllTilesOpen) {
      Swal('Game over', 'You win the game!', 'success');
      this.resetGame();
    }
  }

  initGame() {
    this.initTimeInterval();
    this.initTiles();
    this.welcomeMessage();
  }


welcomeMessage() - show's a welcome message using SweetAlert dialog

initButtons() - initializes button element for pause and play and assigns click event to it

play() -  play the time and allow for tile click

pause() - pause the time and don't allow for tile click

gameOver() - shows a game over message and resets the game

resetGame() -  resets our game state

checkWin() - checks if the player has already won the game, and show a success SweetAlert dialog

initGame() - initializes or starts the game 


Next is let's update our constructor method inside the GameManager class to use some of our newly created method

  constructor() {
    super();
    this.init();
    this.updateDOM();
    setTimeout(() => {
      this.initButtons();
      this.initGame();
    }, 100);
  }


We are wrapping the code for initButtons and initGame  functions inside a setTimeout cause there's some delay on our view initialization


After refreshing the page, you can see that we have completed now our game.



If you are satisfied already, you can go now and relax, but if you want to stay, we will now make our game, a PWA app using Workbox

PWA stands for Progressive Web Application, we're in our app can work offline, can have push notifications and can be installable.



Now, let's start!

First, we need to install the workbox-cli and its webpack-plugin via terminal


npm install workbox-cli -g && npm i workbox-webpack-plugin -D


Next is to run the workbox wizard


workbox wizard 


Follow the steps below:


And it will generate our workbox-config.js file

Next, it to update our webpack.config.js file

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { GenerateSW } = require('workbox-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CopyPlugin([{ from: './src/assets', to: 'assets' }]),
    new GenerateSW({
      swDest: './sw.js'
    })
  ],
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 8000
  }
};


And let's update our index.js file to register our service worker.

import './components/tile';
import './components/game-manager';

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('./sw.js');
  });
}


Next, is to generate a manifest.json using this online generator https://app-manifest.firebaseapp.com/,to tell our browser that this web app is installable


Fill the form up, make sure to choose for Portrait for orientation and Fullscreen for display mode, and choose any icon of your preference.

Then click Generate zip,  you will have these files inside the zip.


Move all the images in the images folder into our app src/assets/images/icons

And the manifest.json inside the src.

And let's reference the manifest.json inside the index.html

DOCTYPE html>
<html lang="en">
  <head>
    <link rel="manifesthref="manifest.json" />
    <meta charset="UTF-8" />
    <meta name="viewportcontent="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatiblecontent="ie=edge" />
    <link rel="stylesheethref="assets/css/style.css" />
    <title>Webcomponent Minesweeper</title>
  </head>
  <body>
    <main>
      <game-manager></game-manager>
    </main>
  </body>
</html>


Now we are all good!

Demo: https://onecompileman.com/minesweeper-web-component/

Repository: https://github.com/onecompileman/minesweeper-web-component



Read More
MY Profile

Stephen G. Vinuya
Aug 18,2019

My 1-year journey to becoming a Microsoft MVP



Hi, I'm Stephen Vinuya, Fullstack Developer and a Microsoft MVP(Most Valuable Professional)

for Developer Technologies (2019 - 2020). In this article, I'm going to share with you guys my 1-year journey

to becoming a Microsoft MVP and how reaching this award has greatly helped me towards the growth of my career.


But wait... What is a Microsoft MVP to begin with?


According to the Microsoft MVP website:

Microsoft Most Valuable Professionals, or MVPs, are technology experts who passionately share their knowledge with the community. They are always on the "bleeding edge" and have an unstoppable urge to get their hands on new, exciting technologies. They have very deep knowledge of Microsoft products and services, while also being able to bring together diverse platforms, products and solutions, to solve real world problems. MVPs make up a global community of over 4,000 technical experts and community leaders across 90 countries/regions and are driven by their passion, community spirit, and quest for knowledge.

But for me, I see it as the Nobel Prize Award for the Information Technology Industry. Since the MVP award is only awarded to someone

who greatly helped, contributed and encourage other people in the IT industry. And not to mention that we are only 2863 as of the moment I'm writing this article across the world.

And we have only 8 MVPs in my country, the Philippines.


I know what you are thinking, yeah that's cool!



So now you might be interested to become an MVP too.



What do you need to do to become one? Being awarded as a Microsoft MVP is usually a 1-year process, wherein the Microsoft MVP Award's Team

will review your year's worth of contributions in the community, and of course, the more contributions you did the higher the chance of becoming one.

Those contributions can be a talk, seminar about your expertise, open-source projects, blogs, and even tutorial videos. So you want to start contributing now,

but wait.. there are certain topics of contribution which are credited by Microsoft, and yes since its a Microsoft award all topics must be related to Microsoft's technologies.


Below are the technologies that your contribution should be related to:

Now you might be thinking why should you become one? Like why do I have to do a year's worth of contribution just to become one?

And also it is not certain that you'll automatically become one, cause the contributions you did will undergo a reviewal process.


It will not also give you money, or will it not?

Let me share with you this, that my salary back when I was still a Junior Web Developer(just 2.5 years ago) is exactly 5 times what I'm earning right now.

And it's also not because I'm awarded as a Microsoft MVP, its because I dreamed and aspired to be a one. It's the JOURNEY of becoming an MVP that

made a huge upgrade to my skill set and my salary also, not just the MVP status itself.


Now let me share with you, the JOURNEY.


It all started when I was about to finish my degree and received my diploma, I decided to attend a coding boot camp.

Wherein speakers will teach the attendees a certain topic and do live coding in front, for us attendees to follow.

The experience was fantastic, and I was very amazed at the speaker that time, for the courage and expertise he has, to be able to code in front smoothly

without having to open Google if ever he encountered an error.



The very first coding boot camp I have attended, Trinmar Boado was the speaker of this event about Feather.js + Angular.js


Then after receiving my diploma, I got a job at FFUF Inc. Manila, as Junior Web Developer, I was assigned to a team to work with various projects.

The usual Junior Web Developer, I consulted my team lead, asks for things I don't know, and assistance to my tasks. Back then we used to have a lot of free time

in the company, what I always do is to watch Youtube tutorials related to development that piques my interest, then I discovered p5.js, by a Youtube channel

that Daniel Shiffman started, he was fun to watch, he codes fast and he explains every single detail perfectly. So, every single day that I have free time at work

I would watch his videos and learn new things.


So I applied what I learned.


Then I after watching all of his videos and learned it, I got this sudden feeling that I need to share this with other people

for them to know this wonderful knowledge that I have learned.



Then after days have passed I got a message from my university where I graduated, to do a seminar for the students with the topic of my preference,

I immediately replied: "Yes, I would love to", but the excitement I had suddenly turned into fear, asking the questions to myself, can I really talk upfront? what if I buckled up or

forgot the codes that I will teach.


Then the day of the event happened.



My very first coding boot camp, at National University about HTML5 Game Development


I was very nervous at first, but when I saw that the students learned something and started to ask me things about what they learned, showing that they are interested,

that was PRICELESS for me. Then it motivated me to do more coding boot camps, so I messaged some of the organizations in my country that

conducts coding seminars and boot camps such as DevCon, Ground Gurus to let myself be invited as a resource speaker.


Then the rest is history.


My First boot camp at Ground Gurus about HTML5 Game development using p5.js



At DevCon PH, about HTML5 Game development



Campus DevCon at STI Novaliches about Angular State Management



At Angular Philippines, about PWA development using Angular


Then little do I know that my skill set is growing inch by inch as I do coding boot camps, my career is also growing at FFUF Manila Inc., that I was promoted

to Mid Level Web Developer and later on to a Team Leader, all in one year. All the new things I have to learn so that I can share my experience has greatly helped me

in reaching the Mid Level Web Developer promotion, then my ability to speak upfront and also to speak in a way that others would understand what I'm saying helped me

a lot to be promoted as a Team Leader.

Then I met an MVP who is also one of the speakers of an event where I was invited as a speaker also, it piques my interest so I ask him how can I become one?

He gave me a lot of advice to help me reached the award. I followed them one by one, I started to do blogs, answer questions to StackOverflow, do talks over and over and over again.


Then the rest is history, 3 months after Devlin Duldulao also a Microsoft MVP the person who also helped me and guide me to become one, nominated me as an MVP,

I was blessed with the great news from being 7 Microsoft MVPs in the Philippines it's now 8, where my name is now included in the list.


Now, I'm working with AlphaZetta as a Platform Developer, still doing talks, blogs, and open source projects.

I'm very grateful to those people who inspired me in reaching this dream.

For me:

Achieving something isn't always about reaching it, it's always the JOURNEY that you have been, sacrifices, passion and also the time you have to spend in order to achieve that counts.

I'm Stephen Vinuya, Microsoft MVP

https://mvp.microsoft.com/en-us/PublicProfile/5003374?fullName=Stephen%20Galang%20Vinuya


Thank you for reading!





Read More
MY Profile

Stephen G. Vinuya
Aug 17,2019

Creating a particle system using ES6 and p5.js


In this article, we are going to explore what a particle system is and how to create them in HTML5 using p5.js.


First, let's define and know what a particle system is.


Particle System is a term coined by William T. Reeves.

William "Bill" Reeves Pixar.jpg

William "Bill" Reeves (born May 5, 1959) is a Canadian animator and technical director known for working with John Lasseter on the animated shorts Luxo Jr. and The Adventures of Andre and Wally B.

Reference: https://en.wikipedia.org/wiki/William_Reeves_(animator)


According to Wikipedia :

particle system is a technique in game physics motion graphics, and computer graphics that uses a large number of very small sprites3D models, or other graphic objects to simulate certain kinds of "fuzzy" phenomena, which are otherwise very hard to reproduce with conventional rendering techniques - usually highly chaotic systems, natural phenomena, or processes caused by chemical reactions.

- According to Allen Martin here

The term, particle system is loosely defined in computer graphics. It has been used to describe modeling techniques, rendering techniques, and even types of animation


For me, Particle System is a group of particles banded together to give users feedback or emphasis based on the action they have made.


So you might be wondering what does a particle system looks like?


Image result for wondering


This is one of the pictures that I got from google, that best describes what a particle system is.

Image result for particle system in games

Now you might be thinking how does this weird looking animation or graphics, can be used?


Here's a sample video on what it looks when a game doesn't have a particle system:


If we see it the game looks bland and boring.


But if we add a particle system to it, it should look like this:


Now that we know what a particle system is. Let's create our own Particle System!

Image result for excited


First, let us download p5.js here


Under the Single Files, section click p5.js.

After that let's create our project folder, and paste the p5.js, downloaded file inside the project folder

After that create 2 files, named: index.html and sketch.js, then open the project folder inside our text editor.

The contents should be like this:




Let's now put contents inside our index.html:


<html lang="en">

<head>
<meta charset="UTF-8/">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>Documenttitle>
>

<body>

<script src="p5.js"></script>
<script src="sketch.js"></script>
body>

html>


index.html's inside this file, we just need to reference our 2 javascript files.

Now let's proceed writing our p5.js codes

Inside our sketch.js file, write these codes:

function setup() {
createCanvas(innerWidth, innerHeight);
}

function draw() {
background(0);
}


Now let's discuss the two functions that we see on the screen:

  • setup - is being called once after the p5.js has been loaded
  • draw - is being called every frame after setup function has been called, p5.js by default has 60 frames per second meaning that draw is being called 60 times per second.

createCanvas - creates a canvas on our screen, with set width and height, by default we are setting it as the width and height of our browser's window.

background - defines the color of our canvas, 0 denotes that we have a black background.


Before creating our particle system, what properties should a particle system contain?

According to William T. Reeves each particle in the particle system should have the following properties

  • Position - (x, y, z) position of the particle system in computer screen
  • Velocity - denotes the speed and the direction of the particle system
  • Color - color of each particles
  • Lifetime - time when all particles should disappear 
  • Age - number that alters particle's size and transparency
  • Shape - shape of the particle
  • Size -  size of the paritcle
  • Transparency - opacity value of each particles


Now let's create our particle class having these properties, using ES6 class.

Create a new file named particle.js, and put the contents below:

class Particle {

constructor(pos, vel, col, life, shape, size, opacity, force) {
this.pos = pos; // Position
this.vel = vel; // Velocity
this.col = col; // Color
this.life = life; // Lifetime
this.shape = shape; // Shape p5.js 2d only supports rect and ellipse
this.size = size; // size
this.opacity = opacity; // transparency
    this.force = force; // additional property for adding gravity
}

}


After that let's import our particle.js file into our index.html

<script src="p5.js"></script>
<script src="sketch.js"></script>
<script src="particle.js"></script>


Now let's create a function to display our particle class.

class Particle {

constructor(pos, vel, col, life, shape, size, opacity, force) {
this.pos = pos;
this.vel = vel;
this.col = col;
this.life = life;
this.origLife= life; // saves the originally set life variable
this.shape = shape;
this.size = size;
this.opacity = opacity;
this.force = force;
}

render() {
push(); // push the state
translate(this.pos.x, this.pos.y); // translate the current screen position to x, y
stroke(this.col, this.opacity); // stroke color and its transparency
noFill(); // inidicates that our shape should have no fill color inside

if (this.shape === 'rect') {
rect(0, 0, this.size.x, this.size.y); // for displaying rect takes, x, y, width, height
} else {
ellipse(0, 0, this.size.x, this.size.y); // for displaying ellipse takes, x, y, width, height
}

pop(); // pops our state
}

}


Now let's create the Particle class inside our sketch.js

let particle;

function setup() {
createCanvas(innerWidth, innerHeight);

particle = new Particle(
createVector(width / 2, height / 2), // position at the center of the window
createVector(0, 1), // velocity
color(255, 87, 120), // color
100, // lifetime
'rect', // shape
createVector(15, 15), // size width and height
255, // transparency from 0 to 255
createVector(0, 0) // force
);
}

function draw() {
background(0);
particle.render(); // display the particle
}


After we reload our page we should see something like this:


Now let's make our particle move or be animated.


class Particle {

constructor(pos, vel, col, life, shape, size, opacity, force) {
this.pos = pos;
this.vel = vel;
this.col = col;
this.life = life;
this.origLife = life;
this.shape = shape;
this.size = size;
this.opacity = opacity;
this.force = force;
}

render() {
push();
translate(this.pos.x, this.pos.y);
stroke(this.col, this.opacity);
noFill();

const lifePercentage = this.life / this.origLife;

if (this.shape === 'rect') {
rect(0, 0, this.size.x * lifePercentage, this.size.y * lifePercentage);
} else {
ellipse(0, 0, this.size.x * lifePercentage, this.size.y * lifePercentage);
}

pop();
}

update() {
this.vel.add(this.force);
this.pos.add(this.vel);
this.vel.limit(3); // limits the velocity speed to 3
this.life--; // decrements life as time passes
this.opacity = map(this.life, 0, this.origLife, 0, 255); // age opacity as life decreases
}

isDead() {
return this.life <= 0;
}

}


Let's discuss the codes here one by one. 

Update():

update() {
this.vel.add(this.force);
this.pos.add(this.vel);
this.vel.limit(3); // limits the velocity speed to 3
this.life--; // decrements life as time passes
this.opacity = map(this.life, 0, this.origLife, 0, 255); // age opacity as life decreases
}


add - adds two vector together, these two lines are responsible for acceleration and movement of our particle.

this.vel.add(this.force);
this.pos.add(this.vel);


As you have noticed we don't have yet the age property of the particle that according to William T. Reeves, should have.

These two lines of code are responsible for that:

As we defined earlier age, defines the alteration of transparency and size of a particle

This line of code denotes the shrinkage of a particle as life decreases.


const lifePercentage = this.life / this.origLife; // gets the life percentage

if (this.shape === 'rect') {
rect(0, 0, this.size.x * lifePercentage, this.size.y * lifePercentage);
} else {
ellipse(0, 0, this.size.x * lifePercentage, this.size.y * lifePercentage);
}

This line of code denotes the decrease of opacity value of a particle as life decreases.

this.life--; // decrements life as time passes
this.opacity = map(this.life, 0, this.origLife, 0, 255); // age opacity as life decreases


After that let's call the Particle class' update function inside our sketch.js class.

function draw() {
background(0);
particle.update();
particle.render();
}


Now we should be able to see something like this: 


Cool, now let's move to create our ParticleSystem class, it would be responsible for creating multiple particles and animating them.

Create a new file named particle-system.js, and put the contents below:

class ParticleSystem {

constructor(pos, life, size, opacity, force, count, generationSpeed) {
this.pos = pos;
this.life = life;
this.origLife = life;
this.size = size;
this.opacity = opacity;
this.force = force;
this.count = count;
this.generationSpeed = generationSpeed;
this.particles = [];
this.generateParticles();
}

generateParticles() {
this.particles = [
...Array(this.count).fill(1).map(n => new Particle(
this.pos.copy(),
createVector(random(-1, 1), random(-1, 1)),
color(random(180, 255), random(100, 255), random(100, 255)),
this.life,
random(['rect', 'ellipse']),
this.size,
this.opacity,
this.force
)),
...this.particles
]
}

update() {
this.particles = this.particles.filter(p => !p.isDead());
if (frameCount % this.generationSpeed === 0) {
this.generateParticles();
}
}

render() {
this.particles.forEach(p => {
p.update();
p.render();
});
}
}


Let's discuss the codes here one by one. 

We have added here two new properties inside the particle-system.js:

  • generationSpeed - frame speed when should we create number of particles.
  • count - n number of particles per generation 

For the following codes, I will explain it's use thru comments

generateParticles():

generateParticles() {
this.particles = [
...Array(this.count).fill(1).map(n => new Particle(
this.pos.copy(), // copy function clones the position object to avoid reference copy to the source
createVector(random(-1, 1), random(-1, 1)), // creates random velocity between -1,1
color(random(180, 255), random(100, 255), random(100, 255)), // creates random reddish color
this.life,
random(['rect', 'ellipse']), // selects a random shape between rect or ellipse
this.size,
this.opacity,
this.force
)),
...this.particles
] // concatenate two arrays
}


update():

update() {
this.particles = this.particles.filter(p => !p.isDead());// checks if particle is dead and remove it in array
if (frameCount % this.generationSpeed === 0) { // check if the current frameCount is in generationSpeed
this.generateParticles();
}
}


After that let's import our particle-system.js file inside our index.html:

<script src="p5.js"></script>
<script src="sketch.js"></script>
<script src="particle.js"></script>
<script src="particle-system.js"></script>


And now let's create a particle system every mouse click.

Let's update our sketch.js file:

let particleSystems = []; // Holds the particle system that we will create

p5.disableFriendlyErrors = true; // for optimization

function setup() {
createCanvas(innerWidth, innerHeight);
}

function draw() {
background(0);
renderParticleSystem(); // displays particle system
}

function mousePressed() {
particleSystems.push(
new ParticleSystem(
createVector(mouseX, mouseY),// creates particle system based mouse location
80, // particle dies every 80th frame
createVector(10, 10), // size
255, // transparency 255
createVector(0, -0.05), force make the particle going up
10, // count of particle per generation
10 // number of frames per generation
)
);
}

function renderParticleSystem() {
particleSystems.forEach(pS => {
pS.update();
pS.render();
});
}


After that, it should look like this:


Cool, now we're done, you are free to use the codes and use them.


Image result for accomplished picture 


Try it https://www.openprocessing.org/sketch/745356

Download the code: https://github.com/onecompileman/particle-system



Read More
MY Profile

Stephen G. Vinuya
Dec 19,2018

Making a Simple Space Shooter using p5.js and ES6 part 3


This is the continuation of the part of this article series about "Making a Simple Space Shooter using p5.js and ES6".


What we have accomplished so far is to make the Player class fire bullets and to generate Enemies.


Next is let's make the game re initialize when, an Enemy collide with the Player.


1. Gameover function

renderPlayer() {
this.player.pos.x = constrain(mouseX, this.player.size.x / 2, width - (this.player.size.x / 2));
this.player.update();
this.player.show();

const bullet = this.player.fireBullet(this.assetManager.bulletImg);
if(bullet) {
this.bullets.push(bullet);
}

if (this.enemies.reduce((acc, e) => acc || e.isCollided(this.player.pos, this.player.size),false)) {
this.init();
}
}


We have updated the our renderPlayer()  to check if it has collided to an enemy, then repeat the game otherwise continue.


2. Stars and score

On the popular Messenger game Everwing, player earns score, when he/she catches the falling coins.  Let's make our own version of it using stars as alternative to coins.


Let's update our GameManager class, init() method :

init() {
this.bullets = [];
this.enemies = [];
this.enemyGenerateSpeed = 200;
this.enemySpeed = 2;
this.stars = [];
this.score = 0;
this.player = new Player(
createVector(width / 2, height - 100),
createVector(0, 0),
this.assetManager.playerImg,
createVector(75, 75),
100,
'player'
);
}


We have added two new variables in the init() function:

- stars - will contains all the stars that is currently in the canvas.

- score - will the count of stars that the player catches


Let's now update our renderEnemies method inside the GameManager class, to create a star every time an enemy dies.

renderEnemies() {
this.enemies.forEach(e => {
e.update();
e.show();
});

this.enemies = this.enemies.filter(e => {
let isDead = e.life < 0;
if (isDead) {
this.stars.push(new GameObject( // creates a new star and push it in the stars array
e.pos,
createVector(random(-1, 1), random(3, 6)), // random velocity
this.assetManager.starImg,
createVector(25, 25),
60,
'star'
))
}
return !isDead;
});
}


After that, we just need to show the stars by creating a new method inside GameManager class named renderStars(), it shows and updates star's position and also check if the star collided to the player

remove the star and increments the score.

renderStars() {
this.stars.forEach(s => {
s.update();
s.show();
});

this.stars = this.stars.filter(s => {
const isCollided = s.isCollided(this.player.pos, this.player.size);
this.score += (isCollided) ? 1 : 0; // increments the score if the player has collided to the star
return !isCollided;
});
}



Let's now create a new method on our GameManager class named renderScore(), to display the score :


renderScore() {
push();
translate(10, 10);
image(this.assetManager.starImg, 0, 0, 30, 30);
fill(255);
textSize(30);
text(`: ${this.score}`, 40, 28);
pop();
}



Now we are all set, let's call all the new methods we have added, in the update method of our GameManager class:

update() {
this.renderPlayer();
this.renderBullets();
this.renderEnemies();
this.generateEnemy();
this.renderStars();
this.renderScore();
}


Our GameManager class now should look like this: 

class GameManager {

constructor() {
this.assetManager = new AssetManager();
}

init() {
this.bullets = [];
this.enemies = [];
this.enemyGenerateSpeed = 200;
this.enemySpeed = 2;
this.stars = [];
this.score = 0;
this.player = new Player(
createVector(width / 2, height - 100),
createVector(0, 0),
this.assetManager.playerImg,
createVector(75, 75),
100,
'player'
);
}

loadAssets() {
this.assetManager.preload();
}

update() {
this.renderPlayer();
this.renderBullets();
this.renderEnemies();
this.generateEnemy();
this.renderStars();
this.renderScore();
}

renderBullets() {
this.bullets.forEach(b => {
b.update();
b.show();
});

this.bullets = this.bullets.filter(b => {
let isCollided = false;

this.enemies = this.enemies.map(e => {

if (!isCollided && b.isCollided(e.pos, e.size)) {
e.life -= this.player.damage;
isCollided = true;
}
return e;
});

return !(b.pos.y < 0 || isCollided);
});
}

renderPlayer() {
this.player.pos.x = constrain(mouseX, 37.5, width - 37.5);
this.player.update();
this.player.show();

const bullet = this.player.fireBullet(this.assetManager.bulletImg);
if (bullet) {
this.bullets.push(bullet);
}
}

generateEnemy() {
if (frameCount % this.enemyGenerateSpeed === 0) {
const enemyCount = random(2, 6);
let posX = [0, 1, 2, 3, 4, 5];

[...Array(parseInt(enemyCount)).keys()].forEach(a => {
let xIndex = posX.splice(parseInt(random(0, posX.length - 1)), 1)[0];

this.enemies.push(new GameObject(
createVector((xIndex * 75) + 37.5, 0 - 75),
createVector(0, this.enemySpeed),
random(this.assetManager.enemiesImg),
createVector(70, 70),
60,
'enemy'
));
})

}
}

renderEnemies() {
this.enemies.forEach(e => {
e.update();
e.show();
});

this.enemies = this.enemies.filter(e => {
let isDead = e.life < 0;
if (isDead) {
this.stars.push(new GameObject(
e.pos,
createVector(random(-1, 1), random(3, 6)), // random velocity
this.assetManager.starImg,
createVector(25, 25),
60,
'star'
))
}
return !isDead;
});
}

renderStars() {
this.stars.forEach(s => {
s.update();
s.show();
});

this.stars = this.stars.filter(s => {
const isCollided = s.isCollided(this.player.pos, this.player.size);
this.score += (isCollided) ? 1 : 0;
return !isCollided;
});
}

renderScore() {
push();
translate(10, 10);
image(this.assetManager.starImg, 0, 0, 30, 30);
fill(255);
textSize(30);
text(`: ${this.score}`, 40, 28);
pop();
}

}


After we run our game it will look like this:


And that concludes our article series.


Source File: https://github.com/onecompileman/sample-space-shooter



Read More
MY Profile

Stephen G. Vinuya
Dec 19,2018

Making a Simple Space Shooter using p5.js and ES6 part 2

            


This is the continuation of the part 1 of this article series about "Making a Simple Space Shooter using p5.js and ES6".


What we have accomplished so far is to create our GameManager class and make our Player object move to the canvas.


To continue let's make our player fire bullets.


1. Firing Bullets


Let's add a fireBullet() function to our Player class.

class Player extends GameObject {

constructor(pos, vel, img, size, life, tag) {
super(pos, vel, img, size, life, tag);
this.fireRate = 10;
this.damage = 20;
this.bulletSpeed = 15;
}

fireBullet(bulletImg) {
return (frameCount % this.fireRate === 0) ? new GameObject(
createVector(this.pos.x, this.pos.y - (this.size.y / 2)), // position
createVector(0, -this.bulletSpeed), // velocity
bulletImg, // image asset
createVector(5, 12), // size
100, // life
'bullet' // tag
) : null;
}

}


-fireBullet() - takes bulletImg image as an argument that is defined in our AssetManager class

-  (frameCount % this.fireRate === 0)  it will only fire every frameCount that is divisible every frameRate we defined, otherwise return null meaning no bullet will be fired at that moment

- We are using this.bulletSpeed  in negative as our y velocity since the bullet will be pointing upwards


Now let's update our GameManager class 

class GameManager {

constructor() {
this.assetManager = new AssetManager();
}

init() {
this.bullets = [];
this.player = new Player(
createVector(width / 2, height - 100),
createVector(0, 0),
this.assetManager.playerImg,
createVector(75, 75),
100,
'player'
);
}

loadAssets() {
this.assetManager.preload();
}

update() {
this.renderPlayer();
this.renderBullets();
}

renderBullets() {
this.bullets.forEach(bullet => {
bullet.update();
bullet.show();
});

this.bullets = this.bullets.filter(b => !(b.pos.y < 0));
}

renderPlayer() {
this.player.pos.x = constrain(mouseX, 37.5, width - 37.5);
this.player.update();
this.player.show();

const bullet = this.player.fireBullet(this.assetManager.bulletImg);
if (bullet) {
this.bullets.push(bullet);
}
}

}


On the GameManager init, we added this.bullets = [];, to hold all of our bullets in the game.

- renderBullets() - show and updates all the bullets in the screen

this.bullets = this.bullets.filter(b => !(b.pos.y < 0));    -removes all the bullets that are leaving the screen to prevent memory leak

- We have updated renderPlayer() to invoke Player's fireBullet() function on every function call, and only add the bullet if it is not null


After running the game, we can see an output like this:


Cool now we have a Player firing bullets, next to create our enemies.


2. Enemies and Collision

Before we create our enemy class, let's add a collision detection on our GameObject class.


isCollided(gameObjPos, gameObjSize) {
return ((gameObjSize.y + this.size.x) / 2) > (this.pos.dist(gameObjPos));
}



We are checking here if the distance from gameObject's position we are comparing it with, is less than the combination of two sizes of both gameObject, then it is colliding with each other, it will return true.

 

Let's now update our GameManager class. 

class GameManager {

constructor() {
this.assetManager = new AssetManager();
}

init() {
this.bullets = [];
this.enemies = [];
this.enemyGenerateSpeed = 200;
this.enemySpeed = 2;
this.player = new Player(
createVector(width / 2, height - 100),
createVector(0, 0),
this.assetManager.playerImg,
createVector(75, 75),
100,
'player'
);
}

loadAssets() {
this.assetManager.preload();
}

update() {
this.renderPlayer();
this.renderBullets();
this.renderEnemies();
this.generateEnemy();
}

renderBullets() {
this.bullets.forEach(b => {
b.update();
b.show();
});

this.bullets = this.bullets.filter(b => {
let isCollided = false; // variable to check if the player is yet to collide to an enemy

this.enemies = this.enemies.map(e => {

if (!isCollided && b.isCollided(e.pos, e.size)) {// only check if the bullet has collided or not
e.life -= this.player.damage; //if the bullet has collided to an enemy subtract player damage to enemy life
isCollided = true; //set the collision to true
}
return e;
});

return !(b.pos.y < 0 || isCollided); // removes enemies that is out of the canvas and whose life is 0
});
}

renderPlayer() {
this.player.pos.x = constrain(mouseX, 37.5, width - 37.5);
this.player.update();
this.player.show();

const bullet = this.player.fireBullet(this.assetManager.bulletImg);
if (bullet) {
this.bullets.push(bullet);
}
}

generateEnemy() {
if (frameCount % this.enemyGenerateSpeed === 0) {
const enemyCount = random(2, 6); // random enemy count 2-6
let posX = [0, 1, 2, 3, 4, 5]; // x positions for the enemies to be generated

[...Array(parseInt(enemyCount)).keys()].forEach(a => {
let xIndex = posX.splice(parseInt(random(0, posX.length - 1)), 1)[0];
               // removes an element in posX variable, indicates that position is taken and to avoid overlapping
                

this.enemies.push(new GameObject(
createVector((xIndex * 75) + 37.5, 0 - 75),
// multiply the zIndex by 75 since 75 is the width of the enemy
createVector(0, this.enemySpeed),
random(this.assetManager.enemiesImg),
createVector(70, 70),
60,
'enemy'
));
})

}
}

renderEnemies() {
this.enemies.forEach(e => {
e.update();
e.show();
});

this.enemies = this.enemies.filter(e => !(e.life <= 0 || e.pos.y > height));
}


}


We have added 3 new variables  on init() method:

- enemies - to hold all the enemies that is within the game canvas

- enemyGeneration - number value that indicates for every nth frame we are creating a new set of enemies

- enemySpeed -  indicates how fast the enemy should move.


New methods:

- renderEnemies() - shows and update the enemies' position

this.enemies = this.enemies.filter(e => !(e.life <= 0 || e.pos.y > height)); -  removes the enemy whose life is 0 or is out of the canvas.

- generateEnemy() - generates set on enemies on random position and random in count, that is based on the enemyGeneration variable


We have updated also the renderBullets() function to check if a bullet has collided to an enemy, if it has collided remove the bullet and subtracts player damage to enemy that has been hit.


After running our game again we can see this output: 




Cool! now it looks like a game now.

That ends our part 2 of this article series. 

Next Article: http://onecompileman.com/blogs/6



Read More