JavaScript Pixel Art:

Using Canvas Without a Library

Matt Wilber

@greenzeta

[IN DEVELOPMENT]

Canvas Portfolio

Peanut Butter Art

Hormel 2015

Smashion

VFiles 2016

Theraflu Thermoscanner

GSK 2017

English Oak Experience

JoMalone 2018

ExoBits NFT

GreenZeta 2021

ChartIQ

Cosaic 2019+

What Is Canvas?

  • Canvas is an HTML element with an accompanying JavaScript API.
     
  • Provides a means to programmatically generate static images within a web page.
     
  • Images can be expanded into animation using javascript timing functions or requestAnimationFrame.

The Canvas API

Rectangle

rect()

fillRect()

strokeRect()

clearRect()

 

Text

fillText()

strokeText()

measureText()

 

Image

drawImage()

Path

fill()

stroke()

beginPath()

moveTo()

closePath()

lineTo()

clip()

quadraticCurveTo()

bezierCurveTo()

arc()

arcTo()

isPointInPath()

Other

save()

restore()

createEvent()  

getContext()  

toDataURL()

Pixel Control
createImageData()
getImageData()
putImageData()

 

Transformation
scale()
rotate()
translate()
transform()
setTransform()

Most developers use canvas via a library.

  • Generating artwork through code can be cumbersome. There are many canvas libraries available that aim to simplify the process.
     
  • Some provide a layer of abstraction over the canvas, offering additional features like object and layer control (EaselJS, p5.js).
     
  • Others offer functionality specific to a type of project like game development (Phaser). 

Sometimes, a library is overkill.

  • Simple projects can be accomplished with the same amount of code.
  • It’s one more dependency to worry about.
  • Adds extraneous functionality that you may not need.
  • Increases the file size of your project.

Use Cases for Vanilla Canvas

  • Static Image
    Illustrator can export as canvas calls.

     
  • Procedural Art
    Building custom functionality unique to your project.

     
  • Image Manipulation
    Edit pixel colors in real-time.

     
  • Minimal Code
    Banner Ad or NFT, where file size costs money.

Let's Make Some Pixel Art!

Project available on GitHub

github.com/mwilber/canvas-pixel-art-example

 

Project Setup

github.com/mwilber/the-legend-of-zeta  >  Step1

Much of the project is built using the techniques discussed in the Zeta Bros. demo from Part 1. This demo focuses on the use of PlugIns and Tile Maps in building a game.

 

Start with the game scene and player character set up with a tracking camera, animations and input controls.

Global Plugins

import { GzRpgCharacterPlugin } from './plugins/GzRpgCharacter';

const gameConfig = {

  	...
  
	plugins: {
 		global: [
			{ key: 'GzRpgCharacterPlugin', plugin: GzRpgCharacterPlugin, start: true }
		]
	},

  	...
  
};

src/main.js

github.com/mwilber/the-legend-of-zeta  >  Step1

this.player = this.add.rpgcharacter({
	x: 400,
	y: 300,
	name: 'zeta',
	image: 'zeta',
	speed: 225
});

src/scenes/GameScene.js

Add Tilemap Layers

...
    
preload() {
	this.load.image('tiles', 'assets/tilemaps/Area-51.png');
	this.load.tilemapTiledJSON('map', 'assets/tilemaps/area-51.json');;
}

create(settings) {
 		
	...
        
	// Load map json from Tiled
	const map = this.make.tilemap({ key: 'map' });
	// settings.tiledKey is the name of the tileset in Tiled
	const tileset = map.addTilesetImage('Area-51', 'tiles');
	// layer key is the layer name set in Tiled
	const backgroundLayer = map.createStaticLayer('Background', tileset, 0, 0);
	const interactiveLayer = map.createStaticLayer('Interactive', tileset, 0, 0);
	const overheadLayer = map.createStaticLayer('Overhead', tileset, 0, 0);

	// Place the player above the tile layers
	this.player.setDepth(10);
	// Place the overhead layer above everything else
	overheadLayer.setDepth(20);

	...

}

src/scenes/GameScene.js

github.com/mwilber/the-legend-of-zeta  >  Step2

Tile Layer Interaction

...

create(settings) {

	...

	// Identify the collision property set in the interactive layer in Tiled
	interactiveLayer.setCollisionByProperty({ collide: true });
	// Set up collision detection between the player and interactive layer
	this.physics.add.collider(this.player, interactiveLayer);
  
  	...

src/scenes/GameScene.js

github.com/mwilber/the-legend-of-zeta  >  Step3

Scene PlugIn

github.com/mwilber/the-legend-of-zeta  >  Step4

import { GzDialog } from './plugins/GzDialog';

const gameConfig = {
	
  	...
  
	plugins: {
		
      	...
      
		scene: [
			{ key: 'gzDialog', plugin: GzDialog, mapping: 'gzDialog' }
		]
	},
	
  	...
  
};

src/main.js

this.gzDialog.setText('This is a test. Hello World!');

src/scenes/GameScene.js

Add Object Layer

export class GameScene extends Phaser.Scene {
	
  	...

	preload() {
		this.load.json('scriptdata', 'assets/data/script.json');
		
      	...
	}

	create(settings) {

		...

		// Extract objects from the object layer
		const objectLayer = map.getObjectLayer('Script');
		// Convert object layer objects to Phaser game objects
		if(objectLayer && objectLayer.objects){
			objectLayer.objects.forEach(
				(object) => {
					let tmp = this.add.rectangle((object.x+(object.width/2)), (object.y+(object.height/2)), object.width, object.height);
					tmp.properties = object.properties.reduce(
						(obj, item) => Object.assign(obj, { [item.name]: item.value }), {}
					);
					this.physics.world.enable(tmp, 1);
					this.physics.add.collider(this.player, tmp, this.HitScript, null, this);
				}
			);
		}

		...

		// Get script data preloaded from script.json
		this.script = this.cache.json.get('scriptdata');

	}

	update(time, delta) {

		// Close the dialog on spacebar press
		if( this.gzDialog.visible ){
			if( this.cursors.space.isDown ){
				this.gzDialog.display(false);
			}
			return false;
		}
		
      	...
	}


	HitScript(player, target){
		if(target.properties.name && !this.gzDialog.visible){
			player.anims.stopOnRepeat();
			this.gzDialog.setText(this.script[player.name][target.properties.name]);
		}
	}

}

src/scenes/GameScene.js

github.com/mwilber/the-legend-of-zeta  >  Step5

Follow @greenzeta on Twitter

greenzeta.com

JavaScript Pixel Art: Using Canvas Without a Library

By Matthew Wilber

JavaScript Pixel Art: Using Canvas Without a Library

TBA Presentation for the Fredericksburg Developer Group.

  • 602