easel-on-diesel v1.3.7
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
Model
s andController
s - An
entry.js
file with a game created and ready to run index.html
with acanvas
objectwebpack.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 typevalue
: 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 itsController
andContainer
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
- will call
'play'
- will call
this.sprite.gotoAndPlay()
- should be accompanied by an animation name as second argument
- plays a new CreateJS Sprite animation
- will call
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 valueright()
: returns model's rightmost x valuetop()
: returns model's topmost y valuebottom()
: returns model's bottommost y valuecenterX()
: returns model's center x valuecenterY()
: returns model's center y value
setLeft(x)
: set model's leftmost x valuesetRight(x)
: set model's rightmost x valuesetTop(y)
: set model's topmost y valuesetBottom(y)
: set model's bottommost y valuesetCenterX(x)
: set model's center x valuesetCenterY(y)
: set model's center y valuesetCenter(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 controllerinteractionLogic()
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 collectionlast()
: returns the last model in its collectionlength()
,count()
: returns the length of its collectionsome(callback)
: returns true if the callback returns true for any of the models in its collectionevery(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 truethreshold
: 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
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago