| | | |<----->| |
// | | 1 | | | | 2 | | | | 3 | |
// |_|___|_| |_|_____|_| |_|_______|_|
//
if (this.size > 1) {
this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
this.collisionBoxes[2].width;
this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
}
this.gap = this.getGap(this.gapCoefficient, speed);
},
/**
* Draw and crop based on size.
*/
draw: function() {
var sourceWidth = this.typeConfig.width;
var sourceHeight = this.typeConfig.height;
if (IS_HIDPI) {
sourceWidth = sourceWidth * 2;
sourceHeight = sourceHeight * 2;
}
// Sprite
var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1));
this.canvasCtx.drawImage(this.image,
sourceX, 0,
sourceWidth * this.size, sourceHeight,
this.xPos, this.yPos,
this.typeConfig.width * this.size, this.typeConfig.height);
},
/**
* Obstacle frame update.
* @param {number} deltaTime
* @param {number} speed
*/
update: function(deltaTime, speed) {
if (!this.remove) {
this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
this.draw();
if (!this.isVisible()) {
this.remove = true;
}
}
},
/**
* Calculate a random gap size.
* - Minimum gap gets wider as speed increses
* @param {number} gapCoefficient
* @param {number} speed
* @return {number} The gap size.
*/
getGap: function(gapCoefficient, speed) {
var minGap = Math.round(this.width * speed +
this.typeConfig.minGap * gapCoefficient);
var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
return getRandomNum(minGap, maxGap);
},
/**
* Check if obstacle is visible.
* @return {boolean} Whether the obstacle is in the game area.
*/
isVisible: function() {
return this.xPos + this.width > 0;
},
/**
* Make a copy of the collision boxes, since these will change based on
* obstacle type and size.
*/
cloneCollisionBoxes: function() {
var collisionBoxes = this.typeConfig.collisionBoxes;
for (var i = collisionBoxes.length - 1; i >= 0; i--) {
this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x,
collisionBoxes[i].y, collisionBoxes[i].width,
collisionBoxes[i].height);
}
}
};
/**
* Obstacle definitions.
* minGap: minimum pixel space betweeen obstacles.
* multipleSpeed: Speed at which multiples are allowed.
*/
Obstacle.types = [
{
type: 'CACTUS_SMALL',
className: ' cactus cactus-small ',
width: 17,
height: 35,
yPos: 105,
multipleSpeed: 3,
minGap: 120,
collisionBoxes: [
new CollisionBox(0, 7, 5, 27),
new CollisionBox(4, 0, 6, 34),
new CollisionBox(10, 4, 7, 14)
]
},
{
type: 'CACTUS_LARGE',
className: ' cactus cactus-large ',
width: 25,
height: 50,
yPos: 90,
multipleSpeed: 6,
minGap: 120,
collisionBoxes: [
new CollisionBox(0, 12, 7, 38),
new CollisionBox(8, 0, 7, 49),
new CollisionBox(13, 10, 10, 38)
]
}
];
//******************************************************************************
/**
* T-rex game character.
* @param {HTMLCanvas} canvas
* @param {HTMLImage} image Character image.
* @constructor
*/
function Trex(canvas, image) {
this.canvas = canvas;
this.canvasCtx = canvas.getContext('2d');
this.image = image;
this.xPos = 0;
this.yPos = 0;
// Position when on the ground.
this.groundYPos = 0;
this.currentFrame = 0;
this.currentAnimFrames = [];
this.blinkDelay = 0;
this.animStartTime = 0;
this.timer = 0;
this.msPerFrame = 1000 / FPS;
this.config = Trex.config;
// Current status.
this.status = Trex.status.WAITING;
this.jumping = false;
this.jumpVelocity = 0;
this.reachedMinHeight = false;
this.speedDrop = false;
this.jumpCount = 0;
this.jumpspotX = 0;
this.init();
};
/**
* T-rex player config.
* @enum {number}
*/
Trex.config = {
DROP_VELOCITY: -5,
GRAVITY: 0.6,
HEIGHT: 47,
INIITAL_JUMP_VELOCITY: -10,
INTRO_DURATION: 1500,
MAX_JUMP_HEIGHT: 30,
MIN_JUMP_HEIGHT: 30,
SPEED_DROP_COEFFICIENT: 3,
SPRITE_WIDTH: 262,
START_X_POS: 50,
WIDTH: 44
};
/**
* Used in collision detection.
* @type {Array.
}
*/
Trex.collisionBoxes = [
new CollisionBox(1, -1, 30, 26),
new CollisionBox(32, 0, 8, 16),
new CollisionBox(10, 35, 14, 8),
new CollisionBox(1, 24, 29, 5),
new CollisionBox(5, 30, 21, 4),
new CollisionBox(9, 34, 15, 4)
];
/**
* Animation states.
* @enum {string}
*/
Trex.status = {
CRASHED: 'CRASHED',
JUMPING: 'JUMPING',
RUNNING: 'RUNNING',
WAITING: 'WAITING'
};
/**
* Blinking coefficient.
* @const
*/
Trex.BLINK_TIMING = 7000;
/**
* Animation config for different states.
* @enum {object}
*/
Trex.animFrames = {
WAITING: {
frames: [44, 0],
msPerFrame: 1000 / 3
},
RUNNING: {
frames: [88, 132],
msPerFrame: 1000 / 12
},
CRASHED: {
frames: [220],
msPerFrame: 1000 / 60
},
JUMPING: {
frames: [0],
msPerFrame: 1000 / 60
}
};
Trex.prototype = {
/**
* T-rex player initaliser.
* Sets the t-rex to blink at random intervals.
*/
init: function() {
this.blinkDelay = this.setBlinkDelay();
this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
Runner.config.BOTTOM_PAD;
this.yPos = this.groundYPos;
this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
this.draw(0, 0);
this.update(0, Trex.status.WAITING);
},
/**
* Setter for the jump velocity.
* The approriate drop velocity is also set.
*/
setJumpVelocity: function(setting) {
this.config.INIITAL_JUMP_VELOCITY = -setting;
this.config.DROP_VELOCITY = -setting / 2;
},
/**
* Set the animation status.
* @param {!number} deltaTime
* @param {Trex.status} status Optional status to switch to.
*/
update: function(deltaTime, opt_status) {
this.timer += deltaTime;
// Update the status.
if (opt_status) {
this.status = opt_status;
this.currentFrame = 0;
this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
this.currentAnimFrames = Trex.animFrames[opt_status].frames;
if (opt_status == Trex.status.WAITING) {
this.animStartTime = getTimeStamp();
this.setBlinkDelay();
}
}
// Game intro animation, T-rex moves in from the left.
if (this.playingIntro && this.xPos < this.config.START_X_POS) {
this.xPos += Math.round((this.config.START_X_POS /
this.config.INTRO_DURATION) * deltaTime);
}
if (this.status == Trex.status.WAITING) {
this.blink(getTimeStamp());
} else {
this.draw(this.currentAnimFrames[this.currentFrame], 0);
}
// Update the frame position.
if (this.timer >= this.msPerFrame) {
this.currentFrame = this.currentFrame ==
this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
this.timer = 0;
}
},
/**
* Draw the t-rex to a particular position.
* @param {number} x
* @param {number} y
*/
draw: function(x, y) {
var sourceX = x;
var sourceY = y;
var sourceWidth = this.config.WIDTH;
var sourceHeight = this.config.HEIGHT;
if (IS_HIDPI) {
sourceX *= 2;
sourceY *= 2;
sourceWidth *= 2;
sourceHeight *= 2;
}
this.canvasCtx.drawImage(this.image, sourceX, sourceY,
sourceWidth, sourceHeight,
this.xPos, this.yPos,
this.config.WIDTH, this.config.HEIGHT);
},
/**
* Sets a random time for the blink to happen.
*/
setBlinkDelay: function() {
this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
},
/**
* Make t-rex blink at random intervals.
* @param {number} time Current time in milliseconds.
*/
blink: function(time) {
var deltaTime = time - this.animStartTime;
if (deltaTime >= this.blinkDelay) {
this.draw(this.currentAnimFrames[this.currentFrame], 0);
if (this.currentFrame == 1) {
// Set new random delay to blink.
this.setBlinkDelay();
this.animStartTime = time;
}
}
},
/**
* Initialise a jump.
*/
startJump: function() {
if (!this.jumping) {
this.update(0, Trex.status.JUMPING);
this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY;
this.jumping = true;
this.reachedMinHeight = false;
this.speedDrop = false;
}
},
/**
* Jump is complete, falling down.
*/
endJump: function() {
if (this.reachedMinHeight &&
this.jumpVelocity < this.config.DROP_VELOCITY) {
this.jumpVelocity = this.config.DROP_VELOCITY;
}
},
/**
* Update frame for a jump.
* @param {number} deltaTime
*/
updateJump: function(deltaTime) {
var msPerFrame = Trex.animFrames[this.status].msPerFrame;
var framesElapsed = deltaTime / msPerFrame;
// Speed drop makes Trex fall faster.
if (this.speedDrop) {
this.yPos += Math.round(this.jumpVelocity *
this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
} else {
this.yPos += Math.round(this.jumpVelocity * framesElapsed);
}
this.jumpVelocity += this.config.GRAVITY * framesElapsed;
// Minimum height has been reached.
if (this.yPos < this.minJumpHeight || this.speedDrop) {
this.reachedMinHeight = true;
}
// Reached max height
if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
this.endJump();
}
// Back down at ground level. Jump completed.
if (this.yPos > this.groundYPos) {
this.reset();
this.jumpCount++;
}
this.update(deltaTime);
},
/**
* Set the speed drop. Immediately cancels the current jump.
*/
setSpeedDrop: function() {
this.speedDrop = true;
this.jumpVelocity = 1;
},
/**
* Reset the t-rex to running at start of game.
*/
reset: function() {
this.yPos = this.groundYPos;
this.jumpVelocity = 0;
this.jumping = false;
this.update(0, Trex.status.RUNNING);
this.midair = false;
this.speedDrop = false;
this.jumpCount = 0;
}
};
//******************************************************************************
/**
* Handles displaying the distance meter.
* @param {!HTMLCanvasElement} canvas
* @param {!HTMLImage} spriteSheet Image sprite.
* @param {number} canvasWidth
* @constructor
*/
function DistanceMeter(canvas, spriteSheet, canvasWidth) {
this.canvas = canvas;
this.canvasCtx = canvas.getContext('2d');
this.image = spriteSheet;
this.x = 0;
this.y = 5;
this.currentDistance = 0;
this.maxScore = 0;
this.highScore = 0;
this.container = null;
this.digits = [];
this.acheivement = false;
this.defaultString = '';
this.flashTimer = 0;
this.flashIterations = 0;
this.config = DistanceMeter.config;
this.init(canvasWidth);
};
/**
* @enum {number}
*/
DistanceMeter.dimensions = {
WIDTH: 10,
HEIGHT: 13,
DEST_WIDTH: 11
};
/**
* Y positioning of the digits in the sprite sheet.
* X position is always 0.
* @type {array.}
*/
DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
/**
* Distance meter config.
* @enum {number}
*/
DistanceMeter.config = {
// Number of digits.
MAX_DISTANCE_UNITS: 5,
// Distance that causes achievement animation.
ACHIEVEMENT_DISTANCE: 100,
// Used for conversion from pixel distance to a scaled unit.
COEFFICIENT: 0.025,
// Flash duration in milliseconds.
FLASH_DURATION: 1000 / 4,
// Flash iterations for achievement animation.
FLASH_ITERATIONS: 3
};
DistanceMeter.prototype = {
/**
* Initialise the distance meter to '00000'.
* @param {number} width Canvas width in px.
*/
init: function(width) {
var maxDistanceStr = '';
this.calcXPos(width);
this.maxScore = this.config.MAX_DISTANCE_UNITS;
for (var i = 0; i < this.config.MAX_DISTANCE_UNITS; i++) {
this.draw(i, 0);
this.defaultString += '0';
maxDistanceStr += '9';
}
this.maxScore = parseInt(maxDistanceStr);
},
/**
* Calculate the xPos in the canvas.
* @param {number} canvasWidth
*/
calcXPos: function(canvasWidth) {
this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
(this.config.MAX_DISTANCE_UNITS + 1));
},
/**
* Draw a digit to canvas.
* @param {number} digitPos Position of the digit.
* @param {number} value Digit value 0-9.
* @param {boolean} opt_highScore Whether drawing the high score.
*/
draw: function(digitPos, value, opt_highScore) {
var sourceWidth = DistanceMeter.dimensions.WIDTH;
var sourceHeight = DistanceMeter.dimensions.HEIGHT;
var sourceX = DistanceMeter.dimensions.WIDTH * value;
var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
var targetY = this.y;
var targetWidth = DistanceMeter.dimensions.WIDTH;
var targetHeight = DistanceMeter.dimensions.HEIGHT;
// For high DPI we 2x source values.
if (IS_HIDPI) {
sourceWidth *= 2;
sourceHeight *= 2;
sourceX *= 2;
}
this.canvasCtx.save();
if (opt_highScore) {
// Left of the current score.
var highScoreX = this.x - (this.config.MAX_DISTANCE_UNITS * 2) *
DistanceMeter.dimensions.WIDTH;
this.canvasCtx.translate(highScoreX, this.y);
} else {
this.canvasCtx.translate(this.x, this.y);
}
this.canvasCtx.drawImage(this.image, sourceX, 0,
sourceWidth, sourceHeight,
targetX, targetY,
targetWidth, targetHeight
);
this.canvasCtx.restore();
},
/**
* Covert pixel distance to a 'real' distance.
* @param {number} distance Pixel distance ran.
* @return {number} The 'real' distance ran.
*/
getActualDistance: function(distance) {
return distance ?
Math.round(distance * this.config.COEFFICIENT) : 0;
},
/**
* Update the distance meter.
* @param {number} deltaTime
* @param {number} distance
* @return {boolean} Whether the acheivement sound fx should be played.
*/
update: function(deltaTime, distance) {
var paint = true;
var playSound = false;
if (!this.acheivement) {
distance = this.getActualDistance(distance);
if (distance > 0) {
// Acheivement unlocked
if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
// Flash score and play sound.
this.acheivement = true;
this.flashTimer = 0;
playSound = true;
}
// Create a string representation of the distance with leading 0.
var distanceStr = (this.defaultString +
distance).substr(-this.config.MAX_DISTANCE_UNITS);
this.digits = distanceStr.split('');
} else {
this.digits = this.defaultString.split('');
}
} else {
// Control flashing of the score on reaching acheivement.
if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
this.flashTimer += deltaTime;
if (this.flashTimer < this.config.FLASH_DURATION) {
paint = false;
} else if (this.flashTimer >
this.config.FLASH_DURATION * 2) {
this.flashTimer = 0;
this.flashIterations++;
}
} else {
this.acheivement = false;
this.flashIterations = 0;
this.flashTimer = 0;
}
}
// Draw the digits if not flashing.
if (paint) {
for (var i = this.digits.length - 1; i >= 0; i--) {
this.draw(i, parseInt(this.digits[i]));
}
}
this.drawHighScore();
return playSound;
},
/**
* Draw the high score.
*/
drawHighScore: function() {
this.canvasCtx.save();
this.canvasCtx.globalAlpha = .8;
for (var i = this.highScore.length - 1; i >= 0; i--) {
this.draw(i, parseInt(this.highScore[i], 10), true);
}
this.canvasCtx.restore();
},
/**
* Set the highscore as a array string.
* Position of char in the sprite: H - 10, I - 11.
* @param {number} distance Distance ran in pixels.
*/
setHighScore: function(distance) {
distance = this.getActualDistance(distance);
var highScoreStr = (this.defaultString +
distance).substr(-this.config.MAX_DISTANCE_UNITS);
this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
},
/**
* Reset the distance meter back to '00000'.
*/
reset: function() {
this.update(0);
this.acheivement = false;
}
};
//******************************************************************************
/**
* Cloud background item.
* Similar to an obstacle object but without collision boxes.
* @param {HTMLCanvasElement} canvas Canvas element.
* @param {Image} cloudImg
* @param {number} containerWidth
*/
function Cloud(canvas, cloudImg, containerWidth) {
this.canvas = canvas;
this.canvasCtx = this.canvas.getContext('2d');
this.image = cloudImg;
this.containerWidth = containerWidth;
this.xPos = containerWidth;
this.yPos = 0;
this.remove = false;
this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,
Cloud.config.MAX_CLOUD_GAP);
this.init();
};
/**
* Cloud object config.
* @enum {number}
*/
Cloud.config = {
HEIGHT: 14,
MAX_CLOUD_GAP: 400,
MAX_SKY_LEVEL: 30,
MIN_CLOUD_GAP: 100,
MIN_SKY_LEVEL: 71,
WIDTH: 46
};
Cloud.prototype = {
/**
* Initialise the cloud. Sets the Cloud height.
*/
init: function() {
this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
Cloud.config.MIN_SKY_LEVEL);
this.draw();
},
/**
* Draw the cloud.
*/
draw: function() {
this.canvasCtx.save();
var sourceWidth = Cloud.config.WIDTH;
var sourceHeight = Cloud.config.HEIGHT;
if (IS_HIDPI) {
sourceWidth = sourceWidth * 2;
sourceHeight = sourceHeight * 2;
}
this.canvasCtx.drawImage(this.image, 0, 0,
sourceWidth, sourceHeight,
this.xPos, this.yPos,
Cloud.config.WIDTH, Cloud.config.HEIGHT);
this.canvasCtx.restore();
},
/**
* Update the cloud position.
* @param {number} speed
*/
update: function(speed) {
if (!this.remove) {
this.xPos -= Math.ceil(speed);
this.draw();
// Mark as removeable if no longer in the canvas.
if (!this.isVisible()) {
this.remove = true;
}
}
},
/**
* Check if the cloud is visible on the stage.
* @return {boolean}
*/
isVisible: function() {
return this.xPos + Cloud.config.WIDTH > 0;
}
};
//******************************************************************************
/**
* Horizon Line.
* Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
* @param {HTMLCanvasElement} canvas
* @param {HTMLImage} bgImg Horizon line sprite.
* @constructor
*/
function HorizonLine(canvas, bgImg) {
this.image = bgImg;
this.canvas = canvas;
this.canvasCtx = canvas.getContext('2d');
this.sourceDimensions = {};
this.dimensions = HorizonLine.dimensions;
this.sourceXPos = [0, this.dimensions.WIDTH];
this.xPos = [];
this.yPos = 0;
this.bumpThreshold = 0.5;
this.setSourceDimensions();
this.draw();
};
/**
* Horizon line dimensions.
* @enum {number}
*/
HorizonLine.dimensions = {
WIDTH: 600,
HEIGHT: 12,
YPOS: 127
};
HorizonLine.prototype = {
/**
* Set the source dimensions of the horizon line.
*/
setSourceDimensions: function() {
for (var dimension in HorizonLine.dimensions) {
if (IS_HIDPI) {
if (dimension != 'YPOS') {
this.sourceDimensions[dimension] =
HorizonLine.dimensions[dimension] * 2;
}
} else {
this.sourceDimensions[dimension] =
HorizonLine.dimensions[dimension];
}
this.dimensions[dimension] = HorizonLine.dimensions[dimension];
}
this.xPos = [0, HorizonLine.dimensions.WIDTH];
this.yPos = HorizonLine.dimensions.YPOS;
},
/**
* Return the crop x position of a type.
*/
getRandomType: function() {
return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
},
/**
* Draw the horizon line.
*/
draw: function() {
this.canvasCtx.drawImage(this.image, this.sourceXPos[0], 0,
this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
this.xPos[0], this.yPos,
this.dimensions.WIDTH, this.dimensions.HEIGHT);
this.canvasCtx.drawImage(this.image, this.sourceXPos[1], 0,
this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
this.xPos[1], this.yPos,
this.dimensions.WIDTH, this.dimensions.HEIGHT);
},
/**
* Update the x position of an indivdual piece of the line.
* @param {number} pos Line position.
* @param {number} increment
*/
updateXPos: function(pos, increment) {
var line1 = pos;
var line2 = pos == 0 ? 1 : 0;
this.xPos[line1] -= increment;
this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
if (this.xPos[line1] <= -this.dimensions.WIDTH) {
this.xPos[line1] += this.dimensions.WIDTH * 2;
this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
this.sourceXPos[line1] = this.getRandomType();
}
},
/**
* Update the horizon line.
* @param {number} deltaTime
* @param {number} speed
*/
update: function(deltaTime, speed) {
var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
if (this.xPos[0] <= 0) {
this.updateXPos(0, increment);
} else {
this.updateXPos(1, increment);
}
this.draw();
},
/**
* Reset horizon to the starting position.
*/
reset: function() {
this.xPos[0] = 0;
this.xPos[1] = HorizonLine.dimensions.WIDTH;
}
};
//******************************************************************************
/**
* Horizon background class.
* @param {HTMLCanvasElement} canvas
* @param {Array.} images
* @param {object} dimensions Canvas dimensions.
* @param {number} gapCoefficient
* @constructor
*/
function Horizon(canvas, images, dimensions, gapCoefficient) {
this.canvas = canvas;
this.canvasCtx = this.canvas.getContext('2d');
this.config = Horizon.config;
this.dimensions = dimensions;
this.gapCoefficient = gapCoefficient;
this.obstacles = [];
this.horizonOffsets = [0, 0];
this.cloudFrequency = this.config.CLOUD_FREQUENCY;
// Cloud
this.clouds = [];
this.cloudImg = images.CLOUD;
this.cloudSpeed = this.config.BG_CLOUD_SPEED;
// Horizon
this.horizonImg = images.HORIZON;
this.horizonLine = null;
// Obstacles
this.obstacleImgs = {
CACTUS_SMALL: images.CACTUS_SMALL,
CACTUS_LARGE: images.CACTUS_LARGE
};
this.init();
};
/**
* Horizon config.
* @enum {number}
*/
Horizon.config = {
BG_CLOUD_SPEED: 0.2,
BUMPY_THRESHOLD: .3,
CLOUD_FREQUENCY: .5,
HORIZON_HEIGHT: 16,
MAX_CLOUDS: 6
};
Horizon.prototype = {
/**
* Initialise the horizon. Just add the line and a cloud. No obstacles.
*/
init: function() {
this.addCloud();
this.horizonLine = new HorizonLine(this.canvas, this.horizonImg);
},
/**
* @param {number} deltaTime
* @param {number} currentSpeed
* @param {boolean} updateObstacles Used as an override to prevent
* the obstacles from being updated / added. This happens in the
* ease in section.
*/
update: function(deltaTime, currentSpeed, updateObstacles) {
this.runningTime += deltaTime;
this.horizonLine.update(deltaTime, currentSpeed);
this.updateClouds(deltaTime, currentSpeed);
if (updateObstacles) {
this.updateObstacles(deltaTime, currentSpeed);
}
},
/**
* Update the cloud positions.
* @param {number} deltaTime
* @param {number} currentSpeed
*/
updateClouds: function(deltaTime, speed) {
var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
var numClouds = this.clouds.length;
if (numClouds) {
for (var i = numClouds - 1; i >= 0; i--) {
this.clouds[i].update(cloudSpeed);
}
var lastCloud = this.clouds[numClouds - 1];
// Check for adding a new cloud.
if (numClouds < this.config.MAX_CLOUDS &&
(this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
this.cloudFrequency > Math.random()) {
this.addCloud();
}
// Remove expired clouds.
this.clouds = this.clouds.filter(function(obj) {
return !obj.remove;
});
}
},
/**
* Update the obstacle positions.
* @param {number} deltaTime
* @param {number} currentSpeed
*/
updateObstacles: function(deltaTime, currentSpeed) {
// Obstacles, move to Horizon layer.
var updatedObstacles = this.obstacles.slice(0);
for (var i = 0; i < this.obstacles.length; i++) {
var obstacle = this.obstacles[i];
obstacle.update(deltaTime, currentSpeed);
// Clean up existing obstacles.
if (obstacle.remove) {
updatedObstacles.shift();
}
}
this.obstacles = updatedObstacles;
if (this.obstacles.length > 0) {
var lastObstacle = this.obstacles[this.obstacles.length - 1];
if (lastObstacle && !lastObstacle.followingObstacleCreated &&
lastObstacle.isVisible() &&
(lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
this.dimensions.WIDTH) {
this.addNewObstacle(currentSpeed);
lastObstacle.followingObstacleCreated = true;
}
} else {
// Create new obstacles.
this.addNewObstacle(currentSpeed);
}
},
/**
* Add a new obstacle.
* @param {number} currentSpeed
*/
addNewObstacle: function(currentSpeed) {
var obstacleTypeIndex =
getRandomNum(0, Obstacle.types.length - 1);
var obstacleType = Obstacle.types[obstacleTypeIndex];
var obstacleImg = this.obstacleImgs[obstacleType.type];
this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType,
obstacleImg, this.dimensions, this.gapCoefficient, currentSpeed));
},
/**
* Reset the horizon layer.
* Remove existing obstacles and reposition the horizon line.
*/
reset: function() {
this.obstacles = [];
this.horizonLine.reset();
},
/**
* Update the canvas width and scaling.
* @param {number} width Canvas width.
* @param {number} height Canvas height.
*/
resize: function(width, height) {
this.canvas.width = width;
this.canvas.height = height;
},
/**
* Add a new cloud to the horizon.
*/
addCloud: function() {
this.clouds.push(new Cloud(this.canvas, this.cloudImg,
this.dimensions.WIDTH));
}
};
})();
-->