|
|
@ -4,38 +4,323 @@ |
|
|
|
|
|
|
|
'use strict'; |
|
|
|
|
|
|
|
import NiceImage from 'dtp-nice-game/lib/nice-image.js'; |
|
|
|
import { NiceEasing, NiceSprite, NiceTween, NiceVector2d } from 'dtp-nice-game'; |
|
|
|
|
|
|
|
const CANVAS_WIDTH = 960; |
|
|
|
const CANVAS_HEIGHT = 540; |
|
|
|
const SKY_HEIGHT = 150; |
|
|
|
const WATER_HEIGHT = 150; |
|
|
|
|
|
|
|
/** |
|
|
|
* GameBackground provides an image to use as the backdrop, an update method for |
|
|
|
* updating animated and other dynamic components, and a draw method, which can |
|
|
|
* all be implemented to provide all sorts of dynamic effects on the background. |
|
|
|
* GameBackground is responsible for rendering the background/environment, and |
|
|
|
* it uses a variety of techniques to do that. Gradients are used to render the |
|
|
|
* sky, beach, and ocean. Images are used to render the trees, plants, and |
|
|
|
* foreground base. |
|
|
|
* |
|
|
|
* ...which just happens to be a NiceImage. |
|
|
|
* GameBackground has modes, and can reconfigure itself and animate between |
|
|
|
* them on command using reusable NiceTween instances. It currently supports |
|
|
|
* GameBackground.MODE_STORY and GameBackground.MODE_PLAY. |
|
|
|
*/ |
|
|
|
export default class GameBackground extends NiceImage { |
|
|
|
export default class GameBackground { |
|
|
|
|
|
|
|
static get MODE_STORY ( ) { return 'story'; } |
|
|
|
static get MODE_PLAY ( ) { return 'play'; } |
|
|
|
|
|
|
|
constructor (game) { |
|
|
|
super(); |
|
|
|
this.game = game; |
|
|
|
this.sprites = { }; |
|
|
|
this.tweens = { }; |
|
|
|
|
|
|
|
this.sun = { |
|
|
|
position: new NiceVector2d(480, 140), |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
async load ( ) { |
|
|
|
await this.loadImages(); |
|
|
|
|
|
|
|
this.buildSprites(); |
|
|
|
this.buildTweens(); |
|
|
|
} |
|
|
|
|
|
|
|
async loadImages ( ) { |
|
|
|
const jobs = [ ]; |
|
|
|
|
|
|
|
jobs.push(this.game.loadImage('bg:foreground-base', '/dist/assets/img/beach/foreground-base.png', 960, 114)); |
|
|
|
jobs.push(this.game.loadImage('bg:waves', '/dist/assets/img/beach/waves.png', 112, 40)); |
|
|
|
|
|
|
|
jobs.push(this.game.loadImage('bg:tree-left', '/dist/assets/img/beach/tree-left.png', 361, 540)); |
|
|
|
jobs.push(this.game.loadImage('bg:tree-right', '/dist/assets/img/beach/tree-right.png', 351, 540)); |
|
|
|
|
|
|
|
jobs.push(this.game.loadImage('bg:plant-left', '/dist/assets/img/beach/plant-left.png', 148, 303)); |
|
|
|
jobs.push(this.game.loadImage('bg:plant-right', '/dist/assets/img/beach/plant-right.png', 170, 211)); |
|
|
|
|
|
|
|
await Promise.all(jobs); |
|
|
|
} |
|
|
|
|
|
|
|
update ( ) { |
|
|
|
buildSprites ( ) { |
|
|
|
this.sprites.foreground = new NiceSprite(this.game, new NiceVector2d(CANVAS_WIDTH / 2, CANVAS_HEIGHT)); |
|
|
|
this.sprites.foreground.image = this.game.images['bg:foreground-base']; |
|
|
|
this.sprites.foreground.registration = new NiceVector2d(480, this.sprites.foreground.image.height); |
|
|
|
|
|
|
|
this.sprites.waves1 = new NiceSprite(this.game, new NiceVector2d(CANVAS_WIDTH * 0.8, SKY_HEIGHT + 32)); |
|
|
|
this.sprites.waves1.image = this.game.images['bg:waves']; |
|
|
|
this.sprites.waves1.registration = new NiceVector2d( |
|
|
|
this.sprites.waves1.image.width / 2, |
|
|
|
this.sprites.waves1.image.height / 2, |
|
|
|
); |
|
|
|
|
|
|
|
this.sprites.waves2 = new NiceSprite(this.game, new NiceVector2d(CANVAS_WIDTH * 0.3, SKY_HEIGHT + 48)); |
|
|
|
this.sprites.waves2.image = this.game.images['bg:waves']; |
|
|
|
this.sprites.waves2.registration = new NiceVector2d( |
|
|
|
this.sprites.waves2.image.width / 2, |
|
|
|
this.sprites.waves2.image.height / 2, |
|
|
|
); |
|
|
|
|
|
|
|
this.sprites.treeLeft = new NiceSprite(this.game, new NiceVector2d(0, CANVAS_HEIGHT)); |
|
|
|
this.sprites.treeLeft.image = this.game.images['bg:tree-left']; |
|
|
|
this.sprites.treeLeft.registration = new NiceVector2d(0, this.sprites.treeLeft.image.height); |
|
|
|
|
|
|
|
this.sprites.treeRight = new NiceSprite(this.game, new NiceVector2d(CANVAS_WIDTH, 0)); |
|
|
|
this.sprites.treeRight.image = this.game.images['bg:tree-right']; |
|
|
|
this.sprites.treeRight.registration = new NiceVector2d(this.sprites.treeRight.image.width, 0); |
|
|
|
|
|
|
|
this.sprites.plantLeft = new NiceSprite(this.game, new NiceVector2d(0, CANVAS_HEIGHT)); |
|
|
|
this.sprites.plantLeft.image = this.game.images['bg:plant-left']; |
|
|
|
this.sprites.plantLeft.registration = new NiceVector2d(0, this.sprites.plantLeft.image.height); |
|
|
|
|
|
|
|
this.sprites.plantRight = new NiceSprite(this.game, new NiceVector2d(CANVAS_WIDTH, CANVAS_HEIGHT)); |
|
|
|
this.sprites.plantRight.image = this.game.images['bg:plant-right']; |
|
|
|
this.sprites.plantRight.registration = new NiceVector2d( |
|
|
|
this.sprites.plantRight.image.width, |
|
|
|
this.sprites.plantRight.image.height, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
buildTweens ( ) { |
|
|
|
const FOREGROUND_DURATION = 1000; |
|
|
|
const TREE_DURATION = 1250; |
|
|
|
const PLANT_DURATION = 1500; |
|
|
|
|
|
|
|
/* |
|
|
|
* TODO: dynamic backgrounds that can animate on their own |
|
|
|
* Yes, this is being called now. It just does nothing for now. |
|
|
|
* FOREGROUND BASE |
|
|
|
*/ |
|
|
|
this.tweens.foregroundIn = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.foreground.position, |
|
|
|
{ y: this.game.playfield.height + this.sprites.foreground.image.height}, |
|
|
|
{ y: this.game.playfield.height }, |
|
|
|
NiceEasing.Quadratic.Out, |
|
|
|
); |
|
|
|
this.tweens.foregroundIn.duration(FOREGROUND_DURATION); |
|
|
|
|
|
|
|
this.tweens.foregroundOut = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.foreground.position, |
|
|
|
{ y: this.game.playfield.height }, |
|
|
|
{ y: this.game.playfield.height + this.sprites.foreground.image.height}, |
|
|
|
NiceEasing.Quadratic.In, |
|
|
|
); |
|
|
|
this.tweens.foregroundOut.duration(FOREGROUND_DURATION); |
|
|
|
|
|
|
|
/* |
|
|
|
* TREE LEFT |
|
|
|
*/ |
|
|
|
this.tweens.treeLeftIn = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.treeLeft.position, |
|
|
|
{ x: -this.sprites.treeLeft.image.width }, |
|
|
|
{ x: 0 }, |
|
|
|
NiceEasing.Elastic.Out, |
|
|
|
); |
|
|
|
this.tweens.treeLeftIn.duration(TREE_DURATION); |
|
|
|
|
|
|
|
this.tweens.treeLeftOut = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.treeLeft.position, |
|
|
|
{ x: 0 }, |
|
|
|
{ x: -this.sprites.treeLeft.image.width }, |
|
|
|
NiceEasing.Elastic.In, |
|
|
|
); |
|
|
|
this.tweens.treeLeftOut.duration(TREE_DURATION); |
|
|
|
|
|
|
|
/* |
|
|
|
* TREE RIGHT |
|
|
|
*/ |
|
|
|
this.tweens.treeRightIn = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.treeRight.position, |
|
|
|
{ x: this.game.playfield.width + this.sprites.treeRight.image.width }, |
|
|
|
{ x: this.game.playfield.width }, |
|
|
|
NiceEasing.Elastic.Out, |
|
|
|
); |
|
|
|
this.tweens.treeRightIn.duration(TREE_DURATION); |
|
|
|
|
|
|
|
this.tweens.treeRightOut = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.treeRight.position, |
|
|
|
{ x: this.game.playfield.width }, |
|
|
|
{ x: this.game.playfield.width + this.sprites.treeRight.image.width }, |
|
|
|
NiceEasing.Elastic.In, |
|
|
|
); |
|
|
|
this.tweens.treeRightOut.duration(TREE_DURATION); |
|
|
|
|
|
|
|
/* |
|
|
|
* PLANT LEFT |
|
|
|
*/ |
|
|
|
this.tweens.plantLeftIn = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.plantLeft.position, |
|
|
|
{ x: -this.sprites.plantLeft.image.width }, |
|
|
|
{ x: 0 }, |
|
|
|
NiceEasing.Elastic.Out, |
|
|
|
); |
|
|
|
this.tweens.plantLeftIn.duration(PLANT_DURATION); |
|
|
|
|
|
|
|
this.tweens.plantLeftOut = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.plantLeft.position, |
|
|
|
{ x: 0 }, |
|
|
|
{ x: -this.sprites.plantLeft.image.width }, |
|
|
|
NiceEasing.Elastic.In, |
|
|
|
); |
|
|
|
this.tweens.plantLeftOut.duration(PLANT_DURATION); |
|
|
|
|
|
|
|
/* |
|
|
|
* PLANT RIGHT |
|
|
|
*/ |
|
|
|
this.tweens.plantRightIn = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.plantRight.position, |
|
|
|
{ x: this.game.playfield.width + this.sprites.plantRight.width }, |
|
|
|
{ x: this.game.playfield.width }, |
|
|
|
NiceEasing.Elastic.Out, |
|
|
|
); |
|
|
|
this.tweens.plantRightIn.duration(PLANT_DURATION); |
|
|
|
|
|
|
|
this.tweens.plantRightOut = new NiceTween( |
|
|
|
this.game, |
|
|
|
this.sprites.plantRight.position, |
|
|
|
{ x: this.game.playfield.width }, |
|
|
|
{ x: this.game.playfield.width + this.sprites.plantRight.width }, |
|
|
|
NiceEasing.Elastic.In, |
|
|
|
); |
|
|
|
this.tweens.plantRightOut.duration(PLANT_DURATION); |
|
|
|
|
|
|
|
/* |
|
|
|
* WAVES |
|
|
|
*/ |
|
|
|
this.tweens.waves1 = this.game |
|
|
|
.createTween( |
|
|
|
this.sprites.waves1, |
|
|
|
{ rotation: (Math.PI * 0.1) }, |
|
|
|
{ rotation: -(Math.PI * 0.1) }, |
|
|
|
) |
|
|
|
.duration(3000) |
|
|
|
.loop() |
|
|
|
.run(); |
|
|
|
} |
|
|
|
|
|
|
|
setMode (mode) { |
|
|
|
this.mode = mode; |
|
|
|
switch (this.mode) { |
|
|
|
case 'story': |
|
|
|
this.tweens.foregroundIn.run(); |
|
|
|
this.tweens.treeLeftIn.run(); |
|
|
|
this.tweens.treeRightIn.run(); |
|
|
|
this.tweens.plantLeftIn.run(); |
|
|
|
this.tweens.plantRightIn.run(); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'play': |
|
|
|
this.tweens.foregroundOut.run(); |
|
|
|
this.tweens.treeLeftOut.run(); |
|
|
|
this.tweens.treeRightOut.run(); |
|
|
|
this.tweens.plantLeftOut.run(); |
|
|
|
this.tweens.plantRightOut.run(); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
draw (ctx, x, y) { |
|
|
|
super.draw(ctx, x, y); |
|
|
|
update (elapsed, now) { |
|
|
|
for (const tweenKey in this.tweens) { |
|
|
|
const tween = this.tweens[tweenKey]; |
|
|
|
if (tween.state !== 'running') { |
|
|
|
continue; |
|
|
|
} |
|
|
|
tween.update(elapsed, now); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
render (ctx) { |
|
|
|
/* |
|
|
|
* Free to render anything we want over that here, and I will... |
|
|
|
* |
|
|
|
* Yes, this is being called now. I'm just not adding anything, yet. |
|
|
|
* |
|
|
|
* Think rain, snowflakes, moving water, clouds, and maybe "live events" |
|
|
|
* Gradient Fills |
|
|
|
*/ |
|
|
|
if (!this.skyGradient) { |
|
|
|
this.skyGradient = ctx.createLinearGradient(0, 0, 0, SKY_HEIGHT); |
|
|
|
this.skyGradient.addColorStop(0.0, '#bdd5b3'); |
|
|
|
this.skyGradient.addColorStop(1.0, '#f0f2dc'); |
|
|
|
} |
|
|
|
ctx.fillStyle = this.skyGradient; |
|
|
|
ctx.fillRect(0, 0, CANVAS_WIDTH, SKY_HEIGHT); |
|
|
|
|
|
|
|
if (!this.beachGradient) { |
|
|
|
this.beachGradient = ctx.createLinearGradient(0, SKY_HEIGHT, 0, CANVAS_HEIGHT); |
|
|
|
this.beachGradient.addColorStop(0.0, '#e0a47f'); |
|
|
|
this.beachGradient.addColorStop(1.0, '#f6eab6'); |
|
|
|
} |
|
|
|
ctx.fillStyle = this.beachGradient; |
|
|
|
ctx.fillRect(0, SKY_HEIGHT, CANVAS_WIDTH, CANVAS_HEIGHT - SKY_HEIGHT); |
|
|
|
|
|
|
|
this.drawSun(ctx); |
|
|
|
|
|
|
|
if (!this.oceanGradient) { |
|
|
|
this.oceanGradient = ctx.createLinearGradient(0, SKY_HEIGHT, 0, SKY_HEIGHT + WATER_HEIGHT); |
|
|
|
this.oceanGradient.addColorStop(0.0, '#72b5ae'); |
|
|
|
this.oceanGradient.addColorStop(1.0, '#edeec800'); |
|
|
|
} |
|
|
|
ctx.fillStyle = this.oceanGradient; |
|
|
|
ctx.fillRect(0, SKY_HEIGHT, CANVAS_WIDTH, WATER_HEIGHT); |
|
|
|
|
|
|
|
/* |
|
|
|
* Image/sprite overlays |
|
|
|
*/ |
|
|
|
for (const spriteKey in this.sprites) { |
|
|
|
const sprite = this.sprites[spriteKey]; |
|
|
|
sprite.render(ctx); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
drawSun (ctx) { |
|
|
|
ctx.beginPath(); |
|
|
|
ctx.ellipse( |
|
|
|
this.sun.position.x, |
|
|
|
this.sun.position.y, |
|
|
|
32, |
|
|
|
32, |
|
|
|
0, |
|
|
|
0, |
|
|
|
Math.PI * 2, |
|
|
|
); |
|
|
|
|
|
|
|
if (!this.sun.gradient) { |
|
|
|
this.sun.gradient = ctx.createRadialGradient( |
|
|
|
this.sun.position.x, |
|
|
|
this.sun.position.y, |
|
|
|
32, |
|
|
|
this.sun.position.x + 16, |
|
|
|
this.sun.position.y - 16, |
|
|
|
8 |
|
|
|
); |
|
|
|
this.sun.gradient.addColorStop(0.0, '#e0a579'); |
|
|
|
this.sun.gradient.addColorStop(1.0, '#eabf8a'); |
|
|
|
} |
|
|
|
ctx.fillStyle = this.sun.gradient; |
|
|
|
|
|
|
|
ctx.lineWidth = 3.0; |
|
|
|
ctx.strokeStyle = '#593117'; |
|
|
|
|
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
} |
|
|
|
} |