Skip to content

Instantly share code, notes, and snippets.

@e1blue
Created April 13, 2018 04:08
Show Gist options
  • Select an option

  • Save e1blue/f51635fc341741b332c67eb0f8fc0b03 to your computer and use it in GitHub Desktop.

Select an option

Save e1blue/f51635fc341741b332c67eb0f8fc0b03 to your computer and use it in GitHub Desktop.
Simple Shooter
<div class="container">
<div class="loading">טוען...</div>
<div class="cover">
<div class="status"></div>
<div class="instructions">
<div class="inner">
<div class="cell cell-1"></div>
<div class="cell cell-2 "></div>
<div class="cell cell-3"></div>
<div class="cell cell-4"></div>
<div class="show cell cell-5 key up"></div>
<div class="cell cell-6"></div>
<div class="cell cell-7"></div>
<div class="show cell cell-8 key shoot"></div>
<div class="cell cell-9"></div>
<div class="show cell cell-10 key right"></div>
<div class="show cell cell-11 key down"></div>
<div class="show cell cell-12 key left"></div>
<div class="show cell cell-13 label">יריה</div>
<div class="show cell cell-14 label">תזוזה</div>
</div>
</div>
<button class="play">התחל</button>
<button class="replay">שוב!</button>
</div>
<div class="player"></div>
<div class="enemy r-1 c-1"></div>
<div class="enemy r-1 c-2"></div>
<div class="enemy r-1 c-3"></div>
<div class="enemy r-1 c-4"></div>
<div class="enemy r-1 c-5"></div>
<div class="enemy r-1 c-6"></div>
<div class="enemy r-1 c-7"></div>
<div class="enemy r-2 c-1"></div>
<div class="enemy r-2 c-2"></div>
<div class="enemy r-2 c-3"></div>
<div class="enemy r-2 c-4"></div>
<div class="enemy r-2 c-5"></div>
<div class="enemy r-2 c-6"></div>
<div class="enemy r-2 c-7"></div>
</div>
<div class="credits">
מוזיקה: <a href="https://www.youtube.com/watch?v=XblaHyMf3uw" target="_blank">BRD - Teleport Pro</a>
</div>
<audio class="music menu" src="//Yarivfruend.com/Portfolio/simple-shooter/sound/start-loop.ogg"/></audio>
<audio class="music main" src="//Yarivfruend.com/Portfolio/simple-shooter/sound/game-loop.ogg"/></audio>
<audio class="sound shoot" src="//Yarivfruend.com/Portfolio/simple-shooter/sound/shoot.wav"/></audio>
<audio class="sound shoot-enemy" src="//Yarivfruend.com/Portfolio/simple-shooter/sound/shoot-enemy.wav"/></audio>
<audio class="sound win" src="//Yarivfruend.com/Portfolio/simple-shooter/sound/win.wav"/></audio>
<audio class="sound kill" src="//Yarivfruend.com/Portfolio/simple-shooter/sound/kill.wav"/></audio>
let game = {};
game.hasEnded = false;
game.keyPressMap = new Set(); // Keeps track of simulatneously pressed down keys
game.loading = document.querySelector('.loading');
game.container = document.querySelector('.container');
game.cover = document.querySelector('.cover');
game.cover.status = document.querySelector('.cover > .status');
game.cover.replayButton = document.querySelector('.cover > .replay');
game.cover.playButton = document.querySelector('.cover > .play');
game.sound = {}
game.sound.music = {}
game.sound.effects = {}
game.sound.music.menu = document.querySelector('.music.menu');
game.sound.music.main = document.querySelector('.music.main');
game.sound.effects.kill = document.querySelector('.sound.kill');
game.sound.effects.win = document.querySelector('.sound.win');
game.sound.effects.shoot = document.querySelector('.sound.shoot');
game.sound.effects.shootEnemy = document.querySelector('.sound.shoot-enemy');
// Set control values by the "key" property value of the event
game.controls = {};
game.controls.moveUp = 'ArrowUp';
game.controls.moveDown = 'ArrowDown';
game.controls.moveLeft = 'ArrowLeft';
game.controls.moveRight = 'ArrowRight';
game.controls.shoot = ' '; // Empty string means the Space button
game.player = {};
game.player.size = 4;
game.player.avatar = document.querySelector('.player');
game.player.moveSpeed = 4;
game.player.projectileSpeed = 10;
game.enemies = new Set();
game.enemies.moveSpeed = 1;
game.enemies.projectileSpeed = 3;
game.enemies.attackSpeed = 4; // Higher => Slower
game.projectileSize = 1;
game.explosionSize = 3;
game.showStart = () => {
game.loading.remove();
game.cover.classList.add('start');
game.cover.status.textContent = 'משחק יריות פשוט';
game.setInstructions();
game.cover.toggle('show');
game.sound.music.menu.play();
}
game.setInstructions = () => {
let instructions = document.querySelector('.instructions');
let keyUpInstruct = document.querySelector('.key.up');
let keyLeftInstruct = document.querySelector('.key.left');
let keyDownInstruct = document.querySelector('.key.down');
let keyRightInstruct = document.querySelector('.key.right');
let keyShootInstruct = document.querySelector('.key.shoot');
keyUpInstruct.textContent = (game.controls.moveUp === 'ArrowUp') ? '▲' : game.controls.moveUp;
keyLeftInstruct.textContent = (game.controls.moveLeft === 'ArrowLeft') ? '◄' : game.controls.moveLeft;
keyDownInstruct.textContent = (game.controls.moveDown === 'ArrowDown') ? '▼' : game.controls.moveDown;
keyRightInstruct.textContent = (game.controls.moveRight === 'ArrowRight') ? '►' : game.controls.moveRight;
keyShootInstruct.textContent = game.controls.shoot;
if (game.controls.shoot === ' ') {
instructions.style.padding = '15px 21px 15px 25px';
keyShootInstruct.textContent = 'space';
keyShootInstruct.style.width = '105px';
}
}
game.initialize = () => {
game.sound.music.menu.pause();
game.sound.music.main.play();
game.projectileSize = game.projectileSize * 10;
game.initializePlayer();
game.initializeEnemies();
document.addEventListener('keydown', game.handleKeyDown);
document.addEventListener('keyup', game.handleKeyUp);
}
game.initializePlayer = () => {
let player = game.player.avatar;
player.style.width = 10 * game.player.size + 'px';
player.style.height = 10 * game.player.size + 'px';
player.style.left = (game.container.clientWidth / 2) - (game.player.avatar.clientWidth / 2) + 'px'; // X
player.style.top = game.container.clientHeight - 100 + 'px'; // Y
}
game.initializeEnemies = () => {
let enemies = document.querySelectorAll('.enemy')
for (let enemy of enemies) {
game.enemies.add(enemy);
enemy.style.display = 'block';
}
game.enemies.oscillate();
game.enemies.attack = window.setInterval(() => {
game.enemies.shoot();
}, game.enemies.attackSpeed * 100);
}
game.createProjectile = (shooter) => {
let projectile = document.createElement("div");
projectile.classList.add('projectile');
projectile.style.height = game.projectileSize + 'px';
projectile.style.width = game.projectileSize / 2 + 'px';
projectile.style.left = (shooter.offsetLeft + shooter.clientWidth / 2 - game.projectileSize / 2) + 'px';
projectile.style.top = (shooter.offsetTop + shooter.clientHeight) + 'px';
projectile = projectile.cloneNode();
switch (shooter.classList[0]) {
case 'player':
projectile.classList.add('friendly');
projectile.style.top = shooter.offsetTop + 'px';
break;
}
game.container.append(projectile);
return projectile;
}
game.collision = (projectile) => {
let projectileTop = Number(projectile.style.top.replace(/\D/g, ''));
let projectileLeft = Number(projectile.style.left.replace(/\D/g, ''));
let projectileWidth = Number(window.getComputedStyle(projectile, null).getPropertyValue("width").replace(/\D/g, ''));
// Enemy is hit.
if (projectile.classList.contains('friendly')) {
for (let enemy of game.enemies) {
if (projectileTop < (enemy.offsetTop + enemy.clientHeight) &&
projectileLeft + projectileWidth / 2 > enemy.offsetLeft - projectileWidth / 2 &&
projectileLeft + projectileWidth / 2 < enemy.offsetLeft + enemy.clientWidth + projectileWidth / 2
) {
game.createExplosion(projectileTop, projectileLeft);
game.sound.effects.kill.currentTime = 0;
game.sound.effects.kill.play();
game.enemies.delete(enemy);
enemy.remove();
return true;
}
}
// Player is hit.
} else if (projectileTop + game.projectileSize > game.player.avatar.offsetTop &&
projectileTop < game.player.avatar.offsetTop + game.player.avatar.clientHeight &&
projectileLeft + projectileWidth / 2 > game.player.avatar.offsetLeft - projectileWidth / 2 &&
projectileLeft + projectileWidth / 2 < game.player.avatar.offsetLeft + game.player.avatar.clientWidth + projectileWidth / 2
) {
// Lose game when player is hit
if (!game.hasEnded) {
// projectile.style.backgroundColor = 'red';
game.isOver('defeat');
}
}
}
game.createExplosion = (hitLocationTop, hitLocationLeft) => {
let explosion = document.createElement("div");
explosion.classList.add('explosion');
explosion.style.height = game.explosionSize * 10 + 'px';
explosion.style.width = game.explosionSize * 10 + 'px';
explosion.style.left = hitLocationLeft + 'px';
explosion.style.top = hitLocationTop + 'px';
explosion = explosion.cloneNode();
game.container.append(explosion);
explosion.addEventListener('animationend', () => {
explosion.remove();
});
}
game.isOver = (result) => {
game.hasEnded = true;
document.removeEventListener('keydown', game.handleKeyDown);
document.removeEventListener('keyup', game.handleKeyUp);
game.enemies.projectileSpeed = 0;
game.player.projectileSpeed = 0;
clearInterval(game.enemies.attack);
game.enemies.clear();
switch (result) {
case 'victory':
game.sound.effects.win.play();
game.cover.status.classList.add('victory');
game.cover.classList.add('victory');
game.cover.status.textContent = 'ניצחון!';
break;
case 'defeat':
game.sound.effects.kill.currentTime = 0;
game.sound.effects.kill.play();
game.cover.status.classList.add('defeat');
game.cover.classList.add('defeat');
game.cover.status.textContent = 'game over';
break;
}
game.cover.toggle('show');
}
game.cover.toggle = (state) => {
switch (state) {
case 'show':
game.cover.classList.add('active');
break;
case 'hide':
game.cover.classList.remove('active');
break;
}
}
game.player.move = (direction) => {
(function step() {
switch (direction) {
case game.controls.moveUp:
if (game.player.avatar.offsetTop > 0) {
game.player.avatar.style.top = Number(game.player.avatar.style.top.replace(/\D/g, '')) - game.player.moveSpeed + 'px';
}
break;
case game.controls.moveDown:
if (game.player.avatar.offsetTop < game.container.clientHeight - game.player.avatar.clientHeight) {
game.player.avatar.style.top = Number(game.player.avatar.style.top.replace(/\D/g, '')) + game.player.moveSpeed + 'px';
}
break;
case game.controls.moveLeft:
if (game.player.avatar.offsetLeft > 0) {
game.player.avatar.style.left = Number(game.player.avatar.style.left.replace(/\D/g, '')) - game.player.moveSpeed + 'px';
}
break;
case game.controls.moveRight:
if (game.player.avatar.offsetLeft < game.container.clientWidth - game.player.avatar.clientWidth) {
game.player.avatar.style.left = Number(game.player.avatar.style.left.replace(/\D/g, '')) + game.player.moveSpeed + 'px';
}
break;
}
if (game.keyPressMap.has(direction) && !game.hasEnded) {
window.requestAnimationFrame(step);
}
})();
}
game.player.shoot = () => {
let projectile = game.createProjectile(game.player.avatar);
// Recoil animation
game.player.avatar.classList.remove('recoil');
game.player.avatar.classList.add('recoil');
game.player.avatar.addEventListener('animationend', () => {
game.player.avatar.classList.remove('recoil');
})
// Shooting sound
game.sound.effects.shoot.currentTime = 0;
game.sound.effects.shoot.play();
(function projection() {
projectile.style.top = Number(projectile.style.top.replace(/\D/g, '')) - game.player.projectileSpeed + 'px';
// Detect collision or exit of bounds
if (Number(projectile.style.top.replace(/\D/g, '')) > 5 && !game.collision(projectile)) {
window.requestAnimationFrame(projection);
} else {
projectile.remove();
// Win game when all enemies are defeated
if (!game.enemies.size) {
game.isOver('victory');
}
}
})();
}
game.enemies.oscillate = () => {
// Animate enemies
(function oscillation() {
for (var enemy of game.enemies) {
if (enemy.classList.contains('r-1')) {
enemy.style.left = enemy.offsetLeft + game.enemies.moveSpeed + 'px';
if (enemy.offsetLeft > game.container.clientWidth) {
enemy.style.left = 0 - enemy.clientWidth + 'px';
}
} else if (enemy.classList.contains('r-2')) {
enemy.style.left = enemy.offsetLeft - game.enemies.moveSpeed + 'px';
if ((enemy.offsetLeft + enemy.clientWidth) < 0) {
enemy.style.left = game.container.clientWidth + 'px';
}
}
}
if (game.enemies) {
window.requestAnimationFrame(oscillation);
}
})();
}
game.enemies.shoot = () => {
let enemyArr = Array.from(game.enemies);
let randomEnemy = enemyArr[Math.floor(Math.random() * enemyArr.length)];
let projectile = game.createProjectile(randomEnemy);
// Shooting sound
game.sound.effects.shootEnemy.currentTime = 0;
game.sound.effects.shootEnemy.play();
(function projection() {
projectile.style.top = Number(projectile.style.top.replace(/\D/g, '')) + game.enemies.projectileSpeed + 'px';
// Detect collision or exit of bounds
if (Number(projectile.style.top.replace(/\D/g, '')) < (game.container.clientHeight) && !game.collision(projectile)) {
window.requestAnimationFrame(projection);
} else {
projectile.remove();
}
})();
}
game.handleKeyDown = (e) => {
if (!game.keyPressMap.has(e.key)) {
game.keyPressMap.add(e.key);
if (e.key === game.controls.moveUp || e.key === game.controls.moveDown || e.key === game.controls.moveLeft || e.key === game.controls.moveRight) {
game.player.move(e.key);
} else if (e.key === game.controls.shoot) {
game.player.shoot();
}
}
}
game.handleKeyUp = (e) => {
game.keyPressMap.delete(e.key);
}
game.cover.playButton.addEventListener('click', () => {
game.cover.classList.remove('start');
game.cover.toggle('hide');
game.initialize();
})
game.cover.replayButton.addEventListener('click', () => {
window.location.reload(false);
})
window.addEventListener('load', () => {
game.showStart();
});
:root {
--background: white;
--accent: #333;
--player: white;
--enemy: #333;
--enemies-start-x: 38px;
--enemies-start-y: 40px;
}
body {
direction: rtl;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
flex-direction: column;
}
body,
button {
font-family: 'Varela Round', sans-serif;
}
.cover {
display: none;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: var(--background);
z-index: 1;
color: var(--accent);
justify-content: space-evenly;
align-items: center;
font-size: 50px;
text-transform: uppercase;
flex-direction: column;
}
.cover.active {
display: flex;
}
.cover>.status {
border: solid 3px #333;
border-radius: 10px;
padding: 0 15px;
}
.cover>.status.victory {
font-size: 70px;
border: 0;
color: white;
box-shadow: 0 0 0px 1px #ccc;
animation: vibrate 100ms linear infinite forwards, color-cycle 800ms linear infinite forwards;
}
.cover>.status.defeat {
padding-top: 3px;
background-color: orangered;
color: white;
border: 0;
animation: pulse 500ms cubic-bezier(0.12, 0.11, 0.27, 1.55) 1 forwards;
}
.instructions {
width: 45%;
font-size: 12px;
border: solid 3px #333;
border-radius: 10px;
padding: 15px 5px 15px 25px
}
.instructions>.inner {
display: grid;
grid-template-columns: repeat(6, 1fr);
text-align: center;
grid-gap: 3px;
}
.instructions>.inner>.cell:not(.show) {
visibility: hidden;
}
.instructions>.inner>.cell-13 {
grid-column: 1 / 4;
grid-row: 4;
}
.instructions>.inner>.cell-14 {
grid-column: 4 / 7;
grid-row: 4;
}
.instructions>.inner>.key {
background-color: #333;
color: white;
font-size: 20px;
height: 35px;
width: 35px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
}
.instructions>.inner>.label {
color: var(--accent);
background-color: transparent;
font-size: 16px;
font-weight: bold;
border-top: dotted 3px #333;
padding-top: 5px;
margin: 3px 25px 0;
}
button {
font-weight: bold;
cursor: pointer;
background-color: transparent;
border: solid 3px var(--accent);
font-size: 30px;
padding: 10px 10px;
border-radius: 10px;
transition: all 100ms ease;
text-transform: uppercase;
}
button:hover {
letter-spacing: 2px;
padding: 10px 13px;
border-radius: 8px;
}
button:active {
transform: scale(0.95);
}
.cover>button.replay,
.cover>button.play,
.instructions {
display: none;
}
.cover.active.victory>button.replay,
.cover.active.defeat>button.replay,
.cover.active.start>button.play,
.cover.active.start>.instructions {
display: block;
}
.cover,
.container {
border-radius: 20px;
}
.container {
display: flex;
justify-content: center;
align-items: center;
width: 500px;
height: 500px;
border: solid 3px var(--accent);
background-color: var(--background);
position: relative;
overflow: hidden;
}
.loading {
background-color: white;
z-index: 1;
font-size: 50px;
}
.player {
position: absolute;
background-color: var(--player);
border: solid 3px var(--accent);
border-radius: 50%;
}
.player.recoil {
animation: recoil 100ms ease 1 forwards;
}
.projectile {
border-radius: 5px;
position: absolute;
background-color: var(--enemy);
}
.projectile.friendly {
background-color: var(--player);
border: solid 3px var(--accent);
}
.explosion {
position: absolute;
background-color: var(--enemy);
border-radius: 50%;
animation: explode 200ms linear 1 forwards;
}
.enemy {
display: none;
position: absolute;
height: 40px;
width: 40px;
background-color: var(--enemy);
border-radius: 7px;
animation: rotate 1s linear infinite forwards;
}
.enemy.r-1 {
top: calc(var(--enemies-start-y) * 3);
}
.enemy.r-2 {
top: var(--enemies-start-y);
}
.enemy.c-1 {
left: var(--enemies-start-x);
}
.enemy.c-2 {
left: calc(var(--enemies-start-x) * 3);
}
.enemy.c-3 {
left: calc(var(--enemies-start-x) * 5);
}
.enemy.c-4 {
left: calc(var(--enemies-start-x) * 7);
}
.enemy.c-5 {
left: calc(var(--enemies-start-x) * 9);
}
.enemy.c-6 {
left: calc(var(--enemies-start-x) * 11);
}
.enemy.c-7 {
left: calc(var(--enemies-start-x) * 13);
}
.credits {
padding: 5px;
}
.credits>a {
color: var(--accent);
}
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
@keyframes recoil {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(0, 10px);
}
100% {
transform: translate(0, 0);
}
}
@keyframes vibrate {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(0, -5px);
}
100% {
transform: translate(0, 0);
}
}
@keyframes color-cycle {
0% {
background-color: orangered;
}
20% {
background-color: orange;
}
40% {
background-color: yellow;
}
60% {
background-color: limegreen;
}
80% {
background-color: dodgerblue;
}
100% {
background-color: orangered;
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
@keyframes explode {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment