How to Integrate P5JS into Electron JS App

I had this wild idea lately. I want to create a simple desktop application. In it, I want to have simple RPG sprite walking in the client area of the application. Since I picked up Electron.js recently for desktop application development, and I always wanted to learn processing.js, I decided to combine the two in a sample application. I have done anything with processing.js for a long while. Once I started research, I realize processing.js is replaced by p5.js. I had no idea how to integrate p5.js into Electron.js application. The whole project is a fun exercise for me, setup the project, write the code, compile and test it out.

First start with creating the Electron.js project. First run this command:

npm init

This will download node_modules and create a very basic node application. The next step will add the electron.js components into node_modules, and add development dependency for electron.js:

npm install electron --save-dev

To add p5.js into the project (integrating the p5.js), I use the same command but different package:

npm install p5 --save-dev

I change the package.json by specifying the entry JavaScript file, and the test run script:

{
  "name": "rpg_sprite1",
  "version": "1.0.0",
  "description": "A simple electron.js app that displays a mvoing sprite.",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "electron",
    "processing.js",
    "rpg",
    "sprite"
  ],
  "author": "Han Bo Sun",
  "license": "Apache-2.0",
  "devDependencies": {
    "@electron-forge/cli": "^7.4.0",
    "electron": "^31.2.1",
    "p5": "^1.9.4"
  }
}

The ones highlighted in bold are the changes I have added in the package.json file. The changes will allow me to compile and run the application.

There is a main entry for every Electron JS application. It defines how the application starts up, how it interacts with the desktop, and how it loads application resources at the start up. I named this main entry "main.js". It looks like the following:

const { app, BrowserWindow } = require('electron');

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  win.loadFile('index.html')
};

app.whenReady().then(() => {
  createWindow();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

The index page that the application will display is as the following:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World! - A P5 JS Demo App</title>
  </head>
  <body>
    <script type="text/javascript" src="./node_modules/p5/lib/p5.js"></script>
    <script type="text/javascript" src="./sketch2.js"></script>
  </body>
</html>

This HTML page does not have anything for content. It imports two JacaScript files. One is the p5.js file. The other is a script I wrote for the RPG sprite displaying.

This is the whole script for the RPG sprite, to display, to move with his arms and legs, and to walk by pressing the arrow keys:

let img = null;
let char1Images = [];
let imgSizeX = 31, imgSizeY = 34;
let idx = 0;
let isCharMoving = false;
let charMovingDirection = "";
let movingStep = 24;
let charStartX = 200;
let charStartY = 180;

function resetCharMovement() {
    isCharMoving = false;
    charMovingDirection = "";
    movingStep = 24;
}


function preload() {
    img = loadImage('./images/dda1c04-c48f94a2-8213-416a-a985-1b06e723a874.png');
}

function setup() {
    createCanvas(764, 550);
    let startX = 0;
    let startY = 127;
    for (let i = 0; i < 3; i++) {
        let cutImg = img.get(startX, startY, imgSizeX, imgSizeY);
        let spriteObj = {
            img: cutImg,
            timeCounter: 8,
            update: function () {
                this.timeCounter -=1;
                if (this.timeCounter < 0) {
                    this.timeCounter = 8;
                }

                if (isCharMoving) {
                    if (charMovingDirection == "left") {
                        charStartX -= 3;
                        movingStep -= 1;
                    } else if (charMovingDirection == "right") {
                        charStartX += 3;
                        movingStep -= 1;
                    } else if (charMovingDirection == "up") {
                        charStartY -= 3;
                        movingStep -= 1;
                    } else if (charMovingDirection == "down") {
                        charStartY += 3;
                        movingStep -= 1;
                    }

                    if (movingStep <= 0) {
                        resetCharMovement();
                    }
                }
            },
            isTimeToChange: function() {
                if (this.timeCounter == 0) {
                    this.timeCounter = 8;
                    return true;
                }

                return false;
            }
        };

        char1Images.push(spriteObj);
        startX = startX + imgSizeX;
    }
}

function draw() {
    background(200);
    if (char1Images[idx] != null) {
        if (char1Images[idx].timeCounter > 0) {
            image (char1Images[idx].img, charStartX, charStartY, imgSizeX * 3, imgSizeY * 3);
            char1Images[idx].update();

            if (char1Images[idx].isTimeToChange()) {
                idx += 1;
                if (idx >= 3) {
                    idx = 0;
                }
            }
        }
    }
}


function keyPressed()
{
    if (isCharMoving) {
        return;
    } else {
        if (key == "ArrowLeft") {
            isCharMoving = true;
            charMovingDirection = "left";
        } else if (key == "ArrowRight") {
            isCharMoving = true;
            charMovingDirection = "right";
        } else if (key == "ArrowUp") {
            isCharMoving = true;
            charMovingDirection = "up";
        } else if (key == "ArrowDown") {
            isCharMoving = true;
            charMovingDirection = "down";
        }
    }
}

To run this sample application, use the following command in the Terminal window:

npm start

Just a few thing about P5JS. Once I added the P5JS to the application, I need to import P5JS to the index.html file. Then I need to create a JavaScript file that renders the screen display. This is the Sketch2.js. The way P5JS works is that it assumes that there were a few functions that it will call to load resources, setup the resources, and render the display. The first function I create is preload(). This function loads the PNG image file:

function preload() {
    img = loadImage('./images/dda1c04-c48f94a2-8213-416a-a985-1b06e723a874.png');
}

I tried to slice the image into different pieces in preload(), but it didn't work. So I have to move the code to the next function called setup().

function setup() {
    createCanvas(764, 550);
    let startX = 0;
    let startY = 127;
    for (let i = 0; i < 3; i++) {
        let cutImg = img.get(startX, startY, imgSizeX, imgSizeY);
        let spriteObj = {
            img: cutImg,
            timeCounter: 8,
            update: function () {
                this.timeCounter -=1;
                if (this.timeCounter < 0) {
                    this.timeCounter = 8;
                }

                if (isCharMoving) {
                    if (charMovingDirection == "left") {
                        charStartX -= 3;
                        movingStep -= 1;
                    } else if (charMovingDirection == "right") {
                        charStartX += 3;
                        movingStep -= 1;
                    } else if (charMovingDirection == "up") {
                        charStartY -= 3;
                        movingStep -= 1;
                    } else if (charMovingDirection == "down") {
                        charStartY += 3;
                        movingStep -= 1;
                    }

                    if (movingStep <= 0) {
                        resetCharMovement();
                    }
                }
            },
            isTimeToChange: function() {
                if (this.timeCounter == 0) {
                    this.timeCounter = 8;
                    return true;
                }

                return false;
            }
        };

        char1Images.push(spriteObj);
        startX = startX + imgSizeX;
    }
}

In the function preload(), I get an object called img, that contains the PNG image file data. The object has method called get() which can be used to cut the image from the upper-left corner and the dimension of the piece I want. In this function, I also do a few other things setting up the character's movement.

The next function is a bit interesting, it is called draw(), it runs about 24 time a second in an infinite loop of which you won't see. It is part of the P5JS internal coding, basically refreshes the screens. When the application runs, you will see the character waving his arms and moving his legs. This animation is possible because of this draw().

function draw() {
    background(200);
    if (char1Images[idx] != null) {
        if (char1Images[idx].timeCounter > 0) {
            image (char1Images[idx].img, charStartX, charStartY, imgSizeX * 3, imgSizeY * 3);
            char1Images[idx].update();

            if (char1Images[idx].isTimeToChange()) {
                idx += 1;
                if (idx >= 3) {
                    idx = 0;
                }
            }
        }
    }
}

To interact with the application so that the character appears moving up, down, left or right. This is done with the function called keyPressed():

function keyPressed()
{
    if (isCharMoving) {
        return;
    } else {
        if (key == "ArrowLeft") {
            isCharMoving = true;
            charMovingDirection = "left";
        } else if (key == "ArrowRight") {
            isCharMoving = true;
            charMovingDirection = "right";
        } else if (key == "ArrowUp") {
            isCharMoving = true;
            charMovingDirection = "up";
        } else if (key == "ArrowDown") {
            isCharMoving = true;
            charMovingDirection = "down";
        }
    }
}

This application is done at this state. I need to clean it up and make it more object-oriented. And it looks pretty nice right now. I think I will do a new series, adding background tiles, scroll-able, and enable conversations with other characters. This is great.

Your Comment


Required
Required
Required

All Related Comments

Loading, please wait...
{{cmntForm.errorMsg}}
{{cmnt.guestName}} commented on {{cmnt.createDate}}.

There is no comments to this post/article.