<div id="eventBox">
  <canvas id="gameCanvas" width="800" height="800"></canvas>
  <div id="controls">
    <div id="leftButton" class="controlButton">←</div>
    <div id="shootButton" class="controlButton">SHOOT</div>
    <div id="rightButton" class="controlButton">→</div>
  </div>
  <button id="startButton">GAME START</button>
  <div id="couponLink"><a>こちらをクリック</a></div>
</div>
* {
  margin: 0;
  padding: 0;
}

body {
  background-color: #333;
}

#eventBox {
  position: relative;
  width: 100%;
  max-width: 800px;
  height: auto;
  margin: 0 auto;
}

canvas {
  background: black;
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  margin: 0 auto;
}

#startButton, #couponLink {
  display: block;
  margin: 20px auto;
  padding: 10px 20px;
  font-size: 20px;
  text-align: center;
}

#couponLink {
  position: absolute;
  top: 30%;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  display: none;
  font-family: sans-serif;
  height: 1.2em;
  text-decoration: none;
  color: white;
  cursor: pointer;
}

.controlButton {
  display: block;
  margin: 0;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  width: 40px;
  height: 30px;
  line-height: 30px;
  background-color: lightgray;
  border-radius: 4px;
}
#shootButton {
  width: 120px;
}
#controls {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 20px;
  column-gap: 10px;
}
const canvas = document.getElementById('gameCanvas');
const context = canvas.getContext('2d');
const startButton = document.getElementById('startButton');
const couponLink = document.getElementById('couponLink');
const leftButton = document.getElementById('leftButton');
const rightButton = document.getElementById('rightButton');
const shootButton = document.getElementById('shootButton');

const player = {
    x: ( canvas.width / 2 ) - 25,
    y: canvas.height - 50,
    width: 50,
    height: 50,
    speed: 5,
    dx: 0,
    dy: 0
};

const bullets = [];
const enemies = [];
const bosses = [];

const playerImage = new Image();
playerImage.src = 'https://nag-design.com/embed/shooting/player.svg';

const enemyImage = new Image();
enemyImage.src = 'https://nag-design.com/embed/shooting/enemy.svg';

const bossImage = new Image();
bossImage.src = 'https://nag-design.com/embed/shooting/boss.svg';

let enemySpawnInterval = 1000;
let bossSpawnInterval = 10000;
let lastEnemySpawnTime = Date.now();
let lastBossSpawnTime = Date.now();
let couponMessage = '';
let couponMessageTimeout = 0;
let gameState = 'start'; // 'start', 'playing', 'gameover', 'coupon'

document.addEventListener('keydown', movePlayer);
document.addEventListener('keyup', stopPlayer);
document.addEventListener('keydown', shootBullet);
startButton.addEventListener('click', startGame);
leftButton.addEventListener('mousedown', () => movePlayer({ key: 'ArrowLeft' }));
rightButton.addEventListener('mousedown', () => movePlayer({ key: 'ArrowRight' }));
shootButton.addEventListener('mousedown', () => shootBullet({ key: ' ' }));
leftButton.addEventListener('mouseup', stopPlayer);
rightButton.addEventListener('mouseup', stopPlayer);

leftButton.addEventListener('touchstart', (e) => {
    e.preventDefault();
    movePlayer({ key: 'ArrowLeft' });
});
rightButton.addEventListener('touchstart', (e) => {
    e.preventDefault();
    movePlayer({ key: 'ArrowRight' });
});
shootButton.addEventListener('touchstart', (e) => {
    e.preventDefault();
    shootBullet({ key: ' ' });
});
leftButton.addEventListener('touchend', (e) => {
    e.preventDefault();
    stopPlayer({ key: 'ArrowLeft' });
});
rightButton.addEventListener('touchend', (e) => {
    e.preventDefault();
    stopPlayer({ key: 'ArrowRight' });
});

function startGame() {
    gameState = 'playing';
    resetGame();
    startButton.style.display = 'none';
    couponLink.style.display = 'none';
}

function resetGame() {
    player.x = ( canvas.width / 2 ) - 25;
    player.y = canvas.height - 70;
    bullets.length = 0;
    enemies.length = 0;
    bosses.length = 0;
    lastEnemySpawnTime = Date.now();
    lastBossSpawnTime = Date.now();
}

function movePlayer(e) {
    if (gameState !== 'playing') return;
    if (e.key === 'ArrowRight') {
        player.dx = player.speed;
    } else if (e.key === 'ArrowLeft') {
        player.dx = -player.speed;
    }
}

function stopPlayer(e) {
    if (e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.type === 'mouseup' || e.type === 'touchend') {
        player.dx = 0;
    }
}

function shootBullet(e) {
    if (gameState !== 'playing') return;
    if (e.key === ' ') {
        bullets.push({
            x: player.x + player.width / 2 - 2.5,
            y: player.y,
            width: 5,
            height: 10,
            speed: 7
        });
    }
}

function update() {
    if (gameState !== 'playing') return;

    player.x += player.dx;
    player.y += player.dy;

    bullets.forEach((bullet, index) => {
        bullet.y -= bullet.speed;
        if (bullet.y < 0) {
            bullets.splice(index, 1);
        }
    });

    if (Date.now() - lastEnemySpawnTime > enemySpawnInterval) {
        enemies.push({
            x: Math.random() * (canvas.width - 50),
            y: 0,
            width: 50,
            height: 50,
            speed: 3
        });
        lastEnemySpawnTime = Date.now();
    }

    if (Date.now() - lastBossSpawnTime > bossSpawnInterval) {
        bosses.push({
            x: Math.random() * (canvas.width - 100),
            y: 0,
            width: 100,
            height: 100,
            speed: 2,
            hp: 2
        });
        lastBossSpawnTime = Date.now();
    }

    enemies.forEach((enemy, index) => {
        enemy.y += enemy.speed;
        if (enemy.y > canvas.height) {
            enemies.splice(index, 1);
        }
    });

    bosses.forEach((boss, index) => {
        boss.y += boss.speed;
        if (boss.y > canvas.height) {
            bosses.splice(index, 1);
        }
    });

    detectCollisions();
    checkGameOver();
}

function detectCollisions() {
    bullets.forEach((bullet, bulletIndex) => {
        enemies.forEach((enemy, enemyIndex) => {
            if (bullet.x < enemy.x + enemy.width &&
                bullet.x + bullet.width > enemy.x &&
                bullet.y < enemy.y + enemy.height &&
                bullet.y + bullet.height > enemy.y) {
                bullets.splice(bulletIndex, 1);
                enemies.splice(enemyIndex, 1);
            }
        });

        bosses.forEach((boss, bossIndex) => {
            if (bullet.x < boss.x + boss.width &&
                bullet.x + bullet.width > boss.x &&
                bullet.y < boss.y + boss.height &&
                bullet.y + bullet.height > boss.y) {
                bullets.splice(bulletIndex, 1);
                boss.hp -= 1;
                if (boss.hp <= 0) {
                    bosses.splice(bossIndex, 1);
                    displayCouponMessage();
                }
            }
        });
    });
}

function checkGameOver() {
    enemies.forEach((enemy) => {
        if (player.x < enemy.x + enemy.width &&
            player.x + player.width > enemy.x &&
            player.y < enemy.y + enemy.height &&
            player.y + player.height > enemy.y) {
            gameOver();
        }
    });

    bosses.forEach((boss) => {
        if (player.x < boss.x + boss.width &&
            player.x + player.width > boss.x &&
            player.y < boss.y + boss.height &&
            player.y + player.height > boss.y) {
            gameOver();
        }
    });
}

function gameOver() {
    gameState = 'gameover';
    startButton.style.display = 'block';
    startButton.textContent = 'RESTART';
}

function displayCouponMessage() {
    couponMessage = 'クーポンGET!';
    couponMessageTimeout = Date.now();
    gameState = 'coupon';
    couponLink.style.display = 'block';
}

function drawPlayer() {
    context.drawImage(playerImage, player.x, player.y, player.width, player.height);
}

function drawBullets() {
    context.fillStyle = 'red';
    bullets.forEach(bullet => {
        context.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
    });
}

function drawEnemies() {
    enemies.forEach(enemy => {
        context.drawImage(enemyImage, enemy.x, enemy.y, enemy.width, enemy.height);
    });
}

function drawBosses() {
    bosses.forEach(boss => {
        context.drawImage(bossImage, boss.x, boss.y, boss.width, boss.height);
    });
}

function drawCouponMessage() {
    if (couponMessage) {
        context.fillStyle = 'white';
        context.font = '30px Arial';
        context.textAlign = 'center';
        context.fillText(couponMessage, canvas.width / 2, canvas.height / 2);

        if (Date.now() - couponMessageTimeout > 2000) {
            couponMessage = '';
        }
    }
}

function drawStartScreen() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    drawPlayer();
}

function drawGameOverScreen() {
    context.fillStyle = 'white';
    context.font = '50px Arial';
    context.textAlign = 'center';
    context.fillText('GAME OVER', canvas.width / 2, canvas.height / 2);
}

function draw() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    if (gameState === 'start') {
        drawStartScreen();
    } else if (gameState === 'playing') {
        drawPlayer();
        drawBullets();
        drawEnemies();
        drawBosses();
        drawCouponMessage();
    } else if (gameState === 'gameover') {
        drawGameOverScreen();
    } else if (gameState === 'coupon') {
        drawCouponMessage();
    }
}

function gameLoop() {
    update();
    draw();
    requestAnimationFrame(gameLoop);
}

gameLoop();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.