<!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/globals.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>
<!-- Collision detection trigger code -->
<script type="text/javascript" src="scripts/game/collision.js"></script>
<!-- Custom Classes representing game objects - link other class files here -->
<script type="text/javascript" src="scripts/game/classes/score.js"></script>
<script type="text/javascript" src="scripts/game/classes/brick.js"></script>
<script type="text/javascript" src="scripts/game/classes/character.js"></script>
<!-- Class Factories - can create specific game objects - link other factories files here -->
<script type="text/javascript" src="scripts/game/factories/score.js"></script>
<script type="text/javascript" src="scripts/game/factories/brick.js"></script>
<script type="text/javascript" src="scripts/game/factories/character.js"></script>
<!-- Game Levels -->
<script type="text/javascript" src="scripts/game/levels/start.js"></script>
<script type="text/javascript" src="scripts/game/levels/end.js"></script>
<!-- Game Background -->
<script type="text/javascript" src="scripts/game/background.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.js"></script>
<!-- Game Interactions - Touch/Keyboard -->
<script type="text/javascript" src="scripts/game/interactions.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);
}
/**
* localStorage utility functions
*/
var LocalStorage = {
set: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function (key) {
return JSON.parse(localStorage.getItem(key));
},
remove: function (key) {
localStorage.removeItem(key);
},
clear: function () {
localStorage.clear();
}
};
// 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
TextureCache = PIXI.utils.TextureCache, // loaded textures repository
Text = PIXI.Text, // a programmatically drawn text object
collisionDetection = new Bump(PIXI) // 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 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
};
// check if we have a background and update its size accordingly
if (objects.background) {
objects.background.height = stageHeight;
objects.background.width = stageWidth;
}
// 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
/**
* Runs at 60fps
* Every frame it calculates new positions/collisions/actions
* Then it redraws the stage to reflect any changes
* This is effectively the game script
*/
var GameLoop = function() {
/**
* Animate/Move objects
* "objects" is defined in "scripts/setup/globals.js"
* Use this kind of approach for a group of moving objects that all need to be constantly moving
*/
// run the main character animation - it ensures the sprite's containment within the stage
objects.mainChar.Animate();
/**
* Check all relevant collisions. See "scripts/game/collision.js"
*/
CheckCollisions();
/**
* Specifically check if any of our bricks need to be removed
* i.e. have had `hasBeenHit` set to `true` on their `sprite` Sprite object
* For bricks, this can happen in the `Brick` class, in the `$this.DoCollision` method
*/
CleanupSprites({
objects: objects.bricks,
ifEmpty: function() { // if empty, run the game over script
// end the game loop
gameOver = true;
// save the current score to local storage
objects.points.Save();
// hide the current level
levels.start.stage.visible = false;
// show the end game scene - see scripts/game/levels/end.js
GameEndSetup();
}
});
/**
* Render the stage a.k.a. (re)construct the stage graphically
*/
renderer.render(stage);
/**
* Loop this function at a default rate of 60 frames per second
* Only do this if the game is not Over
*/
if (!gameOver) {
requestAnimationFrame(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
/**
* Creates objects and starts the game loop
*/
var Setup = function() {
// set the background
SetBackground();
// reset the gameOver variable
gameOver = false;
// setup the start of the game - see scripts/game/levels/start.js
GameStartSetup();
// setup interactions - see scripts/game/interactions.js
Interactions();
// start the game loop i.e. the game script - see scripts/game/loop.js
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
})
.add({
name: "character", // reference
url: "resources/character-sheet.json", // 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 one or multiple groups of custom objects (BRICKS, POWERUPS, ...) if they've been hit and need to disappear.
*
* Notes:
* Each Sprite from the custom object from the 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 objects.
*
* Parameters:
* groupsOfObjectsToCheck - just one or array of JSON objects all looking like:
* {
* objects: array of custom objects containing a GetSprite() method that returns a Pixi sprite object to be check for collision
* ifEmpty: function to run if all objects have been removed
* }
*
* Parameter example:
* groupsOfObjectsToCheck = [
* {
* objects: [], // array of objects
* ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the objects array
* },
* {
* objects: [], // array of objects
* ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the objects array
* }
* ]
*
* Parameter example:
* groupsOfObjectsToCheck = {
* objects: [], // array of objects
* ifEmpty: function() {} // function/behaviour to execute if all objects have been removed from the objects array
* }
*
* Usage example:
* CleanupSprites(
* [
* {
* objects: arrayContainingCustomObjects,
* ifEmpty: function () {
* // code to run if all objects have been hit and are now gone from the stage
* }
* },
* {
* objects: arrayContainingCustomObjects,
* ifEmpty: function () {
* // code to run if all objects have been hit and are now gone from the stage
* }
* },
* ...
* ]
* );
*
*/
var CleanupSprites = function(groupsOfObjectsToCheck) {
// don't change this unless you know exactly why you are doing it
var
// will be used below to store a reference to the objects & their group (defined above) that are currently being checked
group, objects,
// will be used below to store a reference to the object that is currently being checked
sprite;
if (!Array.isArray(groupsOfObjectsToCheck)) {
groupsOfObjectsToCheck = [groupsOfObjectsToCheck];
}
for (var groupIndex = 0; groupIndex < groupsOfObjectsToCheck.length; groupIndex++) {
group = groupsOfObjectsToCheck[groupIndex];
objects = group.objects;
for (var objectIndex = objects.length - 1; objectIndex >= 0; objectIndex--) {
// grab a reference to the object
sprite = objects[objectIndex].GetSprite();
// object hit!
if (sprite.hasBeenHit) {
// destroy the object
sprite.destroy();
// remove the object from the group
objects.splice(objectIndex, 1);
// stop the current for loop
break;
}
}
// if there aren't any other objects left and there is some behaviour for this
if (!objects.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
*
* Usage:
* var pressFn = function() {};
* var releaseFn = function() {};
*
* // replace 76 with your desired ASCII key code
* // uncomment "console.log()" further down to get the key code of any pressed key, for development use
* var kb = new Keyboard(76, pressFn, releaseFn);
*
* if (kb.isDown || kb.isUp) {
* // keyboard used!
* }
*/
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:
* itemToCheck - one of the following
* => a stage part, one of the following: "topLeft", "topRight", "bottomLeft", "bottomRight", "anywhere"
* => OR a rectangular area within the stage: { x: number, y: number, height: number, width: number }
* => OR a circular area within the stage: { x: number, y: number, radius: number }
* => OR a Pixi.js Sprite
* => OR an instance of a custom Class (Star, Brick, etc.)
* press - a "function(){}" that will be executed when the chosen stage part/area/Sprite has been touched
*
* Returns:
* {
* object: the object that collisionDetection will actually check
* type: the type of the object ("part", "box", "sprite")
* isHit: boolean
* position: { // user's touch position within the stage
* x: int
* y: int
* }
* }
*
* Usage:
* var pressFn = function() {};
*
* // obj is a Pixi Sprite or a custom object containing a Pixi Sprite
* // like objects.star1
* var touch = new Touch(obj, pressFn);
*
* if (touch.isHit || touch.isHit) {
* // object is being touched!
* }
*/
// don't change this unless you know exactly why you are doing it
var Touch = function(itemToCheck, press) {
// object that will be returned from this function, similarly to how the keyboard support works
var touchObject = {
object: null,
type: "",
isHit: false,
position: null,
press: press
};
/**
* 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
}
};
// is the object a stage part?
if (stageParts[itemToCheck] !== undefined) {
touchObject.object = stageParts[itemToCheck];
touchObject.type = "part";
}
// is it a Pixi sprite?
else if (itemToCheck.pluginName && itemToCheck.pluginName === "sprite") {
touchObject.object = itemToCheck;
touchObject.type = "sprite";
}
// is it a custom objects that contains a Pixi sprite?
else if (itemToCheck.GetSprite && typeof itemToCheck.GetSprite === "function") {
touchObject.object = itemToCheck.GetSprite();
touchObject.type = "object";
}
// is it a rectangular area?
else if (itemToCheck.x !== undefined && itemToCheck.y !== undefined && itemToCheck.height && itemToCheck.width) {
touchObject.object = itemToCheck;
touchObject.type = "box";
}
// is it a rectangular area?
else if (itemToCheck.x !== undefined && itemToCheck.y !== undefined && itemToCheck.radius) {
itemToCheck.circular = true;
itemToCheck.diameter = itemToCheck.radius;
touchObject.object = itemToCheck;
touchObject.type = "circle";
}
// anything else won't work with this, so stop doing anything else
else {
throw new Error("Touch Helper: itemToCheck parameter is invalid. Please check the documentation in scripts/game/helpers.js. value=", itemToCheck);
}
// every time the user itneracts with the stage, check collision status
var checkTouchCollision = function(event) {
// get the point coordinates of the user's touch
var point = getEventPos(this, event);
// if the user touched within the configured object, then set it accordingly
if (collisionDetection.hitTestPoint(point, touchObject.object)) {
// mark the current touch object as being pressed / down
touchObject.isHit = true;
// point: { x: number, y: number }
touchObject.position = point;
// run the given function, if any
if (touchObject.press && typeof touchObject.press === "function") {
// call the `press` function
// make sure that `this` called inside the `press` function will refer to the object that was just touched
touchObject.press.call(itemToCheck, point);
}
} else {
// mark the current touch object as not being pressed / down
touchObject.isHit = false;
// point: { x: number, y: number }
touchObject.position = point;
}
}
// See here for more information on jQuery.on() : http://api.jquery.com/on/
containerElement.on("touchstart pointerdown touchend pointerup", checkTouchCollision);
// return the touch information for later use
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
};
}
/**
* All relevant collision checks will be triggered from here
*/
var CheckCollisions = function () {
// go through all bricks
for (var key in objects.bricks) {
// get a handle on each brick
var brick = objects.bricks[key];
// if for some reason we've already hit this brick, skip it
// it can happen if lots of collisions happen at once
if (brick.GetSprite().hasBeenHit) {
continue;
}
// check collision with the main character
objects.mainChar.CheckCollision(brick);
}
};
/**
* Creates the game background - should be called first when creating objects in the `Setup` function (scripts/game/setup-objects.js)
*/
var SetBackground = function () {
// create the background using a single colour
// var bg = new Graphics();
// bg.beginFill(0x66CCFF); // set the background colour
// bg.drawRect(0, 0, stageWidth, stageHeight);
// bg.endFill();
//create the background using an existing texture
var bg = new Sprite(resources["chest"].texture);
bg.height = stageHeight;
bg.width = stageWidth;
// make it globally available
objects.background = bg;
// add it to the stage
stage.addChild(bg);
};
/**
* Global variables/arrays/JSON objects go in here
*/
// a series of key-value pairs representing the game objects or object arrays
var objects = {};
// will contain the game levels, each containing a Pixi stage i.e. Container()
var levels = {
start: {},
end: {}
};
// game status
var gameOver;
/**
* Score Factory
*/
var CreateScore = function() {
var
id = "points",
options = {
id: id,
text: "Points: ",
position: {
x: 10,
y: 30 // will appear just beneath the Hits text
}
}
// create a new Score instance and return it should it be needed
return (objects[id] = new Score(options));
}
/**
* Score class. Keeps track of game score.
* (optional) "options" JSON object to be given when creating a new instance
*
* Parameter example:
* {
* id: "score",
* text: "Score: ", // text to precede the score value
* initialScore: 10, // optional
* position: { // optional
* x: 20,
* y: 20
* },
* font: { // optional; if given, all keys seen here must have values set
* fontFamily: "Arial",
* fontSize: 14,
* fill: "white"
* }
* }
*/
var Score = function(options) {
/**
* Current instance
*/
var $this = this;
/**
* Pixi.JS text object
*/
var sprite;
/**
* Score tracker
*/
var score = 0;
/**
* Default config values, a JSON structure
* If any of these keys/values are missing from the `options`, they will be used instead
*/
var baseConfig = {
id: "score",
text: "Score: ",
initialScore: 0,
position: { // starting position
x: 20,
y: 20
},
font: {
fontFamily: "Arial",
fontSize: 14,
fill: "white"
}
};
/**
* 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);
// set the initial score value, if it's a valid number
if (config.initialScore && IsNumeric(config.initialScore)) {
score = config.initialScore;
}
// prepare the Pixi object and add it to the stage
PreparePixiObject();
};
/**
* Build the object and add it to the stage
*/
var PreparePixiObject = function() {
// create the text Sprite
sprite = new Text(
config.text + score, {
fontFamily: "Arial",
fontSize: 14,
fill: "white"
}
);
// determine an [x,y] position for the text
sprite.position.set(config.position.x, config.position.y);
// add it to the stage
levels.start.stage.addChild(sprite);
};
/**
* Method called whenever the score needs changing.
* Accepts a `value` number parameter used to update the score.
* If the number is negative, it will change the score accordingly.
*/
$this.Update = function(value) {
// change the value
score += parseInt(value);
// update the sprite text
sprite.text = "Hits: " + score;
}
/**
* Returns numeric value of current score
*/
$this.Get = function () {
return score;
};
/**
* Saves the current score to LocalStorage - i.e. persistent storage
*/
$this.Save = function () {
// extract the scores array from storage
var scores = LocalStorage.get("scores");
// if we've never had scores saved
if (!scores) {
// initialise the array
scores = [];
}
// add the latest score
scores.push(score);
// sort them highest to lowest
// see https://www.w3schools.com/js/js_array_sort.asp
scores.sort(function (a, b) {
return b - a;
});
// remove the oldest score if we have 11 or more, so we keep 10
if (scores.length > 10) {
// oldest score will be the last one after the above sort
// Array.pop() will remove the last element
scores.pop();
}
// save the scores array
LocalStorage.set("scores", scores);
};
/**
* Run the constructor
*/
Init();
};
/**
* Brick class. Keeps track of a game brick.
* (optional) "options" JSON object to be given when creating a new instance
*
* Parameter example:
* {
* id: "brick",
* dimensions: { // optional
* height: 20,
* width: 20
* }
* position: { // optional
* x: 20,
* y: 20
* }
* }
*/
var Brick = function(options) {
/**
* Current instance
*/
var $this = this;
/**
* Pixi.JS text object
*/
var sprite;
/**
* Default config values, a JSON structure
* If any of these keys/values are missing from the `options`, they will be used instead
*/
var baseConfig = {
id: "brick",
dimensions: {
height: 20,
width: 20
},
position: { // starting position
x: 20,
y: 20
},
scorePerHit: 10
};
/**
* 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);
// set the initial score value, if it's a valid number
if (config.dimensions && IsNumeric(config.dimensions)) {
config.dimensions = {
height: config.dimensions,
width: config.dimensions
};
}
// prepare the Pixi object and add it to the stage
PreparePixiObject();
};
/**
* Build the object and add it to the stage
*/
var PreparePixiObject = function() {
// create the text Sprite
sprite = new Sprite(resources["chest"].texture);
// set the dimensions
sprite.height = config.dimensions.height;
sprite.width = config.dimensions.width;
// determine an [x,y] position for the text
sprite.position.set(config.position.x, config.position.y);
// add it to the level stage
levels.start.stage.addChild(sprite);
};
/**
* Getter method for the Pixi Sprite object
*/
$this.GetSprite = function() {
return sprite;
}
/**
* Method called in scripts/game/collision.js
* Accepts an `object` custom class instance (Star, Character, etc.) parameter used to determine collision
*/
$this.CheckCollision = function(object) {
// perform collision detection between the object and the current Brick Sprite
var collisionResult = collisionDetection.hit(object.GetSprite(), sprite, true, true);
// collided!
if (collisionResult) {
// run the collision action
$this.DoCollision();
}
}
/**
* Collision Action.
* Called by the CheckCollision() method above
*/
$this.DoCollision = function() {
// mark it for removal
sprite.hasBeenHit = true;
// update score
objects.points.Update(config.scorePerHit);
};
/**
* Run the constructor
*/
Init();
};
/**
* Bricks factory - creates many bricks
*/
var CreateBricks = function (bricksRows, bricksPerRow) {
// will keep track of all bricks
objects.bricks = [];
// handle rows
for (var row = 1; row <= bricksRows; row++) {
// handle columns
for (var column = 1; column <= bricksPerRow; column++) {
// create a brick taking into account how many need to fit on each row
CreateBrick(row, column, bricksPerRow);
} // end columns
} // end rows
};
/**
* Brick Factory - creates one brick part of a row/column setup
*/
var CreateBrick = function(row, column, bricksPerRow) {
// determine options for current brick
var options = {
position: {
// Math Vuh-Doo
x: column * stageWidth / (bricksPerRow + 2),
y: row * stageHeight / 10 // considering stage has 10 parts
},
// Math Vuh-Doo
dimensions: {
width: stageWidth / (bricksPerRow + 2), // leave two parts, left and right, as gaps between the bricks and stage walls
height: stageHeight / 10 // considering stage has 10 parts
},
scorePerHit: 30
};
var brick = new Brick(options);
// create a new Brick instance
objects.bricks.push(brick);
// also return the instance if needed
return brick;
}
/**
* Here will take place the setup of various keyboard/touch interactions
*/
var Interactions = function () {
};
{"frames": {
"Layer_1":
{
"frame": {"x":0,"y":0,"w":290,"h":914},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":290,"h":914},
"sourceSize": {"w":290,"h":914}
},
"Layer_2":
{
"frame": {"x":290,"y":0,"w":332,"h":909},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":332,"h":909},
"sourceSize": {"w":332,"h":909}
},
"Layer_3":
{
"frame": {"x":622,"y":0,"w":530,"h":906},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":530,"h":906},
"sourceSize": {"w":530,"h":906}
},
"Layer_4":
{
"frame": {"x":1152,"y":0,"w":690,"h":918},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":690,"h":918},
"sourceSize": {"w":690,"h":918}
},
"Layer_5":
{
"frame": {"x":0,"y":918,"w":512,"h":917},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":512,"h":917},
"sourceSize": {"w":512,"h":917}
},
"Layer_6":
{
"frame": {"x":512,"y":918,"w":336,"h":909},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":336,"h":909},
"sourceSize": {"w":336,"h":909}
},
"Layer_7":
{
"frame": {"x":848,"y":918,"w":529,"h":907},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":529,"h":907},
"sourceSize": {"w":529,"h":907}
},
"Layer_8":
{
"frame": {"x":0,"y":1835,"w":688,"h":920},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":688,"h":920},
"sourceSize": {"w":688,"h":920}
},
"Layer_9":
{
"frame": {"x":688,"y":1835,"w":525,"h":919},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":525,"h":919},
"sourceSize": {"w":525,"h":919}
},
"Layer_10":
{
"frame": {"x":1213,"y":1835,"w":289,"h":913},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":289,"h":913},
"sourceSize": {"w":289,"h":913}
}},
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "https://i.imgur.com/C5Fo19r.png",
"format": "RGBA8888",
"size": {"w":1842,"h":2755},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:9bb2301b68abe277fcbf712ae31f2074:1d888e7d86ab5203e3a92f7d62bac124:c11f47e90ac3a31cb6fed31d32f496f1$"
}
}
// 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
/**
* Character Class - "options" JSON 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
* // ".character" is the identifier that needs to be used below with the "id:" attribute
* objects.character = new Star(
* {
* id: "character", // the identifier for this object, must be the same as the identifier given in the objects JSON
* dimensions: {
* height: 100, // pixels
* width: 100 // pixels
* },
* position: { // starting position
* x: stageHeight / 2,
* y: stageWidth / 2
* },
* speed: 5, // a number to be used for both directions' velocities
* 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 Character = function(options) {
/**
* Current class instance
*/
var $this = this;
/**
* Pixi.JS Sprite
*/
var sprite;
/**
* Texture frame ID number from the atlas - will be changed each animation cycle (or less frequent potentially so the animation won't be too fast)
*/
var currentTextureNumber = 1;
/**
* The texture frames ID prefix
*/
var texturePrefix = "Layer_";
/**
* Frame change frequency i.e. once every how many game loops should this sprite's texture change
*/
var frequency = 15; // once every 6 frames, so 10 times per second
var frequencyCounter = 1;
/**
* Movement animation final position
*/
var finalPosition;
/**
* Base Pixi.JS object config, a JSON structure
*/
var config;
/**
* Default config values, a JSON structure
* If any of these keys/values are missing from the `options`, they will be used instead
*/
var baseConfig = {
id: "character",
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. Will be called at the bottom of the class.
*/
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 "dimensions" option - if number given instead of {height, width} JSON, then use that
if (IsNumeric(config.dimensions)) {
config.dimensions = {
height: config.dimensions,
width: config.dimensions
};
}
// 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
};
}
// ensure the starting position is the final movement position for now
finalPosition = config.position;
// prepare the Pixi Sprite and add it to the stage
PreparePixiSprite();
// prepare touch response
PrepareTouchResponse();
};
/**
* Build the object and add it to the stage
*/
var PreparePixiSprite = function() {
// create the object using one of the "character"'s preloaded texture frames as its Sprite
sprite = new Sprite(resources["character"].textures[texturePrefix + currentTextureNumber]);
// set the height
sprite.height = config.dimensions.height;
// set the width
sprite.width = config.dimensions.width;
// set its initial horizontal position
sprite.x = config.position.x;
// set its initial vertical position
sprite.y = config.position.y;
// set the horizontal movement speed/velocity
sprite.vx = config.speed.x;
// set the vertical movement speed/velocity
sprite.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
*/
sprite.anchor.set(0.5, 0.5);
// set some helper values on this object for later use
sprite.halfHeight = sprite.height / 2;
sprite.halfWidth = sprite.width / 2;
// add it to the stage
levels.start.stage.addChild(sprite);
};
/**
* Touch response - ensure the character moves across the stage
*/
var PrepareTouchResponse = function() {
var touchStageBottomRight = new Touch(
// if player touches the stage anywhere at all
"anywhere",
// execute the following when they do
// `position` is a JSON object that looks like: { x: number, y: number }
// indicates the position within the stage where the user just tapped
function(position) {
// move the character to that position
finalPosition = position;
}
);
};
/**
* Frame changing logic - private
*/
var ChangeToNextFrame = function() {
// increment the game loop frequency counter
frequencyCounter++;
// if it's not yet time
if (frequencyCounter < frequency) {
// don't do anything
return;
} else {
// otherwise, reset the counter
frequencyCounter = 1;
}
// increase the texture frame number
currentTextureNumber++;
// check if it needs to be reset i.e. the frame number is higher than the number of available frames
if (currentTextureNumber >= Object.keys(resources["character"].textures).length) {
// reset it to 1
currentTextureNumber = 1;
}
// reset the sprite's texture with the newly calculated frame ID
sprite.texture = resources["character"].textures[texturePrefix + currentTextureNumber];
}
/**
* Movement logic
*/
$this.Animate = function() {
// if we're there or thereabout (speed dependent)
// i.e. if we're within vx and vy pixels of the final position
if (Math.abs(sprite.x - finalPosition.x) < sprite.vx && Math.abs(sprite.y - finalPosition.y) < sprite.vy) {
// don't do anything
return;
}
// if there's still room to move according to the character's movement speed
if (Math.abs(sprite.x - finalPosition.x) > sprite.vx) {
// if the character's X position is greater than the player's touch's X position
if (sprite.x > finalPosition.x) {
// then decrease the character's X position to bring it closer to the player's touch's X position
sprite.x -= sprite.vx;
// flip the sprite so it looks like it's moving left
// the textures we're using for this Character are facing right
if (sprite.scale.x > 0) {
sprite.scale.x *= -1;
}
// otherwise if the character's X position is less than the player's touch's X position
} else if (sprite.x < finalPosition.x) {
// then increase the character's X position to bring it closer to the player's touch's X position
sprite.x += sprite.vx;
// flip the sprite so it looks like it's moving right
// the textures we're using for this Character are facing right
if (sprite.scale.x < 0) {
sprite.scale.x *= -1;
}
}
}
// if there's still room to move according to the character's movement speed
if (Math.abs(sprite.y - finalPosition.y) > sprite.vy) {
// if the character's Y position is greater than the player's touch's Y position
if (sprite.y > finalPosition.y) {
// then decrease the character's Y position to bring it closer to the player's touch's Y position
sprite.y -= sprite.vy;
// otherwise if the character's Y position is less than the player's touch's Y position
} else if (sprite.y < finalPosition.y) {
// then increase the character's Y position to bring it closer to the player's touch's Y position
sprite.y += sprite.vy;
}
}
// if the code reached this place, it means we've been moving, so ensure the sprite's frame is changing
ChangeToNextFrame();
// use Bump to contain this sprite within the stage
collisionDetection.contain(sprite, stageBox);
};
/**
* Collision logic against a foreign object
*/
$this.CheckCollision = function(object) {
// the collision detection tool's result - checks hits between this object's Sprite and the foreign object's Sprite
var collisionResult = collisionDetection.hit(sprite, object.GetSprite());
// Collided!
if (collisionResult) {
// run the collision logic on this object
$this.DoCollision();
// run the brick collision logic
object.DoCollision();
}
};
/**
* Something to do if collided - called in the `CheckCollision` method above
*/
$this.DoCollision = function() {
// nothing as of yet, it all happens on the brick side
};
/**
* Call the instance constructor
*/
Init();
};
/**
* Character Factory
* Accepts optional `options` JSON object that will be passed to the Character class
* See Character class for more info on the `options` JSON object
*/
var CreateCharacter = function (id, options) {
var baseConfig = {
dimensions: 60, // value can also be given as { height: number, width: number } to set separate dimensions
position: { // starting position
// get a random number between a quarter and three quarters of the stage's width
x: stageWidth / 2,
// get a random number between a quarter and three quarters of the stage's width
y: stageHeight - 60
},
speed: 3, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
};
var config = Object.assign({}, baseConfig, options, { id: id });
// create a new Character instance and return it
return (objects[id] = new Character(config));
}
var GameStartSetup = function () {
// add the first game level to the stage
levels.start.stage = new Container();
stage.addChild(levels.start.stage);
// build a new character
CreateCharacter("mainChar");
// build the points text
CreateScore();
// create two 2 of bricks, 5 per row
CreateBricks(2, 5);
}
/**
* Prepares the end game scene
*/
var GameEndSetup = function() {
// create a new container for the end stage
levels.end.stage = new Container();
// create the final score text
var finalScore = new Text(
"Final score: " + objects.points.Get(), {
fontFamily: "Arial",
fontSize: 14,
fill: "white"
}
);
// put it in the top-middle (ish)
finalScore.position.set(stageWidth / 2 - finalScore.width / 2, stageHeight / 13);
// add it to the stage
levels.end.stage.addChild(finalScore);
// prepare leaderboard
var leaderboardText = new Text(
"Top 10", {
fontFamily: "Arial",
fontSize: 14,
fill: "white"
}
);
// position it below the final score
leaderboardText.position.set(stageWidth / 2 - leaderboardText.width / 2, stageHeight / 13 * 2);
// add it to the stage
levels.end.stage.addChild(leaderboardText);
// now let's prepare the score list
var scores = LocalStorage.get("scores");
// iterate over all scores
for (var i = 0; i < scores.length; i++) {
// extract each score value
var score = scores[i];
// prepare a new pixi object
// prepare leaderboard
var scoreText = new Text(
score, {
fontFamily: "Arial",
fontSize: 14,
fill: "white"
}
);
// position it below the previous bit of text
scoreText.position.set(stageWidth / 2 - scoreText.width / 2, stageHeight / 13 * (i + 3));
// add it to the stage
levels.end.stage.addChild(scoreText);
}
// add the whole thing to main stage
stage.addChild(levels.end.stage);
}