diff --git a/Games/Alien_Invasion/.DS_Store b/Games/Alien_Invasion/.DS_Store new file mode 100644 index 0000000000..74e30099e6 Binary files /dev/null and b/Games/Alien_Invasion/.DS_Store differ diff --git a/Games/Alien_Invasion/README..md b/Games/Alien_Invasion/README..md new file mode 100644 index 0000000000..17a09c4382 --- /dev/null +++ b/Games/Alien_Invasion/README..md @@ -0,0 +1,20 @@ +# AttackAlienInvaders Game App +A cool 2D alien planet exploration steampunk-themed game application with techniques of vanilla JavaScript, HTML5, CSS3 and HTML Canvas. From sprite animation to parallax backgrounds, the game App is completely built from scratch, with no frameworks or libraries, using HTML, CSS and plain vanilla JavaScript. The game contains richful premium art assets for characters, environments and props. + +### [Game Won Demo](https://github.com/KrystalZhang612/KrystalZhang-AttackAlienInvaders-Game-App/blob/main/testing-result-AttackAlienInvader-game-app/Won%20Game.mov): Most Wondrous! Well done explorer! +https://user-images.githubusercontent.com/72481348/198857930-95a5c040-1d47-4ca6-b5a6-15ac05e8e520.mov +### [Game Lost Demo](https://github.com/KrystalZhang612/KrystalZhang-AttackAlienInvaders-Game-App/blob/main/testing-result-AttackAlienInvader-game-app/Lost%20Game.mov): Blazed! Get my repair kit and try again! +https://user-images.githubusercontent.com/72481348/198857699-86d076d4-f746-435b-89ec-333ad1ba01b8.mov + +# Game Premiere Storyline +***Central Computer***:
+`These alien creatures have very similar physiology to the earth seahorses. Their bodies can easily cut through the thick atmosphere. They can move very fast.`
+***Explorer***:
+`Seahorses hives are being attacked by something. This one has been damaged. I wonder if you can hack into this creature’s central computer to control it for a while.`
+***Central Computer***:
+`It is surprisingly easy to override its circuits. Seems like these machines are not used by our technology. I’m getting a lot of new data.`
+***Central Computer***:
+`The atmosphere on this planet is thick enough to allow heavy silicon-based lifeforms to float but smoke is blocking most of the sunlight. Many creatures developed artificial lights and glowing appendages to see through the heavy clouds. The Seahorse sentinel has a basic attack that’s powerful against weak enemies, but if it absorbs energy from one of the overcharged creatures, it gets additional firepower for a short period of time and it instantly replenishes its ammo. Ammo also automatically recharges over time. It seems we just need to help it to get through these aggressive swarms in time so it can join its hive.` +- There is a stream of data about multiple different species. +- The alien creatures in the hood of their ecosystem have special movements, special abilities, and interact with the environments on other planets. + diff --git a/Games/Alien_Invasion/index.html b/Games/Alien_Invasion/index.html new file mode 100644 index 0000000000..760bb1af1b --- /dev/null +++ b/Games/Alien_Invasion/index.html @@ -0,0 +1,44 @@ + + + + + + + + AttackAlienInvader Game + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Games/Alien_Invasion/script.js b/Games/Alien_Invasion/script.js new file mode 100644 index 0000000000..5e50da0a0c --- /dev/null +++ b/Games/Alien_Invasion/script.js @@ -0,0 +1,543 @@ +window.addEventListener('load', function () { + //canvas setup + const canvas = document.getElementById('canvas1'); + const ctx = canvas.getContext('2d'); + canvas.width = 1000; + canvas.height = 500; + + class InputHandler { + constructor(game) { + this.game = game; + window.addEventListener('keydown', e => { + if (((e.key === 'ArrowUp') || + (e.key === 'ArrowDown') + ) && this.game.keys.indexOf(e.key) === - 1) { + this.game.keys.push(e.key); + } else if (e.key === ' ') { + this.game.player.shootTop(); + } else if (e.key === 'd') { + this.game.debug = !this.game.debug; + } + }); + window.addEventListener('keyup', e => { + if (this.game.keys.indexOf(e.key) > -1) { + this.game.keys.splice(this.game.keys.indexOf(e.key), 1); + } + }); + } + } + + class Projectile { + constructor(game, x, y) { + this.game = game; + this.x = x; + this.y = y; + this.width = 10; + this.height = 3; + this.speed = 3; + this.markedForDeletion = false; + this.image = document.getElementById('projectile'); + } + update() { + this.x += this.speed; + if (this.x > this.game.width * 0.8) this.markedForDeletion = true; + } + draw(context) { + context.drawImage(this.image, this.x, this.y); + } + } + + class Particle { + constructor(game, x, y) { + this.game = game; + this.x = x; + this.y = y; + this.image = document.getElementById('gears'); + this.frameX = Math.floor(Math.random() * 3); + this.frameY = Math.floor(Math.random() * 3); + this.spriteSize = 50; + this.sizeModifier = (Math.random() * 0.5 + 0.5).toFixed(1); + this.size = this.spriteSize * this.sizeModifier; + this.speedX = Math.random() * 6 - 3; + this.speedY = Math.random() * -15; + this.gravity = 0.5; + this.markedForDeletion = false; + this.angle = 0; + this.va = Math.random() * 0.2 - 0.1; + this.bounced = 0; + this.bottomBounceBoundary = Math.random() * 80 + 60; + } + update() { + this.angle += this.va; + this.speedY += this.gravity; + this.x -= this.speedX - this.game.speed; + this.y += this.speedY; + if (this.y > this.game.height + this.size || this.x < 0 - this.size) this.markedForDeletion = true; + if (this.y > this.game.height - this.bottomBounceBoundary && this.bounced < 5) { + this.bounced++; + this.speedY *= -0.7; + } + } + draw(context) { + context.save(); + context.translate(this.x, this.y); + context.rotate(this.angle); + context.drawImage(this.image, this.frameX * this.spriteSize, + this.frameY * this.spriteSize, this.spriteSize, this.spriteSize, this.size * -0.5, + this.size * -0.5, this.size, this.size); + context.restore(); + } + } + + class Player { + constructor(game) { + this.game = game; + this.width = 120; + this.height = 190; + this.x = 20; + this.y = 100; + this.frameX = 0; + this.frameY = 0; + this.maxFrame = 37; + this.speedY = 0; + this.maxSpeed = 3; + this.projectiles = []; + this.image = document.getElementById('player'); + this.powerUp = false; + this.powerUpTimer = 0; + this.powerUpLimit = 10000; + } + update(deltaTime) { + if (this.game.keys.includes('ArrowUp')) this.speedY = -this.maxSpeed; + else if (this.game.keys.includes('ArrowDown')) this.speedY = this.maxSpeed; + else this.speedY = 0; + this.y += this.speedY; + // vertical boundaries + if (this.y > this.game.height - this.height * 0.5) this.y = this.game.height - this.height * 0.5; + else if (this.y < -this.height * 0.5) this.y - this.height * 0.5; + //handle projectiles + this.projectiles.forEach(projectile => { + projectile.update(); + }); + this.projectiles = this.projectiles.filter(projectile => !projectile.markedForDeletion); + //sprite animation + if (this.frameX < this.maxFrame) { + this.frameX++; + } else { + this.frameX = 0; + } + // power up + if (this.powerUp) { + if (this.powerUpTimer > this.powerUpLimit) { + this.powerUpTimer = 0; + this.powerUp = false; + this.frameY = 0; + } else { + this.powerUpTimer += deltaTime; + this.frameY = 1; + this.game.ammo += 0.1; + } + } + } + draw(context) { + if (this.game.debug) context.strokeRect(this.x, this.y, this.width, this.height); + this.projectiles.forEach(projectile => { + projectile.draw(context); + }); + context.drawImage(this.image, this.frameX * this.width, this.frameY * this.height, this.width, this.height, this.x, this.y, this.width, this.height); + + } + shootTop() { + if (this.game.ammo > 0) { + this.projectiles.push(new Projectile(this.game, this.x + 80, this.y + 30)); + this.game.ammo--; + } + if (this.powerUp) this.shootBottom(); + } + shootBottom() { + if (this.game.ammo > 0) { + this.projectiles.push(new Projectile(this.game, this.x + 80, this.y + 175)); + } + } + + enterPowerUp() { + this.powerUpTimer = 0; + this.powerUp = true; + if (this.game.ammo < this.game.maxAmmo) this.game.ammo = this.game.maxAmmo; + } + } + class Enemy { + constructor(game) { + this.game = game; + this.x = this.game.width; + this.speedX = Math.random() * -1.5 - 0.5; + this.markedForDeletion = false; + this.frameX = 0; + this.frameY = 0; + this.maxFrame = 37; + } + update() { + this.x += this.speedX - this.game.speed; + if (this.x + this.width < 0) this.markedForDeletion = true; + // sprite animation + if (this.frameX < this.maxFrame) { + this.frameX++; + } else this.frameX = 0; + } + draw(context) { + if (this.game.debug) context.strokeRect(this.x, this.y, this.width, this.height); + context.drawImage(this.image, this.frameX * this.width, this.frameY * this.height, this.width, this.height, this.x, this.y, this.width, this.height); + if (this.game.debug) { + context.font = '20px Bangers'; + context.fillText(this.lives, this.x, this.y); + } + } + } + class Angler1 extends Enemy { + constructor(game) { + super(game); + this.width = 228; + this.height = 169; + this.y = Math.random() * (this.game.height * 0.95 - this.height); + this.image = document.getElementById('angler1'); + this.frameY = Math.floor(Math.random() * 3); + this.lives = 5; + this.score = this.lives; + } + + } + class Angler2 extends Enemy { + constructor(game) { + super(game); + this.width = 213; + this.height = 165; + this.y = Math.random() * (this.game.height * 0.95 - this.height); + this.image = document.getElementById('angler2'); + this.frameY = Math.floor(Math.random() * 2); + this.lives = 6; + this.score = this.lives; + } + + } + + class LuckyFish extends Enemy { + constructor(game) { + super(game); + this.width = 99; + this.height = 95; + this.y = Math.random() * (this.game.height * 0.95 - this.height); + this.image = document.getElementById('lucky'); + this.frameY = Math.floor(Math.random() * 2); + this.lives = 5; + this.score = 15; + this.type = 'lucky' + } + } + + class HiveWhale extends Enemy { + constructor(game) { + super(game); + this.width = 400; + this.height = 227; + this.y = Math.random() * (this.game.height * 0.95 - this.height); + this.image = document.getElementById('hivewhale'); + this.frameY = 0; + this.lives = 20; + this.score = this.lives; + this.type = 'hive'; + this.speedX = Math.random() * -1.2 - 0.2; + } + } + + class Drone extends Enemy { + constructor(game, x, y) { + super(game); + this.width = 115; + this.height = 95; + this.x = x; + this.y = y; + this.image = document.getElementById('drone'); + this.frameY = Math.floor(Math.random() * 2); + this.lives = 3; + this.score = this.lives; + this.type = 'drone'; + this.speedX = Math.random() * -4.2 - 0.5; + } + } + + + class Layer { + constructor(game, image, speedModifier) { + this.game = game; + this.image = image; + this.speedModifier = speedModifier; + this.width = 1768; + this.height = 500; + this.x = 0; + this.y = 0; + } + update() { + if (this.x <= -this.width) this.x = 0; + this.x -= this.game.speed * this.speedModifier; + } + draw(context) { + context.drawImage(this.image, this.x, this.y); + context.drawImage(this.image, this.x + this.width, this.y); + } + } + + class Background { + constructor(game) { + this.game = game; + this.image1 = document.getElementById('layer1'); + this.image2 = document.getElementById('layer2'); + this.image3 = document.getElementById('layer3'); + this.image4 = document.getElementById('layer4'); + this.layer1 = new Layer(this.game, this.image1, 0.2); + this.layer2 = new Layer(this.game, this.image2, 0.4); + this.layer3 = new Layer(this.game, this.image3, 1); + this.layer4 = new Layer(this.game, this.image4, 1.5); + this.layers = [this.layer1, this.layer2, this.layer3, this.layer4]; + } + update() { + this.layers.forEach(layer => layer.update()); + } + draw(context) { + this.layers.forEach(layer => layer.draw(context)); + } + + } + + class Explosion { + constructor(game, x, y) { + this.game = game; + this.frameX = 0; + this.spriteWidth = 200; + this.spriteHeight = 200; + this.width = this.spriteWidth; + this.height = this.spriteHeight; + this.x = x - this.width * 0.5; + this.y = y - this.height * 0.5; + this.fps = 30; + this.time = 0; + this.interval = 1000 / this.fps; + this.markedForDeletion = false; + this.maxFrame = 8; + } + update(deltaTime) { + this.x -= this.game.speed; + if (this.timer > this.interval) { + this.frameX++; + this.timer = 0; + } else { + this.timer += deltaTime; + } + if (this.frameX > this.maxFrame) this.markedForDeletion = true; + } + draw(context) { + context.drawImage(this.image, this.frameX * this.spriteWidth, 0, this.spriteWidth, + this.spriteHeight, this.x, this.y, this.width, this.height); + } + } + + class SmokeExplosion extends Explosion { + constructor(game, x, y) { + super(game, x, y); + this.image = document.getElementById('smokeExplosion'); + } + } + + class FireExplosion extends Explosion { + constructor(game, x, y) { + super(game, x, y); + this.image = document.getElementById('fireExplosion'); + } + } + + + class UI { + constructor(game) { + this.game = game; + this.fontSize = 25; + this.fontFamily = 'Bangers'; + this.color = 'yellow'; + } + draw(context) { + context.save(); + context.fillStyle = this.color; + context.shadowOffsetX = 2; + context.shadowOffsetY = 2; + context.shadowColor = 'black'; + context.font = this.fontSize + 'px' + this.fontFamily; + //score + context.font = '20px Bangers'; + context.fillText('Score: ' + this.game.score, 20, 40); + + //timer + const formattedTime = (this.game.gameTime * 0.001).toFixed(1); + context.font = '20px Bangers'; + context.fillText('Timer:' + formattedTime, 20, 100); + + //game over messages + if (this.game.gameOver) { + context.textAlign = 'center'; + let message1; + let message2; + if (this.game.score > this.game.winningScore) { + message1 = 'Most Wondrous!'; + message2 = 'Well Done explorer!'; + } else { + message1 = 'Blazes!'; + message2 = 'Get my repair kit and try again!'; + } + context.font = '50px Bangers'; + context.fillText(message1, this.game.width * 0.5, this.game.height * 0.5 - 20); + context.font = '25px Bangers'; + context.fillText(message2, this.game.width * 0.5, this.game.height * 0.5 + 20); + } + //ammo + if (this.game.player.powerUp) context.fillStyle = '#ffffbd'; + for (let i = 0; i < this.game.ammo; i++) { + context.fillRect(20 + 5 * i, 50, 3, 20); + } + context.restore(); + } + } + class Game { + constructor(width, height) { + this.width = width; + this.height = height; + this.background = new Background(this); + this.player = new Player(this); + this.input = new InputHandler(this); + this.ui = new UI(this); + this.keys = []; + this.enemies = []; + this.particles = []; + this.explosions = []; + this.enemyTimer = 0; + this.enemyInterval = 2000; + this.ammo = 20; + this.maxAmmo = 50; + this.ammoTimer = 0; + this.ammoInterval = 350; + this.gameOver = false; + this.score = 0; + this.winningScore = 80; + this.gameTime = 0; + this.timeLimit = 30000; + this.speed = 1; + this.debug = false; + + } + update(deltaTime) { + if (!this.gameOver) this.gameTime += deltaTime; + if (this.gameTime > this.timeLimit) this.gameOver = true; + this.background.update(); + this.background.layer4.update(); + this.player.update(deltaTime); + if (this.ammoTimer > this.ammoInterval) { + if (this.ammo < this.maxAmmo) this.ammo++; + this.ammoTimer = 0; + } else { + this.ammoTimer += deltaTime; + } + this.particles.forEach(particle => particle.update()); + this.particles = this.particles.filter(particle => !particle.markedForDeletion); + this.explosions.forEach(explosion => explosion.update(deltaTime)); + this.explosions = this.explosions.filter(explosion => !explosion.markedForDeletion); + this.enemies.forEach(enemy => { + enemy.update(); + if (this.checkCollision(this.player, enemy)) { + enemy.markedForDeletion = true; + this.addExplosion(enemy); + for (let i = 0; i < enemy.score; i++) { + this.particles.push(new Particle(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5)); + } + if (enemy.type === 'lucky') this.player.enterPowerUp(); + else if (!this.gameOver) this.score--; + } + this.player.projectiles.forEach(projectile => { + if (this.checkCollision(projectile, enemy)) { + enemy.lives--; + projectile.markedForDeletion = true; + this.particles.push(new Particle(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5)); + if (enemy.lives <= 0) { + for (let i = 0; i < enemy.score; i++) { + this.particles.push(new Particle(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5)); + } + enemy.markedForDeletion = true; + this.addExplosion(enemy); + if (enemy.type === 'hive') { + for (let i = 0; i < 5; i++) { + this.enemies.push(new Drone(this, enemy.x + Math.random() * enemy.width, + enemy.y + Math.random() * enemy.height + 0.5)); + } + } + if (!this.gameOver) this.score += enemy.score; + /*if (this.score > this.winningScore) this.gameOver = true;*/ + } + } + }); + }); + this.enemies = this.enemies.filter(enemy => !enemy.markedForDeletion); + if (this.enemyTimer > this.enemyInterval && !this.gameOver) { + this.addEnemy(); + this.enemyTimer = 0; + } else { + this.enemyTimer += deltaTime; + } + } + draw(context) { + this.background.draw(context); + this.ui.draw(context); + this.player.draw(context); + this.particles.forEach(particle => particle.draw(context)); + this.enemies.forEach(enemy => { + enemy.draw(context); + }); + this.explosions.forEach(explosion => { + explosion.draw(context); + }); + this.background.layer4.draw(context); + } + addEnemy() { + const randomize = Math.random(); + if (randomize < 0.3) this.enemies.push(new Angler1(this)); + else if (randomize < 0.6) this.enemies.push(new Angler2(this)); + else if (randomize < 0.7) this.enemies.push(new HiveWhale(this)); + else this.enemies.push(new LuckyFish(this)); + } + addExplosion(enemy) { + const randomize = Math.random(); + if (randomize < 0.5) { + this.explosions.push(new SmokeExplosion(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5)); + } + else { + this.explosions.push(new FireExplosion(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5)); + } + } + checkCollision(rect1, rect2) { + return (rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.height + rect1.y > rect2.y) + } + + } + + const game = new Game(canvas.width, canvas.height); + let lastTime = 0; + //animation loop + function animate(timeStamp) { + const deltaTime = timeStamp - lastTime; + console.log(deltaTime); + lastTime = timeStamp; + ctx.clearRect(0, 0, canvas.width, canvas.height); + game.draw(ctx); + game.update(deltaTime); + requestAnimationFrame(animate); + } + animate(0); +}); + diff --git a/Games/Alien_Invasion/style.css b/Games/Alien_Invasion/style.css new file mode 100644 index 0000000000..268a5f3b93 --- /dev/null +++ b/Games/Alien_Invasion/style.css @@ -0,0 +1,34 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +#canvas1 { + border: 5px solid black; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #4d79bc; + max-width: 100%; + max-height: 100%; + font-family: 'Bangers', cursive; +} + +#layer1, +#layer2, +#layer3, +#layer4, +#player, +#angler1, +#angler2, +#lucky, +#projectile, +#gears, +#hivewhale, +#drone, +#smokeExplosion, +#fireExplosion { + display: none; +} \ No newline at end of file diff --git a/README.md b/README.md index 9beae2b51a..593eddf563 100644 --- a/README.md +++ b/README.md @@ -346,14 +346,11 @@ This repository also provides one such platforms where contributers come over an |[Penguins Cant Fly](https://github.com/Will2Jacks/GameZoneForked/tree/Task/Games/Penguins_Cant_Fly)| |[GoFish](https://github.com/kunjgit/GameZone/tree/main/Games/GoFish)| | [Taash_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Taash_Game)| - | [Intellect Quest](https://github.com/Will2Jacks/GameZoneForked/tree/Task/Games/Intellect_Quest) | | [Number_Guessing_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Number_Guessing_Game) | | [Modulo_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Modulo_Game) | - -======= +| [Alien_Invasion](https://github.com/kunjgit/GameZone/tree/main/Games/Alien_Invasion) | | [Drawing_App](https://github.com/kunjgit/GameZone/tree/main/Games/Drawing_app) | - |[Town-Rise](https://github.com/kunjgit/GameZone/tree/main/Games/Town_Rise_Game)| | [IKnowYou-Mind-Reading-Game](https://github.com/kunjgit/GameZone/tree/main/Games/IKnowYou-Mind-Reading-Game) | diff --git a/assets/images/Alien_Invasion.png b/assets/images/Alien_Invasion.png new file mode 100644 index 0000000000..8629cd684e Binary files /dev/null and b/assets/images/Alien_Invasion.png differ