This commit is contained in:
gltron
2022-04-17 18:31:02 +02:00
commit d04ac22128
82 changed files with 11308 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/fonts/TRON.TTF Normal file

Binary file not shown.

BIN
src/assets/fonts/ka1.ttf Executable file

Binary file not shown.

BIN
src/assets/img/boss/e4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
src/assets/img/boss/q4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
src/assets/img/boss/r4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
src/assets/img/boss/t4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
src/assets/img/custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/img/skyline.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/music/jump.mp3 Normal file

Binary file not shown.

BIN
src/assets/music/music2.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

109
src/index.html Normal file
View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="assets/img/meteor_logo.png" type="image/png">
<link rel="stylesheet" href="style/style.css">
<title>Deep Impact</title>
</head>
<body>
<canvas class="background" id="mainCanvas"></canvas>
<div class="page" id="loading" style="display: none;">
<h1>LOADING</h1>
</div>
<div class="main-container">
<div class="page main-menu" id="main-menu">
<img id="title" src="assets/img/meteor_title.png" alt="Deep Impact">
<div class="title-back-container">
<span class="title-back" id="title-back"></span>
</div>
<div class="level-select" id="menuContainer">
<template id="menuItem">
<button class="level-item">
<img>
<p></p>
</button>
</template>
<template id="menuItemCustom">
<button class="level-item" onclick="customLevel()">
<img src="assets/img/custom.png" alt="Custom Level">
<p>Custom Level</p>
</button>
</template>
</div>
</div>
<div class="page custom-level" id="custom-level" style="display: none;">
<h2>Custom Level</h2>
<h3></h3>
<input type="file" name="Music" id="custom-music">
<select name="Theme" id="custom-theme">
<option value="meteor">Meteor</option>
</select>
<button id="custom-level-launch">Launch</button>
<button id="custom-level-back">Back to Main</button>
</div>
<div class="page game-over" id="game-over" style="display: none;">
<h2></h2>
<h3></h3>
<button id="game-over-back">damn</button>
</div>
<div class="page main-game" id="main-game" style="display: none;">
<div class="main-game">
<canvas class="main-game-canvas" id="gameCanvas"></canvas>
<div class="main-game-title" id="main-game-title">
<h1></h1>
<h2></h2>
</div>
<div class="main-game-skyline-container">
<div class="main-game-skyline"></div>
<img class="main-game-skyline-turret" src="assets/img/skyline_turret2.png">
<div class="main-game-skyline"></div>
</div>
<div class="main-game-dashboard">
<div class="main-game-dashboard-inner">
<h2 id="health">HP: -1</h2>
<div class="main-game-dashboard-bullet" id="ammo-laser">
<h4>A</h4>
<img src="assets/img/turrets/bullet_laser.png" alt="Laser">
<h3>100</h3>
</div>
<div class="main-game-dashboard-bullet" id="ammo-fast">
<h4>Z</h4>
<img src="assets/img/turrets/bullet_fast.png" alt="Fast">
<h3>100</h3>
</div>
<div class="main-game-dashboard-bullet" id="ammo-standard">
<h4>E</h4>
<img src="assets/img/turrets/bullet_standard.png" alt="Standard">
<h3>100</h3>
</div>
<div class="main-game-dashboard-bullet" id="ammo-heavy">
<h4>R</h4>
<img src="assets/img/turrets/bullet_heavy.png" alt="Heavy">
<h3>100</h3>
</div>
<input type="range" id="volume" name="volume" value="5" min="0" max="10">
</div>
</div>
</div>
</div>
</div>
<script type="module" src="scripts/index.js"></script>
</body>
</html>

258
src/scripts/game.js Normal file
View File

