diff --git a/js/Game.js b/js/Game.js index bb58bd2..a5c03f6 100644 --- a/js/Game.js +++ b/js/Game.js @@ -74,7 +74,7 @@ export class Game for (const effect of this.level.fullscreenEffects) { effect.update(timestamp); - effect.render(this.context); + effect.render(this.context, this.camera); } this.userInterface.draw(this.context); @@ -238,6 +238,10 @@ export class Game this.isPaused = false; + for (const effect of this.level.fullscreenEffects) { + effect.init(); + } + window.requestAnimationFrame(loopFunction); } diff --git a/js/effects/FullscreenEffect.js b/js/effects/FullscreenEffect.js index 66ab38a..35b3a26 100644 --- a/js/effects/FullscreenEffect.js +++ b/js/effects/FullscreenEffect.js @@ -7,11 +7,15 @@ export class FullscreenEffect this.canvas = canvas; } + init() + { + } + update(timestamp) { } - render(context) + render(context, camera) { } diff --git a/js/effects/FullscreenEffectFactory.js b/js/effects/FullscreenEffectFactory.js index a4f8fc9..e813e77 100644 --- a/js/effects/FullscreenEffectFactory.js +++ b/js/effects/FullscreenEffectFactory.js @@ -1,9 +1,13 @@ import {SnowEffect} from "./SnowEffect.js"; +import {RainEffect} from "./RainEffect.js"; +import {ThunderstormEffect} from "./ThunderstormEffect.js"; export class FullscreenEffectFactory { static EFFECTS = { [SnowEffect.NAME]: SnowEffect, + [RainEffect.NAME]: RainEffect, + [ThunderstormEffect.NAME]: ThunderstormEffect, } getEffect(name) @@ -16,7 +20,6 @@ export class FullscreenEffectFactory const names = []; for (const name in FullscreenEffectFactory.EFFECTS) { - console.log(name); names.push(name); } diff --git a/js/effects/RainEffect.js b/js/effects/RainEffect.js new file mode 100644 index 0000000..c344e96 --- /dev/null +++ b/js/effects/RainEffect.js @@ -0,0 +1,51 @@ +import {FullscreenEffect} from "./FullscreenEffect.js"; + +export class RainEffect extends FullscreenEffect +{ + static NAME = 'rain'; + + constructor() + { + super(); + + this.image = new Image(); + this.image.src = 'js/effects/rain.png'; + this.sound = new Audio('js/effects/rain.mp3'); + this.sound.loop = true; + this.offset = 0; + this.speed = 1.3; + } + + init() + { + super.init(); + + this.sound.play(); + } + + update(timestamp) + { + super.update(timestamp); + + this.offsetX = (timestamp * 0.65 * this.speed) % this.image.width; + this.offsetY = (timestamp * 1.5 * this.speed) % this.image.height; + } + + render(context, camera) + { + super.render(context, camera); + + const cameraX = -1 * (camera.position.x % this.image.width); + const cameraY = -1 * (camera.position.y % this.image.height); + + for (let y = -1; y < Math.ceil(context.canvas.height / this.image.height) + 1; y++) { + for (let x = -1; x < Math.ceil(context.canvas.width / this.image.width) + 1; x++) { + context.drawImage( + this.image, + cameraX + this.offsetX + this.image.width * x, + cameraY + this.offsetY + this.image.height * y, + ); + } + } + } +} diff --git a/js/effects/SnowEffect.js b/js/effects/SnowEffect.js index 813c633..f1b64d9 100644 --- a/js/effects/SnowEffect.js +++ b/js/effects/SnowEffect.js @@ -7,24 +7,44 @@ export class SnowEffect extends FullscreenEffect constructor() { super(); + + this.audio = new Audio('js/effects/storm.mp3'); + this.audio.loop = true; this.image = new Image(); this.image.src = 'js/effects/snow.png'; this.offset = 0; + this.speed = 1.0; + } + + init() + { + super.init(); + + this.audio.play(); } update(timestamp) { super.update(timestamp); - this.offset = timestamp % 512; + + this.offsetX = (timestamp * 0.65 * this.speed) % this.image.width; + this.offsetY = (timestamp * 1.5 * this.speed) % this.image.height; } - render(context) + render(context, camera) { super.render(context); - for (let y = -1; y < Math.ceil(context.canvas.height / 512); y++) { - for (let x = -1; x < Math.ceil(context.canvas.width / 512); x++) { - context.drawImage(this.image, this.offset + 512 * x, this.offset + 512 * y); + const cameraX = -1 * (camera.position.x % this.image.width); + const cameraY = -1 * (camera.position.y % this.image.height); + + for (let y = -1; y < Math.ceil(context.canvas.height / this.image.height) + 1; y++) { + for (let x = -1; x < Math.ceil(context.canvas.width / this.image.width) + 1; x++) { + context.drawImage( + this.image, + cameraX + this.offsetX + this.image.width * x, + cameraY + this.offsetY + this.image.height * y, + ); } } } diff --git a/js/effects/ThunderstormEffect.js b/js/effects/ThunderstormEffect.js new file mode 100644 index 0000000..3873830 --- /dev/null +++ b/js/effects/ThunderstormEffect.js @@ -0,0 +1,88 @@ +import {FullscreenEffect} from "./FullscreenEffect.js"; + +export class ThunderstormEffect extends FullscreenEffect +{ + static NAME = 'thunderstorm'; + + constructor() + { + super(); + + this.currentAlpha = 0.0; + this.nextFlash = undefined; + this.duration = 150; + this.sounds = [ + new Audio('js/effects/thunder01.mp3'), + new Audio('js/effects/thunder02.mp3'), + new Audio('js/effects/thunder03.mp3'), + new Audio('js/effects/thunder04.mp3'), + ]; + this.currentSound = 0; + } + + shuffleSounds(iterations = 10) + { + const buffer = this.sounds.slice(0, this.sounds.length - 1); + + for (let i = 0; i < iterations; i++) { + buffer.sort( + (a, b) => { + return 1 - Math.round(Math.random()) * 2 + } + ); + } + + const index = 1 + Math.round(Math.random() * (buffer.length - 1)); + + this.sounds = buffer.slice(0, index) + .concat([this.sounds[this.sounds.length - 1]]) + .concat(buffer.slice(index)); + } + + update(timestamp) + { + super.update(timestamp); + + if (this.nextFlash > timestamp) { + this.currentAlpha = 0.0; + + return; + } + + if (timestamp < this.nextFlash + this.duration) { + const progress = (timestamp - this.nextFlash) / this.duration; + + this.currentAlpha = Math.sin(Math.PI * progress) * 0.75; + } else { + const duration = 3000 + Math.round(Math.random() * 27000); + + this.nextFlash = timestamp + duration; + + setTimeout( + () => { + if (this.currentSound === 0) { + this.shuffleSounds(); + } + + this.sounds[this.currentSound].currentTime = 0; + this.sounds[this.currentSound].play(); + + this.currentSound = (this.currentSound + 1) % this.sounds.length; + }, + duration + 1000 + ); + } + } + + render(context, camera) + { + super.render(context, camera); + + if (this.currentAlpha === 0.0) { + return; + } + + context.fillStyle = 'rgba(255, 255, 255, ' + this.currentAlpha + ')'; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + } +} diff --git a/js/effects/rain.mp3 b/js/effects/rain.mp3 new file mode 100644 index 0000000..37e3065 Binary files /dev/null and b/js/effects/rain.mp3 differ diff --git a/js/effects/rain.png b/js/effects/rain.png new file mode 100644 index 0000000..4d01804 Binary files /dev/null and b/js/effects/rain.png differ diff --git a/js/effects/storm.mp3 b/js/effects/storm.mp3 new file mode 100644 index 0000000..7d83c35 Binary files /dev/null and b/js/effects/storm.mp3 differ diff --git a/js/effects/thunder01.mp3 b/js/effects/thunder01.mp3 new file mode 100644 index 0000000..c06d22c Binary files /dev/null and b/js/effects/thunder01.mp3 differ diff --git a/js/effects/thunder02.mp3 b/js/effects/thunder02.mp3 new file mode 100644 index 0000000..d90db0e Binary files /dev/null and b/js/effects/thunder02.mp3 differ diff --git a/js/effects/thunder03.mp3 b/js/effects/thunder03.mp3 new file mode 100644 index 0000000..5a6d41a Binary files /dev/null and b/js/effects/thunder03.mp3 differ diff --git a/js/effects/thunder04.mp3 b/js/effects/thunder04.mp3 new file mode 100644 index 0000000..ecd2b5a Binary files /dev/null and b/js/effects/thunder04.mp3 differ diff --git a/tilorswift/js/Tilorswift.js b/tilorswift/js/Tilorswift.js index 267134f..b52553f 100644 --- a/tilorswift/js/Tilorswift.js +++ b/tilorswift/js/Tilorswift.js @@ -30,12 +30,16 @@ import TilorswiftSavedEvent from "./events/TilorswiftSavedEvent.js"; import Level from "../../js/Level.js"; import {IntelligentBrushSwitch} from "./menu/IntelligentBrushSwitch.js"; import {SnowEffect} from "../../js/effects/SnowEffect.js"; +import {RainEffect} from "../../js/effects/RainEffect.js"; +import {ThunderstormEffect} from "../../js/effects/ThunderstormEffect.js"; import {FullscreenEffectFactory} from "../../js/effects/FullscreenEffectFactory.js"; export default class Tilorswift { static EFFECT_NAMES = { [SnowEffect.NAME]: 'Schnee', + [RainEffect.NAME]: 'Regen', + [ThunderstormEffect.NAME]: 'Gewitter', } constructor(level) { @@ -320,9 +324,7 @@ export default class Tilorswift new DialogEffects( FullscreenEffectFactory.getNames(), effects, - { - [SnowEffect.NAME]: 'Schnee', - } + Tilorswift.EFFECT_NAMES, ); } );