<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Pixi.JS Game</title>
<!-- LOAD PIXI.JS & COLLISION DETECTION & jQuery SUPPORT -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.6/pixi.min.js"></script>
<script src="https://cdn.rawgit.com/kittykatattack/bump/a06e59da/bin/bump.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- LOAD ANY CUSTOM CSS RULES THAT MIGHT BE IN THE style.css FILE -->
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="stage">
This will contain the PIXI.js stage, you shouldn't see this message when
running the code a.k.a. Plunker Live Preview
</div>
<!-- LINK THE JavaScript FILE, DON'T WRITE ANY JavaSript HERE!! -->
<!-- Generic helper functions -->
<script type="text/javascript" src="scripts/helpers.js"></script>
<!-- Pixi.JS general and stage setup code - if other setup is required at the start, link the code's file here -->
<script type="text/javascript" src="scripts/setup/pixi.js"></script>
<script type="text/javascript" src="scripts/setup/stage.js"></script>
<!-- Game helper functions -->
<script type="text/javascript" src="scripts/game/helpers.js"></script>
<!-- Custom Classes representing game objects - link other class files here -->
<script type="text/javascript" src="scripts/game/classes/star.js"></script>
<!-- Game Loop / Script -->
<script type="text/javascript" src="scripts/game/loop.js"></script>
<!-- Game Setup - creates objects and starts the Game Loop / Script (above) -->
<script type="text/javascript" src="scripts/game/setup-objects-loop.js"></script>
<!-- Pixi Remote Texture Loader - once all textures have loaded, it runs the Game Setup (above) -->
<script type="text/javascript" src="scripts/game/_loader.js"></script>
</body>
</html>
/* Styles go here */
* {
padding: 0;
margin: 0;
}
#stage {
position: absolute;
display: block;
height: 100%;
width: 100%;
}
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
// put any non-Pixi related functions here
/**
* Normalise the requestAnimationFrame() function to account for
* different browsers having different implementations of it
* https://gist.github.com/addyosmani/5434533
*/
window.requestAnimationFrame = function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(f) {
window.setTimeout(f, 1e3 / 60);
}
}();
/**
* isNumeric function in Javascript
* https://gist.github.com/pinalbhatt/9672790
*/
function IsNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
/**
* RANDOM NUMBER GENERATOR WITH LIMITS
*/
// taken from: http://stackoverflow.com/a/7228322
// returns a random integer between min and max
function RandomNumberBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
// use the WebGL Browser rendering technology by default as it's faster than HTML5 Canvas
var type = "WebGL";
// if it's not supported, then
if (!PIXI.utils.isWebGLSupported()) {
// use the HTML5 Canvas Browser rendering technology
type = "canvas";
}
// test PIXI, should output a test message in the Console
PIXI.utils.sayHello(type);
// Aliases to be used from this point forward, simplifies the code
// use http://pixijs.download/release/docs/index.html for more info on each of these
var
Container = PIXI.Container, // keeps track of objects; also used as main stage
ParticleContainer = PIXI.particles.ParticleContainer, // a highly optimised container that is capable of efficiently rendering fast moving sprites
autoDetectRenderer = PIXI.autoDetectRenderer, // Pixi rendering engine
loader = PIXI.loader, // loads external resources
resources = PIXI.loader.resources, // loaded reasources/sprites repository
Sprite = PIXI.Sprite, // a normal Pixi object
Graphics = PIXI.Graphics, // a programmatically drawn object, used as a Sprite
Texture = PIXI.Texture, // resources are typically rendered as Textures, then assigned to Sprites
Text = PIXI.Text, // a programmatically drawn text object
collisionDetection = new Bump() // collision detection tool
;
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
// prepare variables to be used for the Pixi stage and renderer
var stage = new Container(); // the main Pixi stage container
var objects = {}; // a series of key-value pairs representing the game objects or object arrays
var containerElement = $("#stage"); // jQuery object, grab a handle on the <div id="stage"></div> from index.html
var containerWidth;
var containerHeight; // actual container height/width, determined later
var stageWidth, stageHeight; // stage height/width, determined later
var renderer = autoDetectRenderer(0, 0); // PIXI graphical renderer, don't care about size here
var stageBox; // a JSON object with stage boundaries, mainly used when checking stage wall collisions
// attach the drawing board to the View
containerElement.html(renderer.view);
// code to be run when stage dimensions need to be (re)set
var ResizeStage = function() {
// get the actual height and width of the HTML container from index.html
containerWidth = containerElement.innerWidth();
containerHeight = containerElement.innerHeight();
// set the stage final height and width - this can be artificially limited by changing these values
stageWidth = containerWidth;
stageHeight = containerHeight;
// set the stage boundaries
stageBox = {
x: 0, // top left corner X position
y: 0, // top left corner Y position
height: stageHeight, // number of pixels
width: stageWidth, // number of pixels
halfHeight: stageHeight / 2, // number of pixels - convenience value
halfWidth: stageWidth / 2 // number of pixels - convenience value
};
// make sure the drawing board has the size we want, width first, then height
renderer.resize(stageWidth, stageHeight);
};
// if window dimensions change, resize the stage accordingly
$(window).on('resize', ResizeStage);
// make sure the stage has the right size straight away
ResizeStage();
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
/**
* Star Class - "options" object to be given when creating a new instance
* e.g.:
*
* // create a new object of type Star and save it in the "objects" container
* objects.star = new Star(
* {
* dimensions: {
* height: 100, // pixels
* width: 100 // pixels
* },
* position: { // starting position
* x: stageHeight / 2,
* y: stageWidth / 2
* },
* speed: 5,
* speed: {
* x: 5, // how many pixels at a time will this move on the x axis
* y: 5 // how many pixels at a time will this move on the y axis
* }
* }
* );
*
*/
var Star = function(options) {
// current instance
var $this = this;
// Pixi.JS object
var obj;
// base Pixi.JS object config
var config;
// default config values if any of them are missing from the options
var baseConfig = {
dimensions: {
height: 100, // pixels
width: 100 // pixels
},
position: { // starting position
x: stageHeight / 2,
y: stageWidth / 2
},
speed: {
x: 5, // how many pixels at a time will this move on the x axis
y: 5 // how many pixels at a time will this move on the y axis
},
};
// constructor
var Init = function() {
// if the options object isn't there i.e. not given, then set it to a blank object
if (!options) options = {};
// allow the baseConfig values to be overridden by the options values, if any
// Object.assign() will perform a shallow copy, with the right-most object's properties/keys overwriting the left-most object's properties
config = Object.assign({}, baseConfig, options);
// enable the following line to debug
// console.log("default 'baseConfig' config: ", baseConfig, "options for current instance: ", options, "final config for current instance, based on the default 'baseConfig' and 'options' objects: ", config);
// check speed option - if number given instead of {x, y} JSON, then use that
if (IsNumeric(config.speed)) {
config.speed = {
x: config.speed,
y: config.speed
};
}
// prepare the Pixi object and add it to the stage
PreparePixiObject();
};
// build the object
var PreparePixiObject = function() {
// create the object using the "star" preloaded texture as its Sprite
obj = new Sprite(resources["chest"].texture);
// set the height
obj.height = config.dimensions.height;
// set the width
obj.width = config.dimensions.width;
// set its initial horizontal position
obj.x = config.position.x;
// set its initial vertical position
obj.y = config.position.y;
// set the horizontal movement speed/velocity
obj.vx = config.speed.x;
// set the vertical movement speed/velocity
obj.vy = config.speed.y;
/**
* Set the texture's anchor point relative to the object's dimensions - both values are between 0.0 and 1.0 (i.e. 0 to 100%)
* First value refers to the horizontal anchor point, the second refers to the vertical anchor point
* (0.5, 0.5) -> anchor point will be in the centre of the Sprite
* By default, the anchor point is (0, 0) -> top-left of the object's corner
*/
obj.anchor.set(0.5, 0.5);
// set some helper values on this object for later use
obj.halfHeight = obj.height / 2;
obj.halfWidth = obj.width / 2;
// denote it as a circular shape - for collision detection purposes
obj.circular = true;
// add it to the stage
stage.addChild(obj);
};
// move object horizontally according to its speed/velocity
$this.MoveX = function() {
obj.x += obj.vx;
};
// move object vertically according to its speed/velocity
$this.MoveY = function() {
obj.y += obj.vy;
};
// reposition object to new x,y coordinates
$this.MoveTo = function(x, y) {
obj.x = x;
obj.y = y;
};
// change the object horizontal movement direction
$this.InvertXDirection = function() {
obj.vx *= -1;
};
// change the object vertical movement direction
$this.InvertYDirection = function() {
obj.vy *= -1;
};
// returns TRUE if object has moved off stage left, FALSE otherwise
$this.IsOffStageXLeft = function() {
return ((obj.x - obj.halfWidth) <= 0);
};
// returns TRUE if object has moved off stage right, FALSE otherwise
$this.IsOffStageXRight = function() {
return ((obj.x + obj.halfWidth) >= stageWidth);
};
// returns TRUE if object has moved off stage top, FALSE otherwise
$this.IsOffStageYTop = function() {
return (obj.y - obj.halfHeight <= 0);
};
// returns TRUE if object has moved off stage bottom, FALSE otherwise
$this.IsOffStageYBottom = function() {
return ((obj.y + obj.halfHeight) >= stageHeight);
};
$this.Animate = function() {
// check if object is off stage left
if ($this.IsOffStageXLeft()) {
// if so, then reposition it to just inside the left stage boundary, taking into account its half-width (circular object)
$this.MoveTo(obj.halfWidth, obj.y);
// reverse its horizontal direction
$this.InvertXDirection();
}
// check if object is off stage right
if ($this.IsOffStageXRight()) {
// if so, then reposition it to just inside the right stage boundary, taking into account its half-width (circular object)
$this.MoveTo(stageWidth - obj.halfWidth, obj.y);
// reverse its horizontal direction
$this.InvertXDirection();
}
// check if object is off stage top
if ($this.IsOffStageYTop()) {
// if so, then reposition it to just inside the top stage boundary, taking into account its half-height (circular object)
$this.MoveTo(obj.x, obj.halfHeight);
// reverse its vertical direction
$this.InvertYDirection();
}
// check if object is off stage bottom
if ($this.IsOffStageYBottom()) {
// if so, then reposition it to just inside the bottom stage boundary, taking into account its half-height (circular object)
$this.MoveTo(obj.x, stageHeight - obj.halfHeight);
// reverse its vertical direction
$this.InvertYDirection();
}
// move the object horizontally
$this.MoveX();
// move the object vertically
$this.MoveY();
};
// call the instance constructor
Init();
};
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
/**
* Runs at 60fps
* Every frame it calculates new positions/collisions/actions
* Then it redraws the stage to reflect any changes
*/
var GameLoop = function() {
// Loop this function at a default rate of 60 frames per second
requestAnimationFrame(GameLoop);
// go through all existing game objects
for (var key in objects) {
// get a handle on each object found in the objects JSON structure
var object = objects[key];
// all objects should have an Animate() public method
// perform animation for current object
if (object.Animate && typeof object.Animate == "function") {
object.Animate();
}
}
// render the stage a.k.a. (re)construct the stage graphically
renderer.render(stage);
}
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
/**
* Creates objects and starts the game loop
*/
var Setup = function() {
// build a new star
objects.star = new Star({
dimensions: {
height: 50, // pixels
width: 50 // pixels
},
position: { // starting position
x: stageHeight / 2,
y: stageWidth / 2
},
speed: 5, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
});
// build a new star
objects.star2 = new Star({
dimensions: {
height: 20, // pixels
width: 10 // pixels
},
position: { // starting position
x: stageHeight / 3,
y: stageWidth / 2
},
speed: 15 // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
});
// build a new star
objects.star3 = new Star({
dimensions: {
height: 60, // pixels
width: 60 // pixels
},
position: { // starting position
x: stageHeight / 2,
y: stageWidth / 2
},
speed: 2 // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
});
// start the game loop - defined above
GameLoop();
};
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
/**
* Load an image and run the `Setup()` function when it's done.
* Textures loaded this way will be available to use as such:
*
* // replace "star" with whatever `name` the required texture was given
* var obj = new Sprite(resources["star"].texture);
*/
// Pixi resource loader
loader
// use the following code as a template for loading more remote/external textures
// .add({ // mind the dot at the start of this line
// name: "star", // used to uniquely identify this texture in the "resources" repository
// url: "http://i.imgur.com/A4jyRT0.png", // remote location
// crossOrigin: true // needs this to be able to load images remotely
// })
.add({ // mind the dot at the start of this line
name: "star", // used to uniquely identify this texture in the "resources" repository
url: "http://i.imgur.com/A4jyRT0.png", // remote location
crossOrigin: true // needs this to be able to load images remotely
})
.add({
name: "chest", // reference
url: "https://i.imgur.com/FKjaY7a.png", // remote location
crossOrigin: true // needs this to be able to load images remotely
})
// run the Setup() function when all textures have finished loading
.load(Setup);
// Raz: I'd recommend using semicolons just like you would in C#
// Raz: I'd also recommend you open up the Chrome Developer tools to check for JavaScript & PIXI errors
/**
* Write any functions that help with running/animating/collisions within the game.
* Generally, anything that doesn't quite make sense to add to a particular class.
* e.g.
* a function to check collision between multiple/different sets of objects and
* trigger public methods (actions/behaviour) on them upon collision.
*/
/**
* Cleanup helper capable of destroying and removing from the stage a group of sprites (BRICKS, POWERUPS, ...) if they've been hit and need to disappear.
*
* Parameters:
* groupsOfSpritesToCheck (JSON object):
* {
* sprites: array of Pixi sprite objects to check
* ifEmpty: function to run if all sprites have been removed
* }
*
* Parameter example:
* groupsOfSpritesToCheck = {
* sprites: [], // array of sprites
* ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the sprites array
* }
*
* Usage example:
* CleanupSprites(
* {
* sprites: arrayContainingPixiSprites,
* ifEmpty: function () {
* // code to run if all sprites have been hit and are now gone from the stage
* }
* }
* );
*
* Notes:
* Each Sprite from the sprites array should have a property called "hasBeenHit" (boolean).
* This property needs to be set prior to this function call somewhere where collision detection happens for this particular group of sprites.
*/
var CleanupSprites = function(groupsOfSpritesToCheck) {
// don't change this unless you know exactly why you are doing it
var
// will be used below to detect when objects have been removed from a group
removed,
// will be used below to store a reference to the sprites & their group (defined above) that are currently being checked
group, sprites,
// will be used below to store a reference to the object that is currently being checked
sprite;
for (var groupIndex = 0; groupIndex < groupsOfSpritesToCheck.length; groupIndex++) {
group = groupsOfSpritesToCheck[groupIndex];
sprites = group.sprites;
for (var spriteIndex = sprites.length - 1; spriteIndex >= 0; spriteIndex--) {
// grab a reference to the object
sprite = sprites[spriteIndex];
// object hit!
if (sprite.hasBeenHit) {
// destroy the object
sprite.destroy();
// remove the object from the group
sprites.splice(spriteIndex, 1);
// trigger another while loop
removed = true;
// stop the current for loop
break;
}
}
// if there aren't any other objects left and there is some behaviour for this
if (!sprites.length && group.ifEmpty && typeof group.ifEmpty === "function") {
// run the empty behaviour defined above
group.ifEmpty();
}
}
}
/**
* Keyboard Class
* Based on https://github.com/kittykatattack/learningPixi#keyboard
* Monitors whether a given keyboard key (ASCII) has been pressed or released
*
* Required Parameters:
* keyCode - ASCII number corresponding to a key on the user's keyboard
*
* Optional Parameters:
* press - a function() {} to execute when the key is pressed
* release - a function() {} to execute when the key is released
*
* Returns:
* {
* isDown: boolean,
* isUp: boolean
* }
*
* Notes:
* keyboard.isDown and keyboard.isUp can be used to check the state of a key during a game frame/loop
*/
var Keyboard = function(keyCode, press, release) {
// don't change this unless you know exactly why you are doing it
var key = {};
key.code = keyCode;
key.isDown = false;
key.isUp = true;
key.press = press;
key.release = release;
//The `downHandler`
key.downHandler = function(event) {
// console.log(event.keyCode); // enable this to test out key codes
if (event.keyCode === key.code) {
if (key.isUp && key.press && typeof key.press == "function") key.press();
key.isDown = true;
key.isUp = false;
}
event.preventDefault();
};
//The `upHandler`
key.upHandler = function(event) {
if (event.keyCode === key.code) {
if (key.isDown && key.release && typeof key.release == "function") key.release();
key.isDown = false;
key.isUp = true;
}
event.preventDefault();
};
//Attach event listeners
window.addEventListener(
"keydown", key.downHandler.bind(key), false
);
window.addEventListener(
"keyup", key.upHandler.bind(key), false
);
return key;
}
/**
* Touch Class
* Offers touch/mouse support.
*
* Parameters:
* part - one of the following: "topLeft", "topRight", "bottomLeft", "bottomRight", "anywhere"
*
* Returns:
* {
* isDown: boolean
* isUp: boolean
* position: { // user's touch position within the stage
* x: int
* y: int
* }
* }
*/
var Touch = function(part) {
// don't change this unless you know exactly why you are doing it
/**
* 0 - x,y point indicating the top left corner of each area
*
* halfWidth halfWidth
* 0 ------------ 0 --------------
* | | |
* halfHeight | topLeft | topRight | halfHeight
* | | |
* 0 ------------ 0 --------------|
* | | |
* halfHeight | bottomLeft | bottomRight | halfHeight
* | | |
* ------------------------------
* halfWidth halfWidth
*/
// split the stage into 4 areas
// these will be used by the Bump library to determine collision with the point
var stageParts = {
"topLeft": {
x: 0,
y: 0, // area top left position
width: stageBox.halfWidth,
height: stageBox.halfHeight // area height and width
},
"topRight": {
x: stageBox.halfWidth,
y: 0, // area top left position
width: stageBox.halfWidth,
height: stageBox.halfHeight // area height and width
},
"bottomLeft": {
x: 0,
y: stageBox.halfHeight, // area top left position
width: stageBox.halfWidth,
height: stageBox.halfHeight // area height and width
},
"bottomRight": {
x: stageBox.halfWidth,
y: stageBox.halfHeight, // area top left position
width: stageBox.halfWidth,
height: stageBox.halfHeight // area height and width
},
"anywhere": {
x: 0,
y: 0, // area top left position
width: stageBox.halfWidth * 2,
height: stageBox.halfHeight * 2 // area height and width
}
};
// if the given part is not defined above, throw an error in the console
if (!stageParts[part]) {
throw new Error("Stage part ('" + part + "') not recognised!");
}
// object that will be returned from this function, similarly to how the keyboard support works
var touchObject = {
part: part,
isDown: false,
isUp: false
};
// See here for more information on jQuery.on() : http://api.jquery.com/on/
containerElement.on("touchstart mousedown pointerdown", function(event) {
// get the point coordinates of the user's touch
var point = getEventPos(this, event);
// if the user touched within the configured part, then set it accordingly
if (collisionDetection.hitTestPoint(point, stageParts[part])) {
// mark the current touch object as being pressed / down
touchObject.isDown = true;
touchObject.isUp = false;
// point: { x: number, y: number }
touchObject.position = point;
}
});
// See here for more information on jQuery.on() : http://api.jquery.com/on/
containerElement.on("touchend mouseup pointerup", function(event) {
// get the point coordinates of the user's touch
var point = getEventPos(this, event);
// if the user touched within the configured part, then set it accordingly
if (collisionDetection.hitTestPoint(point, stageParts[part])) {
// mark the current touch object as NOT being pressed / down
touchObject.isUp = true;
touchObject.isDown = false;
}
});
return touchObject;
}
/**
* Supporting function. Returns a JSON object containing { x: number, y: number } coordinates of a JavaScript event.
* Based on code found at: https://bencentra.com/code/2014/12/05/html5-canvas-touch-events.html
*/
function getEventPos(canvasEl, event) {
// get the canvas element bounding coordinates
var rect = canvasEl.getBoundingClientRect();
// return an { x, y } object with the user's event coordinates
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}