@@ -0,0 +1,258 @@
import { levelList } from "./levelList.js"
import Player from "./player.js"
import { Bullet, Meteor, Turret } from "./gameObjects.js"
import { Renderer } from "./renderer.js"
import { setFastTurret, setHeavyTurret, setLaserTurret, setStandardTurret } from "./turretTypes.js"
import SoundEffect from "./soundEffect.js"
export default class Game {
constructor() {
this.canvas = document.getElementById("mainCanvas")
this.renderer = new Renderer(this.canvas)
this.player = new Player()
this.levelList = levelList
this.currentLevel;
this.nextTurretToFire = 0
this.turretFire = false
this.health = 10
this.healthDisplay = document.getElementById("health")
this.showTitle = false
this.lastSlowUpdate = 0
this.ammoCurrent = ""
this.ammo = {
laser: 100,
fast: 100,
standard: 100,
heavy: 100
}
SoundEffect.setEffectVolume(0.3)
document.addEventListener("keydown", (event) => {
switch(event.key) {
case "p": this.renderer.showStats = !this.renderer.showStats; break
case "o": this.renderer.showCollisionBox = !this.renderer.showCollisionBox; break
case "a": this.setTurretTypeAll("laser"); break
case "z": this.setTurretTypeAll("fast"); break
case "e": this.setTurretTypeAll("standard"); break
case "r": this.setTurretTypeAll("heavy"); break
}
})
}
setGameCanvas(gameCanvas = true) {
let backgroundColor
if (gameCanvas) {
this.canvas = document.getElementById("gameCanvas")
backgroundColor = "#a9cdd2"
} else {
this.canvas = document.getElementById("mainCanvas")
backgroundColor = "#153a3a"
}
this.renderer = new Renderer(this.canvas, backgroundColor)
this.setSize()
}
async loadMainMenu(level) {
await level.loadLevel()
this.player.setMusic(level.musicPath)
this.player.start()
this.currentLevel = level
}
async loadLevel(level) {
console.log('Set current level:', level.title)
this.player.stop()
await level.loadLevel()
await this.setupTurrets()
this.showTitle = true
const mainGameTitle = document.getElementById("main-game-title")
mainGameTitle.querySelector("h1").innerText = level.title
mainGameTitle.querySelector("h2").innerText = level.subtitle
this.setShowTitle(true)
setTimeout(() => this.setShowTitle(false), 10000)
this.player.setMusic(level.musicPath)
this.player.start()
this.canvas.addEventListener("mousemove", (event) => this.setTurretTarget(event))
this.canvas.addEventListener("mousedown", () => this.setTurretFire(true))
this.canvas.addEventListener("mouseup", () => this.setTurretFire(false))
this.canvas.addEventListener("dblclick", () => this.setTurretFire(!this.turretFire))
const volumeInput = document.getElementById("volume")
volumeInput.addEventListener("change", () => {
console.log('volume', volumeInput.value)
this.player.gain.gain.value = volumeInput.value / 10
})
this.setGameOver()
this.player.audio.addEventListener('ended', () => {
this.setGameOver(true)
this.health = 0
})
this.currentLevel = level
this.renderer.bossImg = this.currentLevel.bossImg
this.ammoCurrent = "standard"
this.ammo = {
laser: 100,
fast: 100,
standard: 100,
heavy: 100
}
this.setAmmoCounter()
}
setShowTitle(show = false) {
document.getElementById("main-game-title").style.display = show ? "flex" : "none"
}
setGameOver(win = false) {
const gameOverTitle = document.getElementById("game-over")
gameOverTitle.querySelector("h2").innerText = win ? "YOU HAVE OVERCOME THE ATTACK" : "YOU HAVE PERISHED"
gameOverTitle.querySelector("h3").innerText = win ? "Until next time" : "Not much to do left"
gameOverTitle.querySelector("button").innerText = win ? "yay" : "damn"
}
setSize(height, width) {
this.canvas.width = width ?? window.innerWidth
this.canvas.height = height ?? window.innerHeight
}
async setupTurrets() {
const turretY = this.canvas.height - 155
const firstTurretX = (this.canvas.width - 1500) / 2 + 95
this.renderer.turrets.push(new Turret(firstTurretX, turretY))
this.renderer.turrets.push(new Turret(firstTurretX + 380, turretY - 15))
this.renderer.turrets.push(new Turret(firstTurretX + 835, turretY - 10))
this.renderer.turrets.push(new Turret(firstTurretX + 1265, turretY + 5))
for (const turret of this.renderer.turrets) {
await setStandardTurret(turret)
}
this.ammoCurrent = "standard"
}
setTurretTypeAll(type) {
this.ammoCurrent = type
this.renderer.turrets.forEach((_, i) => this.setTurretType(i, type))
}
async setTurretType(i, type) {
const turret = this.renderer.turrets[i]
switch(type) {
case "standard": await setStandardTurret(turret); break
case "fast": await setFastTurret(turret); break
case "laser": await setLaserTurret(turret); break
case "heavy": await setHeavyTurret(turret); break
}
}
setTurretTarget(event) {
this.renderer.mouseX = event.offsetX
this.renderer.mouseY = event.offsetY
for (const turret of this.renderer.turrets) {
const dx = this.renderer.mouseX - turret.x
const dy = this.renderer.mouseY - turret.y
turret.angle = Math.atan2(dy, dx)
}
}
setTurretFire(fire) {
this.turretFire = fire
this.renderer.turrets.forEach((turret) => turret.animationRunning = fire)
// if (!fire) {
// SoundEffect.play(SoundEffect.turretRelease)
// }
}
setAmmoCounter() {
const ammoLaserCounter = document.getElementById("ammo-laser")
ammoLaserCounter.querySelector("h3").innerText = this.ammo.laser
const ammoFastCounter = document.getElementById("ammo-fast")
ammoFastCounter.querySelector("h3").innerText = this.ammo.fast
const ammoStandardCounter = document.getElementById("ammo-standard")
ammoStandardCounter.querySelector("h3").innerText = this.ammo.standard
const ammoHeavyCounter = document.getElementById("ammo-heavy")
ammoHeavyCounter.querySelector("h3").innerText = this.ammo.heavy
}
spawnBullet() {
if (this.ammo[this.ammoCurrent] <= 0) {
return
}
const turret = this.renderer.turrets[this.nextTurretToFire]
const deltaLastShot = performance.now() - turret.lastShot
if (deltaLastShot > turret.firingRate) {
const angle = turret.angle - ((Math.floor(Math.random() * turret.spread) / 1000) * (Math.random() < 0.5 ? -1 : 1))
this.renderer.bullets.push(new Bullet(turret.x , turret.y, angle, turret.bulletSprite, turret.damage))
turret.lastShot = performance.now()
// SoundEffect.play(turret.sound)
}
this.nextTurretToFire = this.nextTurretToFire < this.renderer.turrets.length - 1 ? this.nextTurretToFire + 1 : 0
}
spawnMeteor() {
// meteors[Math.floor(Math.random() * this.meteors.length)]
this.renderer.meteors.push(new Meteor(this.canvas.width, this.canvas.height, this.currentLevel.maxHealth, this.currentLevel.meteorImg, this.currentLevel.meteorImgList))
}
render() {
// Frames
const now = performance.now()
if ((now - this.renderer.lastFrame) > (1000 / 60)) {
if (this.turretFire) {
this.spawnBullet()
}
const damageTaken = this.renderer.render()
if (damageTaken > 0 && this.currentLevel.title !== "MainMenu") {
SoundEffect.play(SoundEffect.explosionBig)
this.health -= damageTaken
}
this.healthDisplay.innerText = `${this.health} HP`
}
// Slow updates
if ((now - this.lastSlowUpdate) > 1000) {
console.log('SLOW')
if (this.turretFire) {
console.log('SET AMMO', this.ammoCurrent, this.ammo)
this.ammo.laser += this.ammo.laser < 100 ? 5 : 0
this.ammo.fast += this.ammo.fast < 100 ? 5 : 0
this.ammo.standard += this.ammo.standard < 100 ? 5 : 0
this.ammo.heavy += this.ammo.heavy < 100 ? 5 : 0
this.ammo[this.ammoCurrent] -= 6
if (this.ammo[this.ammoCurrent] <= 0) {
this.ammo[this.ammoCurrent] = 0
}
this.setAmmoCounter()
}
this.lastSlowUpdate = now
}
}
}

152
src/scripts/gameObjects.js Normal file
View File

@@ -0,0 +1,152 @@
export class GameObject {
x = 0
y = 0
sizeX = 0
sizeY = 0
heightX = 0
heightY = 0
speed = 0
angle = 0
health = 0
spriteImg = new Image()
isAnimated = false
removeAfterAnimation = false
animationRunning = false
frames = 0
framesCounter = 0
get sprite() {
return this.spriteImg
}
step(tickToSimulate) {
for (let i = 0; i < tickToSimulate; i++) {
this.x = this.x + this.speed * Math.cos(this.angle)
this.y = this.y + this.speed * Math.sin(this.angle)
}
// Improve canvas performances
this.x = Math.floor(this.x)
this.y = Math.floor(this.y)
}
// Crude collision box
collideWith(gameObject) {
const x = this.x - this.sizeX
const y = this.y - this.sizeY
const a = x + this.sizeX * 2
const b = y + this.sizeY * 2
const x1 = gameObject.x - gameObject.sizeX
const y1 = gameObject.y - gameObject.sizeY
const a1 = x1 + gameObject.sizeX * 2
const b1 = y1 + gameObject.sizeY * 2
return !(a < x1 || a1 < x || b < y1 || b1 < y)
}
}
export class Meteor extends GameObject {
constructor(maxX, maxY, maxHealth, meteorSprite, meteorSpriteList) {
super()
const xOrigin = Math.floor(Math.random() * maxX - 10) + 10
const xTarget = Math.floor(Math.random() * maxX - 20) + 20
const dx = xTarget - xOrigin
const dy = maxY
this.angle = Math.atan2(dy, dx)
this.speed = Math.floor(Math.random() * 0.5) + 0.1
this.health = Math.floor(Math.random() * (maxHealth - 1)) + 1
this.x = xOrigin
this.y = 0
this.sizeX = 25
this.sizeY = 25
this.heightX = 256
this.heightY = 256
this.meteorSprite = meteorSprite
this.meteorSpriteList = meteorSpriteList
this.isAnimated = false
this.removeAfterAnimation = false
this.animationRunning = false
this.frames = 4
this.framesCounter = 0
}
get sprite() {
if (this.meteorSprite) {
return this.meteorSprite
} else if (this.health <= 10) {
return this.meteorSpriteList[0]
} else if (this.health <= 20) {
return this.meteorSpriteList[1]
} else if (this.health <= 30) {
return this.meteorSpriteList[2]
} else {
return this.meteorSpriteList[3]
}
}
}
export class Bullet extends GameObject {
constructor(x, y, angle, bulletSprite, damage) {
super()
this.x = x
this.y = y
this.sizeX = 5
this.sizeY = 10
this.heightX = 0
this.heightY = 0
this.spriteImg= bulletSprite
this.speed = 1
this.angle = angle
this.health = 1
this.damage = damage
this.isAnimated = false
this.removeAfterAnimation = false
this.animationRunning = false
this.frames = 0
this.framesCounter = 0
}
}
export class Explosion extends GameObject {
constructor(x, y, explosionSprite) {
super()
this.x = x
this.y = y
this.sizeX = 50
this.sizeY = 50
this.heightX = 256
this.heightY = 256
this.spriteImg= explosionSprite
this.isAnimated = true
this.removeAfterAnimation = true
this.animationRunning = true
this.frames = 32
this.framesCounter = 0
}
}
export class Turret extends GameObject {
constructor(x, y) {
super()
this.x = x
this.y = y
this.sizeX = 25
this.sizeY = 35
this.heightX = 0
this.heightY = 0
this.spriteImg= null
this.bulletSprite = null
this.isAnimated = false
this.removeAfterAnimation = false
this.animationRunning = false
this.frames = 0
this.framesCounter = 0
this.firingRate = 0
this.spread = 0
this.damage = 0
}
}

86
src/scripts/index.js Normal file
View File

@@ -0,0 +1,86 @@
import Game from './game.js'
import { Router } from './router.js'
import { preloadEveryThing } from './preloader.js'
import { mainMenuLevel } from './levelList.js'
const router = new Router()
const titleBack = document.getElementById("title-back")
let game
document.addEventListener('DOMContentLoaded', async () => {
await preloadEveryThing()
init()
})
async function init() {
game = new Game()
router.showPage("mainMenu")
await loadMenu()
mainRenderLoop()
document.getElementById("custom-level-launch").addEventListener("click", () => setCustomLevel())
document.getElementById("custom-level-back").addEventListener("click", () => router.showPage("mainMenu"))
document.getElementById("game-over-back").addEventListener("click", () => router.showPage("mainMenu"))
}
async function mainRenderLoop () {
game.player.analyse()
if (router.currentPage === "mainMenu") {
const valueBass = game.player.getRangeAverageRatio(0 ,10)
const scaleBass = (1.25 - 1) * valueBass
titleBack.style.transform = `scale(${1 + scaleBass}) translateZ(0)`
}
if (game.currentLevel) {
const value = game.player.getRangeAverageRatio(game.currentLevel.startingValue, game.currentLevel.nbValue)
if (value > game.currentLevel.threshold) {
game.spawnMeteor()
}
if (game.health <= 0 && router.currentPage === "mainGame") {
game.player.stop()
game = new Game()
game.setGameCanvas(false)
await game.loadMainMenu(mainMenuLevel)
router.showPage("gameOver")
}
game.render()
}
requestAnimationFrame(mainRenderLoop, game.canvas)
}
async function loadMenu() {
const itemTemplate = document.getElementById("menuItem");
const itemCustomTemplate = document.getElementById("menuItemCustom");
const menuContainer = document.getElementById("menuContainer");
for (const level of game.levelList) {
const menuItem = document.importNode(itemTemplate.content, true)
menuItem.querySelector("img").src = level.bossPath
menuItem.querySelector("p").textContent = level.title
menuItem.querySelector("button").addEventListener("click", () => loadLevel(level))
menuContainer.appendChild(menuItem)
}
const customItem = document.importNode(itemCustomTemplate.content, true)
customItem.querySelector("button").addEventListener("click", () => router.showPage("customLevel"))
menuContainer.appendChild(customItem)
game.setGameCanvas(false)
await game.loadMainMenu(mainMenuLevel)
}
function loadLevel(level) {
router.showPage("mainGame")
game.setGameCanvas(true)
game.loadLevel(level)
}
function setCustomLevel() {
console.log('custom level')
}

32
src/scripts/level.js Normal file
View File

@@ -0,0 +1,32 @@
import { preloadImage } from "./preloader.js"
export class Level {
constructor(title, subtitle, maxHealth, threshold, startingValue, nbValue, musicPath, meteorPath, bossPath) {
this.title = title
this.subtitle = subtitle
this.maxHealth = maxHealth
this.startingValue = startingValue
this.nbValue = nbValue
this.threshold = threshold
this.musicPath = musicPath
this.meteorPath = meteorPath
this.bossPath = bossPath
}
async loadLevel() {
if (this.meteorPath) {
this.meteorImg = await preloadImage(this.meteorPath)
console.log('load custom meteor', this.meteorImg)
} else {
this.meteorImgList = []
this.meteorImgList.push(await preloadImage("assets/img/meteors/meteor.png"))
this.meteorImgList.push(await preloadImage("assets/img/meteors/meteor_2.png"))
this.meteorImgList.push(await preloadImage("assets/img/meteors/meteor_3.png"))
this.meteorImgList.push(await preloadImage("assets/img/meteors/meteor_4.png"))
console.log('load meteor list', this.meteorImgList)
}
this.bossImg = await preloadImage(this.bossPath)
}
}

18
src/scripts/levelList.js Normal file
View File

@@ -0,0 +1,18 @@
import { Level } from "./level.js"
// Freq settings
// 0 ,10 bass
// 150, 40 med
// 400, 200 high
const levelList = [
new Level('Great Master', 'Bearer of the thousands', 20, 0.65, 0, 40,'assets/music/The Scene Is Dead (MASTER BOOT RECORD Remix).mp3', 'assets/img/meteors/diploma.png', 'assets/img/boss/r4.png'),
new Level('High Tech', 'Comin from above', 20, 0.65, 150, 40,'assets/music/ABIS & Signal & Tasha Baxter - The Wall (Buunshin Remix).flac', 'assets/img/meteors/plane_A.png', 'assets/img/boss/e4.png'),
new Level('Richard', '', 25, 0.75, 150, 40, 'assets/music/El Tigr3 - Black Star.flac', null, 'assets/img/boss/richard.png'),
new Level('V', '', 30, 0.75, 150, 40, 'assets/music/The Only Thing They Fear is V.flac', null, 'assets/img/boss/samurai.png'),
new Level('The End', 'Final attack', 40, 0.75, 90, 40, 'assets/music/The City Must Survive.flac', null, 'assets/img/meteors/meteor.png'),
]
const mainMenuLevel = new Level('MainMenu', '', 1, 0.95, 150, 40, 'assets/music/nightcall_8bit.mp3', null, 'assets/img/meteors/meteor.png')
export { levelList, mainMenuLevel }

86
src/scripts/player.js Normal file
View File

@@ -0,0 +1,86 @@
export default class Player {
constructor() {
this.audioContext = new window.AudioContext()
this.gain = this.audioContext.createGain()
this.audio = {}
this.source = {}
this.analyser = this.audioContext.createAnalyser()
this.analyser.fftSize = 2048
this.startingScale = 0
this.pulseRatio = 1
this.maxValueHistory = 100
this.hzHistory = []
this.frequences = new Uint8Array(this.analyser.frequencyBinCount)
}
createSourceFromAudioElement(audioElement) {
const source = this.audioContext.createMediaElementSource(audioElement)
return source
}
connectSource(source) {
source.connect(this.gain)
this.gain.connect(this.analyser)
this.analyser.connect(this.audioContext.destination)
this.audio.addEventListener('ended', this.stop)
}
setMusic(trackUrl) {
this.audio = new Audio(trackUrl)
this.source = this.createSourceFromAudioElement(this.audio)
this.connectSource(this.source)
}
start() {
if (this.audioContext.state === 'suspended') {
this.audioContext.resume()
.then(() => this.audio.play())
} else {
this.audio.play()
}
}
stop() {
this.audio?.pause()
}
analyse() {
this.analyser.getByteFrequencyData(this.frequences)
for (let i = 0; i < this.frequences.length; i++) {
if (!this.hzHistory[i]) {
this.hzHistory[i] = []
}
if (this.hzHistory[i].length > this.maxValueHistory) {
this.hzHistory[i].shift()
}
this.hzHistory[i].push(this.frequences[i])
}
}
getRangeAverageRatio(startingValue, nbValue) {
let total = 0
for (let i = startingValue; i < nbValue + startingValue; i++) {
total += this.getFrequenceRatio(i)
}
return total / nbValue
}
getFrequenceRatio(index) {
let min = 255
let max = 0
this.hzHistory[index].forEach(value => {
if (value < min) {
min = value
}
if (value > max) {
max = value
}
})
const scale = max - min
const actualValue = this.frequences[index] - min
const percentage = scale === 0 ? 0 : actualValue / scale
return this.startingScale + this.pulseRatio * percentage
}
}

24
src/scripts/preloader.js Normal file
View File

@@ -0,0 +1,24 @@
import { levelList, mainMenuLevel } from "./levelList.js"
function preloadImage(path) {
return new Promise(resolve => {
const image = new Image()
image.src = path
image.onerror = (e) => {
console.error('fuk', path, e)
resolve()
}
image.onload = resolve(image)
})
}
async function preloadEveryThing() {
for (const level of levelList) {
await level.loadLevel()
console.log('Preloaded level:', level.title)
}
await mainMenuLevel.loadLevel()
console.log('Preloaded everything')
}
export { preloadImage, preloadEveryThing }

