Created
October 24, 2020 20:35
-
-
Save AldeRoberge/f1dc348ae5d436f710f594ade344c597 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Jeux - S.T.A.R.S</title> | |
<link rel="stylesheet" type="text/css" href="css/style.css"/> | |
<style> | |
.game-canvas { | |
border: 1px solid #d3d3d3; | |
background-color: #f1f1f1; | |
} | |
.dialog { | |
background-color: #161616; | |
} | |
</style> | |
</head> | |
<body onload="startGame()"> | |
<!-- The game canvas will be generated here --> | |
<br> | |
<br> | |
<div class="center credits"> | |
<h3>Utilisation</h3> | |
<p>Souris = bouger dans le monde</p> | |
<p>M = rendre la musique en sourdine</p> | |
<h3>Débug</h3> | |
<p>A = Skipper le niveau</p> | |
<br> | |
<p>2020 - Les Jeux Pequno Brouette</p> | |
</div> | |
<!-- | |
_____ _______ _____ _____ | |
/ ____|__ __| /\ | __ \ / ____| | |
| (___ | | / \ | |__) | | (___ | |
\___ \ | | / /\ \ | _ / \___ \ | |
____) | | |_ / ____ \ _| | \ \ _ ____) | | |
|_____(_) |_(_)_/ \_(_)_| \_(_)_____/ | |
Surely | |
This | |
Acronym | |
Reads | |
S.T.A.R.S. | |
https://en.wikipedia.org/wiki/Recursive_acronym | |
--> | |
<script> | |
let debugDraw = false; | |
const worldObjects = []; | |
let myGamePiece; | |
let objectSpawner; | |
const gameUpdateRate = 1; | |
// Sounds | |
let shieldSound; | |
let hurtSound; | |
let healSound; | |
let deathSound; | |
let ammoCollectSound; | |
let enemyDestroySound; | |
let shootSound; | |
let music; | |
let victory; | |
let phaseHandler; | |
// World scroll | |
let isWorldScrolling = true; | |
function startGame() { | |
myGamePiece = new PlayerObject(gameWidth / 2, gameHeight / 2); | |
myGameArea.start(); | |
objectSpawner = new StarSpawner(); | |
shieldSound = new Audio("audio/shield.wav"); | |
hurtSound = new Audio("audio/hurt.wav"); | |
healSound = new Audio("audio/heal.wav"); | |
ammoCollectSound = new Audio("audio/ammo.wav"); | |
deathSound = new Audio("audio/death.wav"); | |
victory = new Audio("audio/victory.wav"); | |
enemyDestroySound = new Audio("audio/enemyDestroySound.wav"); | |
shootSound = new Audio("audio/shoot.wav"); | |
music = new Audio("audio/music.mp3"); | |
} | |
let isGamePaused = false; | |
let score = 0; | |
let scoreCountText; | |
let bullets = 0; | |
let ammoText; | |
let level = 0; | |
let levelText; | |
let debug = ""; | |
let debugText; | |
// Game is ended, player has no more health | |
let isGameEnded = false; | |
let isGameEndedFail = false; | |
const maxHealth = 20; | |
let currentHealth = 0; | |
let drawHealthBar = false; | |
// Goes from 255 to 0 to show player hurt (255 = white, 0 = red (hurt)) | |
let damageIndicator = 255; | |
// The player is hurt / damaged | |
function HitPlayer(damage) { | |
damageIndicator = 0; // See Player update method | |
if (isPlayerShielded) { | |
isPlayerShielded = false; | |
} else { | |
currentHealth -= damage; | |
} | |
hurtSound.play(); | |
if (currentHealth < 0) { | |
deathSound.play(); | |
isGameEnded = true; | |
isGameEndedFail = true; | |
ShowCursor(true); | |
setDialog(""); | |
music.pause(); | |
} | |
} | |
// The player is healed | |
function HealPlayer(heal) { | |
damageIndicator = 255; | |
healSound.play(); | |
drawHealthBar = true; | |
currentHealth += heal; | |
if (currentHealth >= maxHealth) { | |
currentHealth = maxHealth; | |
} | |
} | |
function CollectAmmo(number) { | |
bullets += number; | |
ammoCollectSound.play(); | |
} | |
window.addEventListener("click", go); | |
let isGameStarted = false; | |
let skipTitleCard = false; | |
function go() { | |
if (!isGameStarted) { | |
isGameStarted = true; | |
music.play(); | |
ShowCursor(false); | |
const showTextDelay = 3500; | |
new UIText(gameWidth / 2, gameHeight / 2, "Les Jeux Péquno Brouette", "25px Segoe UI", "white", showTextDelay); | |
new UIText(gameWidth / 2, gameHeight / 1.85, "PRÉSENTENT", "20px Segoe UI", "white", showTextDelay); | |
// Debug | |
debugText = new UIText(gameWidth / 2, 200, "Debug : " + debug, "0px Arial", "white", -1); | |
if (skipTitleCard) { | |
phaseHandler = new PhaseHandler(); | |
beginGame(true); | |
} else { | |
setTimeout(function () { | |
let textShowLength = showTextDelay * 1.5; | |
new UIText(gameWidth / 2, gameHeight / 2, "S. T. Δ. R. S.", "75px Segoe UI", "white", textShowLength / 1.5); | |
new UIText(gameWidth / 2, gameHeight / 2 + 25, "L'ÉPREUVE GALACTIQUE", "25px Segoe UI", "white", textShowLength / 1.5); | |
setTimeout(function () { | |
beginGame(false); | |
}, textShowLength + 1000); | |
}, showTextDelay + 1000); | |
} | |
} | |
} | |
function beginGame(isSkipTitle) { | |
const stopBlinkingTime = 800; | |
// Shows the score | |
ammoText = new UIText(gameWidth - 100, 50, "Balles : " + bullets, "30px Segoe UI", "white", -1); | |
ammoText.blink(true); | |
scoreCountText = new UIText(100, 50, "Points : " + score, "30px Segoe UI", "white", -1); | |
scoreCountText.blink(true); | |
levelText = new UIText(gameWidth / 2, 50, "Niveau : " + level, "20px Segoe UI", "white", -1); | |
levelText.blink(true); | |
setTimeout(function () { | |
scoreCountText.blink(false); | |
ammoText.blink(false); | |
levelText.blink(false); | |
if (!isSkipTitle) { | |
phaseHandler = new PhaseHandler(); | |
} | |
}, stopBlinkingTime); | |
} | |
function setDebug(text) { | |
debug = text; | |
} | |
var gameWidth = 1200; | |
var gameHeight = 800; | |
var myGameArea = { | |
canvas: document.createElement("canvas"), | |
start: function () { | |
myGameArea.canvas.setAttribute('class', 'center') | |
this.canvas.width = gameWidth; | |
this.canvas.height = gameHeight; | |
this.context = this.canvas.getContext("2d"); | |
document.body.insertBefore(this.canvas, document.body.childNodes[0]); | |
setInterval(updateGameArea, gameUpdateRate); | |
window.addEventListener('mousemove', function (e) { | |
myGameArea.x = e.pageX; | |
myGameArea.y = e.pageY; | |
}) | |
}, | |
clear: function () { | |
this.context.fillStyle = "black"; | |
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); | |
} | |
} | |
function ShowCursor(b) { | |
if (b) { | |
myGameArea.canvas.style.cursor = "auto"; //hide the original cursor | |
} else { | |
myGameArea.canvas.style.cursor = "none"; //hide the original cursor | |
} | |
} | |
function UIText(x, y, text, font, color, destroyAfter) { | |
let displayText = text; | |
let timeLived = 0; | |
this.setText = function (text) { | |
displayText = text; | |
} | |
this.blink = function (isBlinking) { | |
this.isBlinking = isBlinking; | |
} | |
const blinkTimeLength = 100; | |
let blinkTime = 0; | |
this.update = function (gameTick, ctx) { | |
timeLived += gameTick; | |
if (destroyAfter > 0 && timeLived > destroyAfter) { | |
console.log("Destroyed text."); | |
// Destroy this object | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
} | |
blinkTime += gameTick; | |
if (blinkTime > blinkTimeLength * 2) { | |
blinkTime = 0; | |
} | |
if (this.isBlinking && blinkTime > blinkTimeLength) { | |
return; | |
} | |
ctx.textAlign = "center"; | |
ctx.fillStyle = color; | |
ctx.font = font; | |
ctx.fillText(displayText, x, y); | |
} | |
worldObjects.push(this); | |
} | |
let previousX = 0; | |
let playerXOffset = 0; | |
let previousY = 0; | |
let playerYOffset = 0; | |
function PlayerObject(x, y) { | |
this.name = "Player"; | |
// The final size of the player | |
let losangeEndSize = 20; | |
// The size of the player | |
let losangeSize = 500; | |
this.x = x; | |
this.y = y; | |
this.Shoot = function () { | |
if (bullets > 0) { | |
bullets--; | |
shootSound.play(); | |
worldObjects.push(new BulletObject(myGamePiece.x, myGamePiece.y, 1)); | |
} else { | |
console.log("Does not have enough bullets..."); | |
} | |
} | |
this.update = function (gameTick, ctx) { | |
losangeSize -= gameTick / 2; | |
if (losangeSize <= losangeEndSize) { | |
losangeSize = losangeEndSize; | |
} | |
// Draw the main triangle | |
if (damageIndicator < 255) { | |
damageIndicator += 1; | |
} | |
ctx.fillStyle = "rgb(255," + damageIndicator + "," + damageIndicator + ")"; | |
// LIMIT THE X AND Y SO THE PLAYER DOES NOT GO AWAY FROM THE CANVAS | |
/** | |
* YOU'RE BOUND TO THIS PRISON SHELL, SLAVE | |
*/ | |
// Clamp X | |
if (this.x < 20) { | |
this.x = 20; | |
} else if (this.x > gameWidth - 20) { | |
this.x = gameWidth - 20; | |
} | |
// Clamp Y | |
if (this.y < 20) { | |
this.y = 20; | |
} else if (this.y > gameHeight - 20) { | |
this.y = gameHeight - 20; | |
} | |
// Calculate offset (to move the stars) | |
playerXOffset = previousX - this.x; | |
previousX = this.x; | |
playerYOffset = previousY - this.y; | |
previousY = this.y; | |
ctx.beginPath(); | |
ctx.moveTo(this.x, this.y - losangeSize); | |
ctx.lineTo(this.x - losangeSize, this.y + losangeSize); | |
ctx.lineTo(this.x + losangeSize, this.y + losangeSize); | |
ctx.fill(); | |
// Draw the red propellers | |
ctx.fillStyle = "red"; | |
ctx.beginPath(); | |
ctx.moveTo(this.x - losangeSize, this.y + losangeSize); | |
ctx.lineTo(this.x - (losangeSize / 2), this.y + losangeSize * 2); | |
ctx.lineTo(this.x, this.y + losangeSize); | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.moveTo(this.x + losangeSize, this.y + losangeSize); | |
ctx.lineTo(this.x + (losangeSize / 2), this.y + losangeSize * 2); | |
ctx.lineTo(this.x, this.y + losangeSize); | |
ctx.fill(); | |
// Draw the orange (flickering) propellers | |
ctx.fillStyle = "orange"; | |
const flickeringHeight = this.y + (losangeSize * 2) - (Math.random() * 15) - 0.25; | |
ctx.beginPath(); | |
ctx.moveTo(this.x - losangeSize, this.y + losangeSize); | |
ctx.lineTo(this.x - (losangeSize / 2), flickeringHeight); | |
ctx.lineTo(this.x, this.y + losangeSize); | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.moveTo(this.x + losangeSize, this.y + losangeSize); | |
ctx.lineTo(this.x + (losangeSize / 2), flickeringHeight); | |
ctx.lineTo(this.x, this.y + losangeSize); | |
ctx.fill(); | |
// Draw the healthbar | |
if (drawHealthBar) { | |
const sizeFactor = 2; | |
const barSize = maxHealth * sizeFactor; | |
ctx.fillStyle = "red"; | |
ctx.fillRect(this.x - barSize / 2, this.y + losangeSize * 2.5, barSize, 10); | |
ctx.fillStyle = "green"; | |
ctx.fillRect(this.x - barSize / 2, this.y + losangeSize * 2.5, currentHealth * sizeFactor, 10); | |
ctx.beginPath(); | |
ctx.textAlign = "center"; | |
ctx.fillStyle = "white"; | |
ctx.font = "bold 10px Arial"; | |
ctx.fillText(currentHealth, this.x, this.y + 58); | |
} | |
//Draw the player shield | |
if (isPlayerShielded) { | |
let shieldRadius = 50; | |
let cercle = new Path2D(); | |
cercle.arc(this.x, this.y + 20, shieldRadius, 0, 2 * Math.PI); | |
ctx.fillStyle = "rgb(0,255,217, 0.25)"; | |
ctx.fill(cercle); | |
} | |
} | |
} | |
function StarSpawner() { | |
const spawnStarTime = 5; | |
let spawnStarIndex = 0; | |
this.update = function (tickDelta) { | |
spawnStarIndex += tickDelta; | |
if (spawnStarIndex > spawnStarTime) { | |
// Dont stack-spawn stars if the world isnt scrolling | |
if (isWorldScrolling) { | |
spawnStarIndex = 0; | |
const starRadius = 1; | |
const x = Math.random() * gameWidth; | |
const y = -(starRadius / 2); | |
worldObjects.push(new StarObject(x, y, starRadius)); | |
} | |
} | |
} | |
worldObjects.push(this); | |
} | |
function TextPhase(phaseHandler, text, textColor, font, time, delay) { | |
this.start = function () { | |
console.log("Started text phase..."); | |
setDialog(text, textColor, font); | |
} | |
let tickIndex = 0; | |
this.update = function (tickDelta) { | |
tickIndex += tickDelta; | |
if (tickIndex > time) { | |
clearDialog(); | |
phaseHandler.nextPhase(delay); | |
} | |
} | |
} | |
function IntroPhase(enemies, phaseHandler, text, textColor, font) { | |
this.start = function () { | |
console.log("Started intro phase..."); | |
setDialog(" ", "green", "30px Arial"); | |
} | |
let tickIndex = 0; | |
this.update = function (tickDelta) { | |
tickIndex += tickDelta; | |
if (tickIndex > 2000) { | |
tickIndex = 0; | |
worldObjects.push(new HealObject(gameWidth / 2 - 20, -5)); | |
phaseHandler.nextPhase(1000); | |
} | |
} | |
} | |
function RecompensePhase(heal, bullets, shield, phaseHandler) { | |
this.start = function () { | |
console.log("Started recompense phase..."); | |
} | |
this.update = function (tickDelta) { | |
// Spawn healing obj | |
if (heal > 0) { | |
heal--; | |
const x = GetRandomObjectSpawnPositionX(); | |
worldObjects.push(new HealObject(x, -10)); | |
} | |
// Spawn bullets obj | |
if (bullets > 0) { | |
bullets--; | |
const x = GetRandomObjectSpawnPositionX(); | |
worldObjects.push(new AmmoObject(x, -10)); | |
} | |
// Spawn shield obj | |
if (shield > 0) { | |
shield--; | |
worldObjects.push(new ShieldObject(gameWidth / 2 + 20, -5)); | |
} | |
// No more power-ups | |
if (heal === 0 && bullets === 0 && shield === 0) { | |
setDialog("") | |
phaseHandler.nextPhase(2000); | |
} | |
} | |
} | |
function WallPhase(endAfterNumberOfBeats, speed, frequency) { | |
this.frequency = frequency; | |
this.enemySpeed = speed; | |
this.start = function () { | |
console.log("Started wall phase..."); | |
} | |
let beatIndex = 0; | |
let isBeat = false; | |
this.update = function (tickDelta) { | |
score++; | |
function SpawnWall() { | |
console.log("Spawning wall..."); | |
beatIndex++; | |
if (beatIndex > endAfterNumberOfBeats && endAfterNumberOfBeats > -1) { | |
phaseHandler.nextPhase(0); | |
} | |
console.log(beatIndex % frequency); | |
if (beatIndex % frequency !== 0) { | |
return; | |
} | |
let enemyCount = 30; | |
let enemiesPos = gameWidth / enemyCount; | |
let holeIndex = Math.floor(Math.random() * enemyCount); | |
console.log("Hole index is " + holeIndex); | |
let currentEnemyPos = 0; | |
for (let i = 0; i < enemyCount; i++) { | |
currentEnemyPos += enemiesPos; | |
// Skip hole (two width) | |
if (i === holeIndex || (i + 1 === holeIndex)) { | |
continue; | |
} | |
//console.log("Spawning enemy at (" + currentEnemyPos + ", -20)"); | |
worldObjects.push(new EnemyObject(currentEnemyPos, -20, 20, 1, speed)); | |
} | |
} | |
if (beatFlip && !isBeat) { | |
isBeat = true; | |
SpawnWall(); | |
} else if (!beatFlip && isBeat) { | |
isBeat = false; | |
SpawnWall(); | |
} | |
} | |
} | |
function BossPhase(phaseHandler) { | |
this.phaseHandler = phaseHandler; | |
this.wallPhase = new WallPhase(-1, 0.20, 4); | |
this.start = function () { | |
console.log("Started boss phase..."); | |
setDialog("C'EST ASSEZ!!!", "red"); | |
worldObjects.push(new BossEnemyObject(this.phaseHandler, gameWidth / 2, -200, 0, 200)); | |
} | |
this.update = function (tickDelta) { | |
this.wallPhase.update(tickDelta); | |
} | |
} | |
function VictoryPhase(phaseHandler) { | |
this.start = function () { | |
console.log("Started boss phase..."); | |
music.pause(); | |
victory.play(); | |
isGameEnded = true; | |
} | |
this.update = function (tickDelta) { | |
} | |
} | |
function EasyPhase(enemies, phaseHandler, spawnTime, message, color, enemySpeed, waveLength, delay) { | |
this.start = function () { | |
console.log("Started easy phase..."); | |
setDialog(message, color, "30px Arial"); | |
} | |
let tickIndex = 0; | |
let tickCount = 0; | |
this.update = function (tickDelta) { | |
score++; | |
tickIndex += tickDelta; | |
if (tickIndex > spawnTime) { | |
tickCount++; | |
tickIndex = 0; | |
const x = GetRandomObjectSpawnPositionX(); | |
worldObjects.push(new EnemyObject(x, -20, 20, 1, enemySpeed)); | |
} | |
if (tickCount > waveLength) { | |
setDialog("") | |
phaseHandler.nextPhase(delay); | |
} | |
} | |
} | |
function GetRandomObjectSpawnPositionX() { | |
let ret = Math.random() * gameWidth; | |
let clampLimit = 10; | |
// Clamp to left | |
if (ret < clampLimit) { | |
ret = clampLimit; | |
} | |
// Clamp to right | |
if (ret > gameWidth - clampLimit) { | |
return gameWidth - clampLimit; | |
} | |
return ret; | |
} | |
function GetRandomObjectSpawnPositionY() { | |
return Math.random() * gameHeight; | |
} | |
function FeelMyWrathPhase(enemies, phaseHandler, text, textColor, font) { | |
this.phaseHandler = phaseHandler; | |
this.enemies = enemies; | |
let tickIndex = 0; | |
this.start = function () { | |
setDialog(text, textColor, font); | |
} | |
let tickCount = 0; | |
this.update = function (tickDelta) { | |
score++; | |
tickIndex += tickDelta; | |
if (tickIndex > 10) { | |
tickCount++; | |
tickIndex = 0; | |
const x = GetRandomObjectSpawnPositionX(); | |
worldObjects.push(new EnemyObject(x, -5, 10, 1, 1)); | |
} | |
if (tickCount > 1000) { | |
phaseHandler.nextPhase(1000); | |
} | |
} | |
} | |
function PhaseHandler() { | |
let currentPhaseIndex = -1; | |
let phase0 = new IntroPhase(100, this); | |
let phase1 = new TextPhase(this, " ", "Green", "30px Arial", 10, 1000); | |
let phase2 = new EasyPhase(100, this, 50, " ", "white", 0.75, 300, 500); | |
let phase3 = new TextPhase(this, "Pas mal du tout!", "Green", "30px Arial", 2500, 400); | |
let phase4 = new EasyPhase(100, this, 100, "Et si j'y vais comme ça?", "orange", 1.5, 200, 0); | |
let phase5 = new FeelMyWrathPhase(100, this, "C'EST TA FIN!!!", "red", "30px Arial"); | |
let phase6 = new RecompensePhase(0, 0, 1, this); | |
let phase7 = new WallPhase(10, 0.25, 2); | |
let phase8 = new RecompensePhase(10, 10, 10, this) | |
let phase9 = new BossPhase(this); | |
let phase10 = new VictoryPhase(); | |
let isInBetweenPhases = false; | |
let timeBeforeNextPhase = 0; | |
this.nextPhase = function (delay) { | |
level++; | |
setDialog(""); | |
isInBetweenPhases = true; | |
timeBeforeNextPhase = delay; | |
} | |
this.update = function (tickDelta) { | |
if (isInBetweenPhases) { | |
if (timeBeforeNextPhase > 0) { | |
// Wait for next phase... | |
timeBeforeNextPhase -= 1; | |
} else { | |
// Ready for next phase | |
isInBetweenPhases = false; | |
currentPhaseIndex++; | |
GetCurrentPhase().start(); | |
} | |
return; | |
} | |
isWorldScrolling = true; | |
GetCurrentPhase().update(tickDelta); | |
} | |
// TODO | |
//this.nextPhase(1000); | |
this.nextPhase(1); | |
function GetCurrentPhase() { | |
switch (currentPhaseIndex) { | |
case 0: | |
return phase0; | |
case 1: | |
return phase1; | |
case 2: | |
return phase2; | |
case 3: | |
return phase3; | |
case 4: | |
return phase4; | |
case 5: | |
return phase5; | |
case 6: | |
return phase6; | |
case 7: | |
return phase7; | |
case 8: | |
return phase8; | |
case 9: | |
return phase9; | |
case 10: | |
return phase10; | |
case 11: | |
return phase11; | |
case 12: | |
return phase12; | |
case 13: | |
return phase13; | |
} | |
} | |
worldObjects.push(this); | |
} | |
// Returns the distance between two points | |
function CalculateDistance(x1, y1, x2, y2) { | |
const a = x1 - x2; | |
const b = y1 - y2; | |
return Math.sqrt(a * a + b * b); | |
} | |
function AmmoObject(x, y) { | |
this.name = "AmmoObject"; | |
this.x = x; | |
this.y = y; | |
this.update = function (tickDelta, ctx) { | |
if (isWorldScrolling) this.y += 0.5; | |
// Destroy this object if it is outside the bounds of the world | |
if (this.y > gameHeight) { | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
let distanceToPlayer = CalculateDistance(this.x, this.y, myGamePiece.x, myGamePiece.y); | |
if (distanceToPlayer < 20) { | |
CollectAmmo(1); | |
// Destroy this object | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
} | |
DrawHexagon(ctx, this.x, this.y, 20, "#ffb600"); | |
} | |
} | |
let isPlayerShielded = false; | |
function ShieldPlayer() { | |
shieldSound.play(); | |
isPlayerShielded = true; | |
} | |
function ShieldObject(x, y) { | |
this.name = "Shield"; | |
this.x = x; | |
this.y = y; | |
this.update = function (tickDelta, ctx) { | |
if (isWorldScrolling) this.y += 0.5; | |
// Destroy this object if it is outside the bounds of the world | |
if (this.y > gameHeight) { | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
let distanceToPlayer = CalculateDistance(this.x, this.y, myGamePiece.x, myGamePiece.y); | |
if (distanceToPlayer < 20) { | |
ShieldPlayer(); | |
// Destroy this object | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
} | |
let size = 10; | |
let fillStyle = "#0028ff"; | |
DrawHexagon(ctx, this.x, this.y, size, fillStyle); | |
} | |
} | |
// Draws an exagon | |
function DrawHexagon(ctx, x, y, size, fillStyle) { | |
ctx.beginPath(); | |
ctx.moveTo(x + size * Math.cos(0), y + size * Math.sin(0)); | |
for (let side = 0; side < 7; side++) { | |
ctx.lineTo(x + size * Math.cos(side * 2 * Math.PI / 6), y + size * Math.sin(side * 2 * Math.PI / 6)); | |
} | |
ctx.fillStyle = fillStyle; | |
ctx.fill(); | |
} | |
function HealObject(x, y) { | |
this.name = "Heal"; | |
this.x = x; | |
this.y = y; | |
this.update = function (tickDelta, ctx) { | |
if (isWorldScrolling) this.y += 0.5; | |
// Destroy this object if it is outside the bounds of the world | |
if (this.y > gameHeight) { | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
let distanceToPlayer = CalculateDistance(this.x, this.y, myGamePiece.x, myGamePiece.y); | |
if (distanceToPlayer < 20) { | |
HealPlayer(maxHealth); | |
// Destroy this object | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
} | |
DrawHexagon(ctx, this.x, this.y, 10, "#2cff00"); | |
} | |
} | |
function BulletObject(x, y, speed) { | |
this.name = "Bullet"; | |
this.x = x; | |
this.y = y; | |
this.update = function (tickDelta, ctx) { | |
if (this.y < 0) { | |
console.log("Bullet was outside of this world.") | |
// Destroy this object if it is outside the bounds of the world | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
for (var obj2 in worldObjects) { | |
let obj = worldObjects[obj2]; | |
if (obj.name === "BossEnemyObject") { | |
let distance = CalculateDistance(this.x, this.y, obj.x, obj.y); | |
console.log("Distance to boss is " + distance + " my " + x + ", " + y + ", his " + obj.x + ", " + obj.y); | |
if (distance < obj.size) { | |
obj.damage(obj.size); | |
console.log("Collision with enemy"); | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
} | |
} | |
// Draw the star | |
let cercle = new Path2D(); | |
cercle.arc(this.x, this.y, 2, 0, 2 * Math.PI); | |
ctx.fillStyle = "gray"; | |
ctx.fill(cercle); | |
if (isWorldScrolling) this.y -= tickDelta * speed; | |
} | |
} | |
function BossEnemyObject(phaseHandler, x, y, finalY, size) { | |
this.name = "BossEnemyObject"; | |
this.x = x; | |
this.y = y; | |
this.maxHealth = 5; | |
this.currentHealth = this.maxHealth; | |
this.finalY = finalY; | |
this.size = size; | |
this.phaseHandler = phaseHandler; | |
this.damage = function () { | |
enemyDestroySound.play(); | |
this.currentHealth -= 1; | |
console.log("Damagin! Current health : " + this.currentHealth); | |
if (this.currentHealth > 20) { | |
setDialog("ARRGGGHHHH!!!", "red"); | |
} else if (this.currentHealth > 15) { | |
setDialog("FINI!!!!!!", "red"); | |
} else if (this.currentHealth > 10) { | |
setDialog("L'UNIVERS M'APPARTIENT", "red"); | |
}else if (this.currentHealth > 10) { | |
setDialog("JE SUIS LE SEUL À POUVOIR TE BATTRE", "red"); | |
} else if (this.currentHealth > 5) { | |
setDialog("SALE VERMINE", "red"); | |
} else if (this.currentHealth <= 0) { | |
setDialog("", "red"); | |
console.log("BOSS DIED"); | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
this.phaseHandler.nextPhase(0); | |
} | |
} | |
this.update = function (tickDelta, ctx) { | |
ctx.fillStyle = "red"; | |
let cercle = new Path2D(); | |
cercle.arc(this.x, this.y, size, 0, 2 * Math.PI); | |
ctx.fill(cercle); | |
if (this.y < this.finalY) { | |
this.y += 1; | |
} | |
const sizeFactor = 5; | |
const barSize = this.maxHealth * sizeFactor; | |
ctx.fillStyle = "red"; | |
ctx.fillRect(this.x - barSize / 2, this.y + size + 20, barSize, 10); | |
ctx.fillStyle = "green"; | |
ctx.fillRect(this.x - barSize / 2, this.y + size + 20, this.currentHealth * sizeFactor, 10); | |
} | |
} | |
function EnemyObject(x, y, size, damage, speed) { | |
this.name = "Enemy"; | |
this.x = x; | |
this.y = y; | |
this.damage = function () { | |
enemyDestroySound.play(); | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
} | |
this.update = function (tickDelta, ctx) { | |
if (this.y > gameHeight) { | |
// Destroy this object if it is outside the bounds of the world | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
let distanceToPlayer = CalculateDistance(this.x, this.y, myGamePiece.x, myGamePiece.y); | |
if (distanceToPlayer < size) { | |
HitPlayer(damage); | |
// Destroy this object | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
} | |
ctx.fillStyle = "red"; | |
ctx.beginPath(); | |
ctx.moveTo(this.x - size, this.y + size); | |
ctx.lineTo(this.x - (size / 2), this.y + size * 2); | |
ctx.lineTo(this.x, this.y + size); | |
ctx.fill(); | |
if (isWorldScrolling) this.y += tickDelta * speed; | |
} | |
} | |
// A star is an aesthetic object | |
function StarObject(x, y, radius) { | |
this.name = "Star"; | |
this.radius = radius; | |
this.x = x; | |
this.y = y; | |
this.z = 1; | |
this.update = function (tickDelta, context) { | |
if (this.y > gameHeight) { | |
// Destroy this object | |
worldObjects.splice(worldObjects.indexOf(this), 1); | |
return; | |
} | |
// Update position | |
if (isWorldScrolling) { | |
this.y += tickDelta / 2; | |
// Offset according to player pos' | |
this.x -= playerXOffset / 90; | |
this.y += playerYOffset / 50; | |
} | |
// Draw the star | |
let cercle = new Path2D(); | |
cercle.arc(this.x, this.y, radius, 0, 2 * Math.PI); | |
context.fillStyle = "gray"; | |
context.fill(cercle); | |
} | |
} | |
let dialogFont = ""; | |
let dialogColor = ""; | |
let dialogText = ""; | |
// Shows a dialog at the bottom of the screen | |
function setDialog(text, color, font) { | |
if (text) { | |
dialogText = text; | |
} else { | |
dialogText = " "; | |
} | |
if (color) { | |
dialogColor = color; | |
} | |
if (font) { | |
dialogFont = font; | |
} | |
} | |
// No dialog to be displayed | |
function clearDialog() { | |
dialogText = ""; | |
} | |
// Draws the dialog (every frame) | |
function drawDialogView(ctx) { | |
if (dialogText) { | |
ctx.beginPath(); | |
ctx.textAlign = "center"; | |
ctx.fillStyle = dialogColor; | |
ctx.font = dialogFont; | |
ctx.fillText(dialogText, gameWidth / 2, gameHeight - 20); | |
} | |
} | |
let lastUpdateTime = 0; | |
let beginRender = new Date(); | |
let isMusicPaused = false; | |
window.addEventListener('mousedown', function (event) { | |
console.log("Shooting!"); | |
myGamePiece.Shoot(); | |
}); | |
// Register key input | |
window.addEventListener('keyup', function (event) { | |
if (event.defaultPrevented) { | |
return; | |
} | |
const key = event.key || event.keyCode; | |
if (key === 'm' || key === 'M') { | |
if (!isMusicPaused) { | |
music.volume = 0; | |
isMusicPaused = true; | |
} else { | |
music.volume = 1; | |
isMusicPaused = false; | |
} | |
} | |
if (key === 'Escape' || key === 'Esc' || key === 'Space') { | |
console.log("Pausing game"); | |
isGamePaused = !isGamePaused; | |
if (!isGameEnded) { | |
if (isGamePaused) { | |
ShowCursor(true); | |
music.pause(); | |
} else { | |
ShowCursor(false); | |
music.play(); | |
} | |
} | |
} | |
if (key === 'a' || key === 'A') { | |
console.log("Skipping this phase"); | |
phaseHandler.nextPhase(0); | |
} | |
}); | |
let beatFlip = false; | |
let beatCount = 0; | |
// Updates the game | |
function updateGameArea() { | |
const context = myGameArea.context; | |
if (isGamePaused) { | |
return; | |
} | |
myGameArea.clear(); | |
if (!isGameStarted) { | |
//jeu avec titre, description, musique et bouton “Démarrer” | |
context.textAlign = "center"; | |
context.fillStyle = "white"; | |
context.font = "50px Arial "; | |
context.fillStyle = "magenta"; | |
context.fillText("S. T. Δ. R. S.", gameWidth / 2, gameHeight / 2); | |
context.textAlign = "center"; | |
context.font = "31px Arial"; | |
var gradient = context.createLinearGradient(0, 0, gameWidth, 0); | |
gradient.addColorStop(0.25, "blue"); | |
gradient.addColorStop(0.50, "magenta"); | |
gradient.addColorStop(0.75, "white"); | |
context.fillStyle = gradient; | |
context.fillText("L'épreuve galactique", gameWidth / 2, gameHeight / 2 + 40); | |
context.textAlign = "center"; | |
context.fillStyle = "gray"; | |
context.font = "20px Arial"; | |
context.fillText("CLIQUEZ POUR DÉMARRER", gameWidth / 2, gameHeight / 2 + 250); | |
// Mute | |
context.textAlign = "center"; | |
context.fillStyle = "rgb(48,48,48)"; | |
context.font = "15px Arial"; | |
context.fillText("M pour rendre la musique en sourdine", gameWidth / 2, gameHeight - 20); | |
return; | |
} | |
if (myGameArea.x && myGameArea.y) { | |
myGamePiece.x = myGameArea.x; | |
myGamePiece.y = myGameArea.y; | |
} | |
if (ammoText && scoreCountText) { | |
ammoText.setText("Balles : " + bullets); | |
scoreCountText.setText("Points : " + score); | |
levelText.setText("Niveau : " + level); | |
debugText.setText("Debug : " + debug); | |
} | |
if (isGameEnded) { | |
context.beginPath(); | |
context.textAlign = "center"; | |
context.font = "30px Arial"; | |
if (isGameEndedFail) { | |
context.fillStyle = "red"; | |
context.fillText("MISSION ÉCHOUÉE", gameWidth / 2, gameHeight / 2 - 20); | |
} else { | |
context.fillStyle = "green"; | |
context.fillText("MISSION RÉUSSIE", gameWidth / 2, gameHeight / 2 - 20); | |
} | |
context.font = "20px Arial"; | |
context.fillText("Pointage : " + score, gameWidth / 2, gameHeight / 2); | |
return; | |
} | |
worldObjects.forEach(element => { | |
//console.log("Updating " + element); | |
element.update(lastUpdateTime, context); | |
// Clear the graph path before repainting | |
myGameArea.context.beginPath(); | |
} | |
); | |
// Draw the game object on top of everything else | |
myGamePiece.update(lastUpdateTime, context); | |
drawDialogView(context); | |
const endRender = new Date(); | |
lastUpdateTime = (endRender.getMilliseconds() - beginRender.getMilliseconds()) / gameUpdateRate; | |
if (lastUpdateTime < 0) { | |
lastUpdateTime = 0; | |
} | |
//console.log("render : " + endRender.getMilliseconds() + ", " + beginRender.getMilliseconds()) | |
beginRender = new Date(); | |
beatCount += 1; | |
// Counts the beat in the song | |
if (beatCount > 166.75) { | |
beatCount = 0; | |
beatFlip = beatFlip !== true; | |
// Time the console logs to the sound of the music :) | |
if (beatCount % 8 === 0) { | |
if (debugDraw) console.log("Updating " + worldObjects.length + " objects with time delta " + lastUpdateTime); | |
} | |
} | |
setDebug(beatFlip + ", " + beatCount); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment