<!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/star.js"></script>
<script type="text/javascript" src="scripts/game/classes/score.js"></script>
<script type="text/javascript" src="scripts/game/classes/asteroid.js"></script>
<script type="text/javascript" src="scripts/game/classes/platform.js"></script>
<!-- Class Factories - can create specific game objects - link other factories files here -->
<script type="text/javascript" src="scripts/game/factories/star.js"></script>
<script type="text/javascript" src="scripts/game/factories/score.js"></script>
<script type="text/javascript" src="scripts/game/factories/asteroid.js"></script>
<script type="text/javascript" src="scripts/game/factories/platform.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>
<!-- 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(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
/**
* 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
* // ".star" is the identifier that needs to be used below with the "id:" attribute
* objects.star = new Star(
* {
* id: "star", // 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,
* 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
* },
* onHit: function () {
* // code to run if this particular object has been hit
* }
* }
* );
*
*/
var Star = function(options) {
/**
* Current class instance
*/
var $this = this;
/**
* Pixi.JS Sprite
*/
var sprite;
/**
* 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: "star",
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
};
}
// prepare the Pixi Sprite and add it to the stage
PreparePixiSprite();
};
/**
* Build the object and add it to the stage
*/
var PreparePixiSprite = function() {
// create the object using the "star" preloaded texture as its Sprite
sprite = new Sprite(resources["star"].texture);
// 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;
// denote it as a circular shape - for collision detection purposes
sprite.circular = true;
// actually make the Sprite be circular by applying a mask to the Texture it uses
// first, create the relevant shape
var circleMask = new Graphics();
circleMask.beginFill(0x9966FF);
circleMask.drawCircle(0, 0, sprite.halfHeight + 2); // use the halfHeight property as the mask's radius - needs an extra 2 pixels to be accurate
circleMask.endFill();
// then add the shape as the Sprite's mask
sprite.mask = circleMask; // comment this out to check if the mask shape actually fills the Sprite area as expected and modify the Graphics object accordingly
// also add the mask as a child to our Sprite - doesn't work without it
sprite.addChild(circleMask);
// add it to the stage
stage.addChild(sprite);
};
// getter method for the Pixi Sprite object
$this.GetSprite = function() {
return sprite;
}
// move object horizontally according to its speed/velocity
$this.MoveX = function() {
sprite.x += sprite.vx;
};
// move object vertically according to its speed/velocity
$this.MoveY = function() {
sprite.y += sprite.vy;
};
// reposition object to new x,y coordinates
$this.MoveTo = function(x, y) {
sprite.x = x;
sprite.y = y;
};
$this.Animate = function() {
// use Bump to contain this sprite within the stage
// the third parameter makes it bounce off the walls
collisionDetection.contain(sprite, stageBox, true);
// move the object horizontally
//$this.MoveX();
// move the object vertically
//$this.MoveY();
};
/**
* 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.movingCircleCollision(sprite, object.GetSprite(), false);
// var collisionResult = collisionDetection.hit(sprite, object.GetSprite(), true, false, true);
// check if the Star has collided with the foreign object's Sprite
// result may be different depending on which type of collision it was
// boolean (TRUE/FALSE) result if foreign sprite is also a circle
// one of the following if the foreign sprite is a rectangle:
// HIT: "topLeft", "topMiddle", "topRight", "leftMiddle", "rightMiddle", "bottomLeft", "bottomMiddle", or "bottomRight"
// NO HIT: undefined
//
// if collisionResult is NOT FALSE or undefined
if (collisionResult) {
// run the collision logic on this object - will update score as well
$this.DoCollision();
// run the collision logic on the foreign object - but don't update score again
object.DoCollision(false);
}
};
/**
* Something to do if collided - called in the `CheckCollision` method above
* If `alsoUpdateScore` is given a boolean value, it will update the score if parameter value is `TRUE`
*/
$this.DoCollision = function(alsoUpdateScore) {
// set the collision following flag on the pixi object
sprite.hasBeenHit = true;
// and add this object to the objects removal list
globalObjectsToRemove.stars.objects.push($this);
// spawn another in its place using the same config as at the start
// effectively reset the star object, replace it with a fresh instance
// objects[config.id] = CreateStar(config.id);
// update game score if required, increment it by 1
if (alsoUpdateScore === undefined || alsoUpdateScore === true) {
objects.score.Update(1);
}
};
/**
* 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
* This is effectively the game script
*/
var GameLoop = function() {
/**
* Animate/Move objects
* "objects" is defined in "scripts/setup/globals.js"
*/
// go through all existing game objects
// this kind of code structure can be used if you have a set of objects you want to group together and perform a set of actions (like Animate()) on all of them
// 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();
// }
// }
// or just do it manually, as the case requires it
//objects.star1.Animate();
//objects.star2.Animate();
// objects.asteroid.Animate();
for (var key in objects.asteroids) {
var asteroid = objects.asteroids[key];
asteroid.Animate();
}
/**
* Check all relevant collisions
* See "scripts/game/collision.js"
*/
CheckCollisions();
/**
* Remove all objects from the stage that have been added to the `globalObjectsToRemove` JSON structure's arrays
* "globalObjectsToRemove" is defined in "scripts/setup/globals.js"
*/
for (var key in globalObjectsToRemove) {
// `key` will be, for example, `stars` from the `globalObjectsToRemove` JSON object
// you can add more groups, like `stars`, to the qglobalObjectsToRemoveq JSON object in "scripts/setup/globals.js"
CleanupSprites(globalObjectsToRemove[key]);
}
/**
* 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
*/
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();
// build a new star
objects.star1 = CreateStar("star1", 100, 800);
objects.star2 = CreateStar("star2", 200, 800);
objects.star3 = CreateStar("star3", 300, 800);
objects.star4 = CreateStar("star4", 400, 800);
objects.star5 = CreateStar("star5", 500, 800);
// build the hits text
objects.score = CreateScore();
objects.platform1 = CreatePlatform("platform1", 100, 700);
objects.platform2 = CreatePlatform("platform2", 300, 700);
objects.platform3 = CreatePlatform("platform3", 500, 700);
objects.platform4 = CreatePlatform("platform4", 700, 700);
objects.asteroids = [
CreateAsteroid("asteroid1"),
CreateAsteroid("asteroid2"),
CreateAsteroid("asteroid3"),
CreateAsteroid("asteroid4"),
CreateAsteroid("asteroid5")
];
// start the game loop i.e. the game script - 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: "galaxy", // reference
url: "https://i.imgur.com/Fm2qSGt.jpg", // remote location
crossOrigin: true // needs this to be able to load images remotely
})
.add({
name: "asteroid",
url: "https://i.imgur.com/dMb74Tk.png",
crossOrigin: true
})
.add({
name: "Platform",
url: "https://i.imgur.com/7JF2Vqa.png",
crossOrigin: true
})
// 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 Pixi.js Sprite
* press - a "function(){}" that will be executed when the chosen stage part/area/Sprite has been touched
* release - a "function(){}" that will be executed when the chosen stage part/area/Sprite has stopped being 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() {};
* var releaseFn = function() {};
*
* // obj is a Pixi Sprite
* var touch = new Touch(obj, pressFn, releaseFn);
*
* 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, release) {
// 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,
release: release
};
/**
* 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 rectangular area?
else if (itemToCheck.x && itemToCheck.y && itemToCheck.height && itemToCheck.width) {
touchObject.object = itemToCheck;
touchObject.type = "box";
}
// 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") {
touchObject.press();
}
} else {
// mark the current touch object as not being pressed / down
touchObject.isHit = false;
// point: { x: number, y: number }
touchObject.position = point;
// run the given function, if any
if (touchObject.release && typeof touchObject.release == "function") {
touchObject.release();
}
}
}
// See here for more information on jQuery.on() : http://api.jquery.com/on/
containerElement.on("touchstart mousedown pointerdown touchend mouseup 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 () {
// check collision between the two stars
objects.star1.CheckCollision(objects.star2);
var sprites = [];
for (var key in objects.asteroids) {
var asteroid = objects.asteroids[key];
asteroid.CheckCollision(objects.star1);
asteroid.CheckCollision(objects.star2);
asteroid.CheckCollision(objects.star3);
asteroid.CheckCollision(objects.star4);
asteroid.CheckCollision(objects.star5);
asteroid.CheckCollision(objects.platform1);
asteroid.CheckCollision(objects.platform2);
asteroid.CheckCollision(objects.platform3);
asteroid.CheckCollision(objects.platform4);
sprites = asteroid.GetSprite();
}
collisionDetection.multipleCircleCollision(sprites);
};
/**
* Star Factory
*/
var CreateStar = function (id, xPos, yPos) {
// create a new Star instance and return it
return new Star({
id: id, // must be the same as the identifier in the objects JSON
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: xPos, //RandomNumberBetween(stageWidth / 4, stageWidth / 4 * 3),
// get a random number between a quarter and three quarters of the stage's width
y: yPos// RandomNumberBetween(stageHeight / 4, stageHeight / 4 * 3)
},
speed: 3, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
});
}
/**
* 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["galaxy"].texture);
bg.height = stageHeight;
bg.width = stageHeight;
// 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 = {};
// this is a convenience JSON structure that will serve as a killer for custom objects that have been "hit"
// see lecture notes for more information on this
// also see the `DoCollision` method of the `Star` class in "scripts/game/classes/star.js" for an example
var globalObjectsToRemove = {
stars: {
objects: [],
ifEmpty: function () {
// if you would like to execute something by default once all stars that have been marked for remval are gone
// can be replaced at any time
}
},
chests: {
objects: [],
ifEmpty: function () {
//
}
}
// ... add other groups here by following the above structure
};
/**
* Score Factory
* Accepts an `initialScore` as a number to set the starting score accordingly. The Class uses 0 by default if not set.
*/
var CreateScore = function (initialScore) {
// create a new Score instance and return it
return new Score(initialScore);
}
/**
* Score class. Keeps track of game score.
* Accepts `initialScore` as a starting score value when creating a new instance. Uses 0 by default if not set.
*/
var Score = function(initialScore) {
/**
* Current instance
*/
var $this = this;
/**
* Pixi.JS text object
*/
var sprite;
/**
* Score tracker
*/
var score = 0;
/**
* Constructor
*/
var Init = function() {
// set the initial score value, if it's a valid number
if (initialScore && IsNumeric(initialScore)) {
score = 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(
"Hits: " + score, {
fontFamily: "Arial",
fontSize: 14,
fill: "white"
}
);
// determine an [x,y] position for the text
sprite.position.set(20, 20);
// add it to the stage
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 += value;
// update the sprite text
sprite.text = "Hits: " + score;
}
/**
* Run the constructor
*/
Init();
};
/**
* Asteroid Factory
*/
var CreateAsteroid = function (id) {
// create a new ASTEROID instance and return it
return new Asteroid({
id: id, // must be the same as the identifier in the objects JSON
dimensions: 40, // 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: RandomNumberBetween(stageWidth / 4, stageWidth / 4 * 3),
// get a random number between a quarter and three quarters of the stage's width
y: RandomNumberBetween(stageHeight / 4, stageHeight / 4 * 3)
},
speed: 5, // value can also be given as { x: X_SPEED, y: Y_SPEED } to set separate axis speeds
});
}
var Asteroid = function(options)
{
var $this = this;
var sprite;
var config;
var baseConfig =
{
id: "asteroid",
dimensions:
{
height: 20,
width: 20
},
position:
{
x: stageHeight / 2,
y: stageWidth / 2
},
speed:
{
x: 5,
y: 5
},
};
var Init = function()
{
if(!options) options = {};
config = Object.assign({}, baseConfig, options);
if (IsNumeric(config.dimensions))
{
config.dimensions =
{
height: config.dimensions,
width: config.dimensions
};
}
if (IsNumeric(config.speed))
{
config.speed =
{
x: config.speed,
y: config.speed
};
}
// prepare the Pixi Sprite and add it to the stage
PreparePixiSprite();
};
var PreparePixiSprite = function() {
// create the object using the "star" preloaded texture as its Sprite
sprite = new Sprite(resources["asteroid"].texture);
// 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;
// denote it as a circular shape - for collision detection purposes
sprite.circular = true;
// actually make the Sprite be circular by applying a mask to the Texture it uses
// first, create the relevant shape
// var circleMask = new Graphics();
// circleMask.beginFill(0x9966FF);
// circleMask.drawCircle(0, 0, sprite.height); // use the halfHeight property as the mask's radius - needs an extra 2 pixels to be accurate
// circleMask.endFill();
// // then add the shape as the Sprite's mask
// sprite.mask = circleMask; // comment this out to check if the mask shape actually fills the Sprite area as expected and modify the Graphics object accordingly
// // also add the mask as a child to our Sprite - doesn't work without it
// sprite.addChild(circleMask);
// add it to the stage
stage.addChild(sprite);
};
// getter method for the Pixi Sprite object
$this.GetSprite = function() {
return sprite;
}
// move object horizontally according to its speed/velocity
$this.MoveX = function() {
sprite.x += sprite.vx;
};
// move object vertically according to its speed/velocity
$this.MoveY = function() {
sprite.y += sprite.vy;
};
// reposition object to new x,y coordinates
$this.MoveTo = function(x, y) {
sprite.x = x;
sprite.y = y;
};
$this.Animate = function() {
// use Bump to contain this sprite within the stage
// the third parameter makes it bounce off the walls
collisionDetection.contain(sprite, stageBox, true);
// move the object horizontally
$this.MoveX();
// move the object vertically
$this.MoveY();
};
/**
* Collision logic against a foreign object
*/
$this.CheckCollision = function(object, doAction) {
// the collision detection tool's result - checks hits between this object's Sprite and the foreign object's Sprite
var collisionResult = collisionDetection.circleCollision(sprite, object.GetSprite());
// var collisionResult = collisionDetection.hit(sprite, object.GetSprite(), true, false, true);
// check if the Star has collided with the foreign object's Sprite
// result may be different depending on which type of collision it was
// boolean (TRUE/FALSE) result if foreign sprite is also a circle
// one of the following if the foreign sprite is a rectangle:
// HIT: "topLeft", "topMiddle", "topRight", "leftMiddle", "rightMiddle", "bottomLeft", "bottomMiddle", or "bottomRight"
// NO HIT: undefined
//
// if collisionResult is NOT FALSE or undefined
if (collisionResult && (doAction === undefined || doAction === true)) {
// run the collision logic on this object - will update score as well
$this.DoCollision();
// run the collision logic on the foreign object - but don't update score again
object.DoCollision(false);
}
};
/**
* Something to do if collided - called in the `CheckCollision` method above
* If `alsoUpdateScore` is given a boolean value, it will update the score if parameter value is `TRUE`
*/
$this.DoCollision = function(alsoUpdateScore) {
// set the collision following flag on the pixi object
sprite.hasBeenHit = true;
// and add this object to the objects removal list
// globalObjectsToRemove.stars.objects.push($this);
// spawn another in its place using the same config as at the start
// effectively reset the star object, replace it with a fresh instance
// objects[config.id] = CreateStar(config.id);
// update game score if required, increment it by 1
if (alsoUpdateScore === undefined || alsoUpdateScore === true) {
objects.score.Update(1);
}
};
/**
* Call the instance constructor
*/
Init();
};
var CreatePlatform = function (id, xPos, yPos) {
return new Platform({
id: id,
//dimensions: 50,
position:
{
x: xPos, //RandomNumberBetween(stageWidth / 4, stageWidth / 4 * 3),
y: yPos //RandomNumberBetween(stageHeight / 4, stageHeight / 4 * 3)
},
speed: 2,
});
}
var Platform = function(options)
{
var $this = this;
var sprite;
var config;
var baseConfig = //This method gives the platform its details such as name, size, position and speed
{
id: "Platform",
dimensions:
{
height: 50,
width: 100
},
position:
{
x: stageWidth / 2,
y: stageHeight / 2
},
speed:
{
x: 0,
y: 0
},
};
var Init = function() //This method is like the "Start/Awake method"
{
if(!options) options = {};
config = Object.assign({}, baseConfig, options);
if(IsNumeric(config.dimensions))
{
config.dimensions =
{
height: config.dimensions,
width: config.dimensions
};
}
if(IsNumeric(config.speed))
{
config.speed =
{
x: config.speed,
y: config.speed
};
}
PreparePixiSprite();
};
var PreparePixiSprite = function()
{
sprite = new Sprite(resources["Platform"].texture);
sprite.height = config.dimensions.height;
sprite.width = config.dimensions.width;
sprite.x = config.position.x;
sprite.y = config.position.y;
sprite.vx = config.speed.x;
sprite.vy = config.speed.y;
sprite.anchor.set(0.5,0.5);
sprite.halfHeight = sprite.height /2;
sprite.halfWidth = sprite.width / 2;
//console.log('sprite x:', sprite.x, 'y:', sprite.y);
sprite.circular = true;
stage.addChild(sprite);
};
$this.MoveTo = function(x, y) {
sprite.x = x;
sprite.y = y;
};
// move object horizontally according to its speed/velocity
$this.MoveX = function() {
sprite.x += sprite.vx;
};
// move object vertically according to its speed/velocity
$this.MoveY = function() {
sprite.y += sprite.vy;
};
$this.GetSprite = function()
{
return sprite;
};
$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.movingCircleCollision(sprite, object.GetSprite(), false);
// var collisionResult = collisionDetection.hit(sprite, object.GetSprite(), true, false, true);
// check if the Star has collided with the foreign object's Sprite
// result may be different depending on which type of collision it was
// boolean (TRUE/FALSE) result if foreign sprite is also a circle
// one of the following if the foreign sprite is a rectangle:
// HIT: "topLeft", "topMiddle", "topRight", "leftMiddle", "rightMiddle", "bottomLeft", "bottomMiddle", or "bottomRight"
// NO HIT: undefined
//
// if collisionResult is NOT FALSE or undefined
if (collisionResult) {
// run the collision logic on this object - will update score as well
$this.DoCollision();
// run the collision logic on the foreign object - but don't update score again
object.DoCollision(false);
}
};
$this.DoCollision = function(alsoUpdateScore) {
// set the collision following flag on the pixi object
sprite.hasBeenHit = true;
// and add this object to the objects removal list
// globalObjectsToRemove.stars.objects.push($this);
// spawn another in its place using the same config as at the start
// effectively reset the star object, replace it with a fresh instance
// objects[config.id] = CreateStar(config.id);
// update game score if required, increment it by 1
// if (alsoUpdateScore === undefined || alsoUpdateScore === false) {
// objects.score.Update(0);
//}
};
Init();
}