Skip to content
No description, website, or topics provided.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
modules
.gitignore
README.md
main.js
package-lock.json
package.json
setup.sh

README.md

Easel on Diesel

To see Easel on Diesel in action, check out Space Invaders. Using only Easel on Diesel, this was created in under 300 lines!

Easel on Diesel is a framework for making 2D games using Javascript. A fair understanding of EaselJs is recommended for use. EaselJS docs can be found here.

Easel on Diesel's goal is to smooth over the game-making process by abstracting away all non-logic components of the process, as well as teach basic Object Orientation concepts and design patterns.

It does this by mimicking a popular web framework pattern: MVC (Model, View, Controller) architecture (with EaselJS handling the View portion).

Setup

PSA: The easiest way to start a project is to clone the skeleton repo!

The skeleton repo can be found here: https://github.com/Robb-Veltman/Eisel-On-Diesel-Skeleton.git. This will give you access to:

  • Easy-to-follow folder structure
  • Example Models and Controllers
  • An entry.js file with a game created and ready to run
  • index.html with a canvas object
  • webpack.config.js file

Simply:

git clone https://github.com/Robb-Veltman/Eisel-On-Diesel-Skeleton.git myGame

Then, inside your directory:

npm install

npm run build (to run webpack in watch mode)

And that's it! You're ready to make a game.

If for some reason you can't clone the skeleton repository: npm install --save easel-on-diesel Easel on Diesel requires ES6. As of the current build, these are the dependencies:

  • "webpack": "^2.1.0-beta.22",
  • "babel-core": "^6.23.1",
  • "babel-loader": "^6.3.2",
  • "babel-preset-es2015-node6": "^0.4.0",
  • "createjs-collection": "^0.8.2-1"

You can still get a skeleton

You can also get access to the scaffolding via a shell script. This is done using setup.sh, which can be accessed by:

bash ./node_modules/easel-on-diesel/setup.sh

Models

import { Model } from 'easel-on-diesel'

Models are any object which will appear on the screen. They are created via a parameters object, which requires 3 parameters, and can accept infinitely more. Example:

const PLAYER_PARAMS = {
  // Required parameters:
  spritesheet: new SpriteSheet({
    images: ['./assets/spritesheets/player.png'],
    frames: { width: 28, height: 14, count: 1 },
  }),

  container: mainContainer,
  controller: Players,
  // Additional model-specific parameters:
  lives: 5,
  moveSpeed: { left: 0, right: 0, up: 0, down: 0 },
  reloadTime: 350,
};

All non-required parameters will be accessible inside the model instance by simply calling this.parameterName. In the above example, this.lives will return 5.

Required Parameters

The following parameters are required, and will throw an error if not found:

Spritesheet

import { SpriteSheet } from 'easel-on-diesel'

An EaselJS Spritesheet object. Spritesheet docs can be found here. Example:

spritesheet: new SpriteSheet({
  images: ['./assets/spritesheets/player.png'],
  frames: { width: 28, height: 14, count: 1 },
}),

Container

import { Container } from 'easel-on-diesel'

An EaselJS Container object. Docs can be found here. All models should be given a container, and NOT added directly to the stage.

Controller

import { Controller } from 'easel-on-diesel'

All models must belong to one Controller. See below.

EventListeners

Event listeners must be added asynchronously, as they will be functions that are model-specific. As such, they cannot be put in the parameters object, but instead must be housed in an object called this.eventListeners.

They should follow a naming convention like so:

  • key: string of event listener type
  • value: callback that corresponds to the listener

Example:

this.eventListeners = {
  'keydown': (e) => this._handleKeyDown(e),
  'keyup': (e) => this._handleKeyUp(e),
};

If a model's event listeners are not housed in the eventListeners object with the proper naming convention, they will not be automatically added/deleted upon construction/removal, nor will they be deleted upon game.reset().

Extendable Methods

The following are methods which should be extended (by default, these methods are blank, so no super is needed) on a per-model basis:

updateLogic()

All logic that happens per screen tick, per the individual model goes here. Logic concerning any model-to-model interaction should be housed in the model's Controller.

Example:

updateLogic() {
  this.x += this.xSpeed;
  this.y += this.ySpeed;
  this.shoot();
}

killLogic()

All logic that should happen when a model is removed. Example:

killLogic() {
  this.explode();
  game.score += 100;
}

update()

  • Should NOT be called by the user; called automatically from the model's controller
  • Calls updateLogic(), then positions the sprite appropriate on the screen

kill()

  • Should be called by the user
  • Calls killLogic() before removing the model from its Controller and Container

Other Methods

changeAnimation(type, nameOrFrame)

type: should be a string of either 'stop' or 'play'

  • 'stop'
    • will call this.sprite.gotoAndStop()
    • should be accompanied by an integer as second argument
    • goes to a single frame and stops there
  • 'play'
    • will call this.sprite.gotoAndPlay()
    • should be accompanied by an animation name as second argument
    • plays a new CreateJS Sprite animation

Positioning Helpers

Models also come with a number of convenience methods for getting/assigning its position:

  • isOffScreen(): returns boolean stating whether any part of the model has gone off the visible canvas screen

  • left(): returns model's leftmost x value

  • right(): returns model's rightmost x value

  • top(): returns model's topmost y value

  • bottom(): returns model's bottommost y value

  • centerX(): returns model's center x value

  • centerY(): returns model's center y value

  • setLeft(x): set model's leftmost x value

  • setRight(x): set model's rightmost x value

  • setTop(y): set model's topmost y value

  • setBottom(y): set model's bottommost y value

  • setCenterX(x): set model's center x value

  • setCenterY(y): set model's center y value

  • setCenter(x, y): set model's center x and y value

Controllers

import { Controller } from 'easel-on-diesel'

Controllers determine how models interact with other models on the screen. Models should belong to one controller; upon instantiation, any new model is automatically added to its controller and its container. In general, think of Controlers as collections of models that fall into one category.

For example, in Space Invaders, there would be 5 Controlers: Players (even though there is only 1 player, he/she still needs a controller), Enemies, PlayerProjectiles, EnemyProjectiles, and Walls (the green structures that protect the player from enemy lasers).

These are fairly straightforward, because there is a 1:1 relationship between models and Controllers, but Controllers allow for more flexibility.

For example, consider a platformer game with 2 kinds of platforms: those you can blow up, and those you can't. It would be inefficient to have different controllers for every type of platform in the game. Imagine if there were 5 kinds, or 10?

To remedy this, controllers can be made using polymorphism. For the above platformer example, simply create a Landables controller. Every type of platform model could be housed in this controller. Then, in the Landables interactionLogic() (see below) method, a switch statement could be made on the type of platform to further delegate its interaction. For example, if a playerProjectile collides with a model in the Landables controller, check its type. Is it a destructible platform? Blow it up. Indestructible? Call platProjectile.kill().

Methods

interactionLogic(model)

  • model: a model housed in the controller interactionLogic() should receive a model argument.

This method already exists inside of a forEach() statement, meaning that all the user has to write is the way in which one model in this controller should interact with all other controllers.

This is easier shown through example:

Consider a simple game, where you want to check collision detection between playerProjectile models and enemy models.

import { Controller, CollisionDetection as CD } from 'easel-on-diesel';
import PlayerProjectiles from './player_projectiles';

class Enemies extends Controller {
  interactionLogic(enemy) {
    PlayerProjectiles.forEach( proj => {
      if (CD.rectRect(enemy, proj)) {
        enemy.explode();
        proj.kill();
      }
    });
  }
}

export default new Enemies(); // IMPORTANT: remember to export

The last line is important to remember. Controllers must export an INSTANCE of themselves, not the class itself. In the future, I will try to have them be auto-instantiated.

Array Methods

As Controllers function like Arrays, many array methods are callable on them, such as:

  • first(): returns the first model in its collection
  • last(): returns the last model in its collection
  • length(), count(): returns the length of its collection
  • some(callback): returns true if the callback returns true for any of the models in its collection
  • every(callback): return true if the callback returns true for every model in its collection

CollisionDetection

import { CollisionDetection } from 'easel-on-diesel'

rectRect(rect1, rect2)

Returns a boolean determining whether rect1 collides with rect2. Used for collision detection between two models, in their respective controllers.

Display

import { Display } from 'easel-on-diesel'

getDimensions()

If you use setup.sh, this method will be called automatically in your entry file. It looks for a canvas element in your document, and calculates various dimensions for ease of use later. They are as follows:

  • width
  • height
  • left
  • right
  • top
  • bottom
  • centerX
  • centerY

Util

import { Util } from 'easel-on-diesel'
  • randomInt(min, max): returns a random int between min and max, inclusive

Timer

import { Timer } from 'easel-on-diesel'

Timers should be used instead of setTimeout or setInterval whenever possible, as they are 'baked into the tick'.

Timers can be given an optional argument for a specific time threshold (in milliseconds). See isPastThreshold() below.

isPastThreshold(willReset = false, threshold)

  • willReset: boolean determining whether to reset the timer. If a timer is only concerned with one action, this will likely be true
  • threshold: If a timer was given a threshold in its constructor, then this argument is optional. Otherwise, it should be a time threshold given in milliseconds.

Examples:

const myTimer = new Timer();
if (myTimer.isPastThreshold(false, 2000)) {
  console.log('Two seconds have passed.');
};
// returns true if 2 seconds have past since instantiation; console logs once if so

const myLoopedTimer = new Timer(3000);
if (myLoopedTimer.isPastThreshold(true)) {
  console.log('Another 3 seconds have passed');
};
// returns true if 3 seconds have passed, console logs, then repeats every 3 seconds
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.