218
src/scripts/renderer.js Normal file
View File

@@ -0,0 +1,218 @@
import { Explosion } from "./gameObjects.js"
import { preloadImage } from "./preloader.js"
import SoundEffect from "./soundEffect.js"
export class Renderer {
constructor(canvas, backgroundColor) {
this.canvas = canvas
this.context = this.canvas.getContext("2d", { alpha: false })
this.meteors = []
this.turrets = []
this.bullets = []
this.effects = []
this.mouseX = -50
this.mouseY = -50
this.lastFrame = 0
this.backgroundColor = backgroundColor
this.bossImg = new Image()
this.bossY = 0
this.showStats = false
this.showCollisionBox = false
this.loadSprites()
}
async loadSprites() {
this.explosionImg = await preloadImage("assets/img/effects/explosion.png")
}
renderGameObjectSimple(gameObject) {
this.context.save()
this.context.translate(gameObject.x, gameObject.y)
this.context.rotate(gameObject.angle - Math.PI / 2)
this.context.drawImage(gameObject.sprite, 0, 0, 20, 20)
this.context.restore()
}
renderGameObject(gameObject) {
let destroy = false
let posX = gameObject.x
let posY = gameObject.y
const applyRotation = gameObject.angle !== 0
if (applyRotation) {
this.context.save()
const gameObjectCenterX = gameObject.x
const gameObjectCenterY = gameObject.y
this.context.translate(gameObjectCenterX, gameObjectCenterY)
this.context.rotate(gameObject.angle + Math.PI / 2)
this.context.translate(-gameObject.sizeX / 2, -gameObject.sizeY)
posX = 0
posY = 0
// console.log(gameObject.sizeX, gameObject.sizeY, gameObjectCenterX, gameObjectCenterY, gameObject.angle)
}
if (gameObject.isAnimated) {
const frameX = gameObject.heightX * (gameObject.framesCounter % 8)
const frameY = gameObject.heightY * Math.floor(gameObject.framesCounter / 8)
this.context.drawImage(gameObject.sprite, frameX, frameY, gameObject.heightX, gameObject.heightY, posX, posY, gameObject.sizeX, gameObject.sizeY)
if (gameObject.animationRunning) {
gameObject.framesCounter++
if (gameObject.framesCounter === gameObject.frames) {
if (gameObject.removeAfterAnimation) {
destroy = true
} else {
gameObject.framesCounter = 0
}
}
}
} else {
this.context.drawImage(gameObject.sprite, posX, posY, gameObject.sizeX, gameObject.sizeY)
}
if (applyRotation) {
this.context.restore()
}
if (this.showCollisionBox) {
this.renderCollisionBox(gameObject)
}
return destroy
}
renderBackground() {
this.context.fillStyle = this.backgroundColor
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height)
this.context.fill()
}
renderDebug(startTime, deltaTime) {
const endTime = performance.now()
const fps = Math.round(1 / (deltaTime / 1000))
this.context.font = '25px serif';
this.context.fillStyle = 'black'
this.context.textAlign = 'start'
this.context.fillText(`FPS: ${fps}`, 10, 50)
this.context.fillText(`Delta: ${deltaTime}`, 10, 75)
this.context.fillText(`Frame time: ${endTime - startTime}`, 10, 100)
this.context.fillText(`Meteors: ${this.meteors.length}`, 10, 125)
this.context.fillText(`Bullets: ${this.bullets.length}`, 10, 150)
this.context.fillText(`Mouse x: ${this.mouseX} y:${this.mouseY}`, 10, 175)
}
renderTarget() {
this.context.beginPath()
this.context.arc(this.mouseX, this.mouseY, 25, 0, 2 * Math.PI, false)
this.context.lineWidth = 1
this.context.strokeStyle = 'red'
this.context.stroke()
}
renderCollisionBox(gameObject) {
this.context.strokeRect(gameObject.x - gameObject.sizeX, gameObject.y - gameObject.sizeY, gameObject.sizeX * 2, gameObject.sizeY * 2)
}
renderMeteorLoop(deltaTime) {
let damageTaken = 0
for (let i = this.meteors.length - 1; i >= 0; i--) {
const meteor = this.meteors[i]
meteor.step(deltaTime)
if (meteor.y > this.canvas.height) {
damageTaken += 1
this.meteors.splice(i, 1)
} else if(meteor.x < 0 || meteor.x > this.canvas.width) {
this.meteors.splice(i, 1)
} else if (meteor.health <= 0) {
SoundEffect.play(SoundEffect.explosion)
this.effects.push(new Explosion(meteor.x, meteor.y, this.explosionImg))
this.meteors.splice(i, 1)
} else if (this.renderGameObject(meteor)) {
this.meteors.splice(i, 1)
}
}
return damageTaken
}
renderBulletLoop(deltaTime) {
for (let i = this.bullets.length - 1; i >= 0; i--) {
const bullet = this.bullets[i]
let collision = false
bullet.step(deltaTime)
if (
bullet.y > this.canvas.height || bullet.y < 0 || bullet.x > this.canvas.width || bullet.x < 0
) {
this.bullets.splice(i, 1)
} else {
for (let j = this.meteors.length - 1; j >= 0; j--) {
const meteor = this.meteors[j]
collision = bullet.collideWith(meteor)
if (collision) {
this.bullets.splice(i, 1)
meteor.health -= bullet.damage
break;
}
}
if (!collision) {
this.renderGameObject(bullet)
}
}
}
}
renderTurretLoop() {
for (const turret of this.turrets) {
this.renderGameObject(turret)
}
}
renderEffectLoop(deltaTime) {
for (let i = this.effects.length - 1; i >= 0; i--) {
const effect = this.effects[i]
effect.step(deltaTime)
if (this.renderGameObject(effect)) {
this.effects.splice(i, 1)
}
}
}
renderSun() {
this.context.drawImage(this.bossImg, this.canvas.width / 2, (this.canvas.height + 250) - this.bossY, 100, 100)
}
render() {
const startTime = performance.now()
const deltaTime = startTime - this.lastFrame
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
this.renderBackground()
this.renderSun()
const damageTaken = this.renderMeteorLoop(deltaTime)
this.renderBulletLoop(deltaTime)
this.renderTurretLoop()
this.renderEffectLoop(deltaTime)
this.renderTarget()
if (this.showStats) {
this.renderDebug(startTime, deltaTime)
}
this.lastFrame = startTime
return damageTaken
}
}

23
src/scripts/router.js Normal file
View File

@@ -0,0 +1,23 @@
export class Router {
currentPage = "none"
mainMenu = document.getElementById("main-menu")
customLevel = document.getElementById("custom-level")
loading = document.getElementById("loading")
mainGame = document.getElementById("main-game")
gameOver = document.getElementById("game-over")
hideEverything() {
this.mainMenu.style.display = "none"
this.customLevel.style.display = "none"
this.loading.style.display = "none"
this.mainGame.style.display = "none"
this.gameOver.style.display = "none"
}
showPage(pageName) {
this.hideEverything()
this.currentPage = pageName
this[pageName].style.display = "flex"
}
}

View File

@@ -0,0 +1,31 @@
export default class SoundEffect {
static turretLaser = new Audio("assets/sounds/turret_laser.mp3")
static turretFast = new Audio("assets/sounds/turret_fast.mp3")
static turretStandard = new Audio("assets/sounds/turret_standard.mp3")
static turretHeavy = new Audio("assets/sounds/turret_heavy.flac")
static explosion = new Audio("assets/sounds/explosion.mp3")
static explosionBig = new Audio("assets/sounds/explosion_big.mp3")
static turretRelease = new Audio("assets/sounds/turret_release.wav")
static soundList = [
SoundEffect.turretLaser,
SoundEffect.turretFast,
SoundEffect.turretStandard,
SoundEffect.turretHeavy,
SoundEffect.explosion,
SoundEffect.explosionBig,
SoundEffect.turretRelease
]
static setEffectVolume(volume) {
SoundEffect.soundList.forEach((sound) => sound.volume = volume)
}
static stopAll() {
SoundEffect.soundList.forEach((sound) => sound.pause())
}
static play(audio) {
audio.paused ? audio.play() : audio.currentTime = 0
}
}

View File

@@ -0,0 +1,68 @@
import { preloadImage } from "./preloader.js"
import SoundEffect from "./soundEffect.js"
async function setStandardTurret(turret) {
turret.spriteImg= await preloadImage("assets/img/turrets/turret_standard.png")
turret.bulletSprite = await preloadImage("assets/img/turrets/bullet_standard.png")
turret.firingRate = 100
turret.lastShot = 0
turret.spread = 100
turret.damage = 20
turret.isAnimated = true
turret.removeAfterAnimation = false
turret.animationRunning = false
turret.frames = 8
turret.heightX = 256
turret.heightY = 256
turret.sound = SoundEffect.turretStandard
}
async function setFastTurret(turret) {
turret.spriteImg= await preloadImage("assets/img/turrets/turret_fast.png")
turret.bulletSprite = await preloadImage("assets/img/turrets/bullet_fast.png")
turret.firingRate = 20
turret.lastShot = 0
turret.spread = 200
turret.damage = 10
turret.isAnimated = true
turret.removeAfterAnimation = false
turret.animationRunning = false
turret.frames = 8
turret.heightX = 256
turret.heightY = 256
turret.sound = SoundEffect.turretFast
}
async function setHeavyTurret(turret) {
turret.spriteImg= await preloadImage("assets/img/turrets/turret_heavy.png")
turret.bulletSprite = await preloadImage("assets/img/turrets/bullet_heavy.png")
turret.firingRate = 200
turret.lastShot = 0
turret.spread = 50
turret.damage = 30
turret.isAnimated = true
turret.removeAfterAnimation = false
turret.animationRunning = false
turret.frames = 16
turret.heightX = 256
turret.heightY = 256
turret.sound = SoundEffect.turretHeavy
}
async function setLaserTurret(turret) {
turret.spriteImg= await preloadImage("assets/img/turrets/turret_laser.png")
turret.bulletSprite = await preloadImage("assets/img/turrets/bullet_laser.png")
turret.firingRate = 0
turret.lastShot = 0
turret.spread = 10
turret.damage = 2
turret.isAnimated = true
turret.removeAfterAnimation = false
turret.animationRunning = false
turret.frames = 8
turret.heightX = 256
turret.heightY = 256
turret.sound = SoundEffect.turretLaser
}
export { setStandardTurret, setFastTurret, setHeavyTurret, setLaserTurret }

237
src/style/style.css Normal file
View File

@@ -0,0 +1,237 @@
@font-face {
font-family: 'ka';
src: url('../assets/fonts/ka1.ttf');
}
@font-face {
font-family: 'revamped';
src: url('../assets/fonts/Revamped.otf');
}
html, body {
font-family: 'ka';
color: rgb(223, 223, 223);
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
input {
border: 2px solid #24201e !important;
padding: 10px 15px;
font-size: 30px;
max-width: 100px;
}
button {
color: #04ab96;
background-color: #153a3a;
transition: 0.25s;
border: 6px solid;
border-color: #368b8c;
border-radius: 10px;
font: inherit;
font-size: 30px;
line-height: 1;
margin: 0.5em;
padding: 10px 15px;
}
button:hover {
box-shadow: inset 0 0 0 2.2em #24201e;
border-color: #24201e;
color: #fff;
cursor: pointer;
}
button:focus {
box-shadow: inset 0 0 0 2.2em #461f1e;
border-color: #24201e;
color: #fff;
}
button:disabled {
box-shadow: inset 0 0 0 2.2em #5a5a5a;
border-color: #24201e;
color: #fff;
}
.background {
position: fixed;
width: 100%;
height: 100%;
z-index: -2;
/* filter: blur(5px); */
background-color: #153a3a;
}
.page {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main-container {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.title-container {
display: grid;
}
.title-back {
height: 400px;
width: 400px;
background-color: #246363;
border-radius: 50%;
}
.title-back-container {
z-index: -1;
top: 0px;
left: 0px;
position: absolute;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.level-select {
display: flex;
}
.level-item img {
height: 75px;
}
.level-item p {
margin: 0px;
}
.main-game {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
}
.main-game-canvas {
position: fixed;
width: 100%;
height: 100%;
background-color: #a9cdd2;
}
.main-game-skyline-container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: end;
width: 100%;
}
.main-game-skyline {
background-image: url("../assets/img/skyline.png");
background-repeat: repeat-x;
background-size: contain;
width: 100%;
height: 68px;
z-index: 1;
}
.main-game-skyline-turret {
width: 1500px;
z-index: 1;
}
.main-game-dashboard {
height: 80px;
width: 100%;
background-color: #264649;
z-index: 1;
display: flex;
justify-content: center;
}
.main-game-dashboard-inner {
display: flex;
align-items: center;
}
.main-game-dashboard-inner h2 {
margin: 30px;
margin-right: 55px;
}
.main-game-dashboard-inner input {
margin-left: 55px;
}
.main-game-dashboard-bullet {
max-height: 60px;
margin: 15px;
display: flex;
align-items: center;
}
.main-game-dashboard-bullet img {
max-height: 60px;
margin: 10px;
}
.main-game-turret-selector {
margin: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.main-game-turret-selector h2 {
margin: 0;
margin-right: 10px;
}
.main-game-title {
display: none;
flex-direction: column;
align-items: center;
z-index: 1;
font-size: 30px;
margin-bottom: 500px;
color: black;
font-family: 'revamped';
animation: 4s anim-lineUp ease-out;
transition: opacity 2s;
}
.main-game-title h2 {
margin-top: 15px;
}
@keyframes anim-lineUp {
0% {
opacity: 0;
transform: translateY(80%);
}
20% {
opacity: 0;
}
50% {
opacity: 1;
transform: translateY(0%);
}
100% {
opacity: 1;
transform: translateY(0%);
}
}