การสร้างเกมแพลตฟอร์มด้วย HTML5 Canvas และ JavaScript

 


DEMO

ดาวน์โหลดทรัพยากรของเกม


บทนำ

เกมแพลตฟอร์มเป็นแนวเกมที่ได้รับความนิยมมาก เนื่องจากมีรูปแบบการเล่นที่เข้าใจง่ายและสามารถพัฒนาได้หลากหลายรูปแบบ ในบทความนี้ เราจะอธิบายการสร้างเกมแพลตฟอร์มโดยใช้ HTML5 Canvas และ JavaScript พร้อมอธิบายโครงสร้างโค้ดทั้งหมดอย่างละเอียด

โครงสร้างหลักของโค้ด

โค้ดที่เราใช้พัฒนาเกมนี้แบ่งออกเป็นส่วนต่างๆ ได้แก่:

  1. การกำหนดค่าเริ่มต้นและการสร้าง Canvas
  2. การโหลด sprite และการแสดงผลตัวละคร
  3. การสร้างแพลตฟอร์มและไอเท็ม
  4. การควบคุมตัวละคร
  5. การอัปเดตและแสดงผลเกม
  6. การตรวจจับการชนและกฎของเกม

1. การกำหนดค่าเริ่มต้นและการสร้าง Canvas

ในส่วนแรกของโค้ด เราจะกำหนดค่าเริ่มต้นและสร้าง Canvas สำหรับการแสดงผลของเกม

const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 400;


อธิบายการทำงาน:

  • document.getElementById("gameCanvas") ใช้เพื่อดึง <canvas> จาก HTML
  • getContext("2d") ใช้สำหรับการวาดภาพแบบ 2 มิติบน Canvas
  • กำหนดขนาดของ Canvas เป็น 800x400 พิกเซล

2. การโหลด Sprite และการแสดงผลตัวละคร

const player = {
  x: 50,
  y: 300,
  width: 40,
  height: 50,
  color: "blue",
  speed: 5,
  dx: 0,
  dy: 0,
  gravity: 0.5,
  jumpPower: -10,
  onGround: false
};


อธิบายการทำงาน:

  • player.x, player.y เป็นตำแหน่งของตัวละคร
  • width, height เป็นขนาดของตัวละคร
  • color กำหนดสีของตัวละคร
  • speed เป็นความเร็วการเคลื่อนที่ในแนวราบ
  • dx, dy เป็นตัวแปรสำหรับความเร็วของตัวละคร
  • gravity จำลองแรงโน้มถ่วงให้ตัวละครตกลงสู่พื้น
  • jumpPower กำหนดแรงกระโดด
  • onGround ใช้ตรวจสอบว่าตัวละครอยู่บนพื้นหรือไม่

3. การสร้างแพลตฟอร์มและไอเท็ม

const platforms = [
  { x: 0, y: 350, width: 800, height: 50, color: "green" },
  { x: 200, y: 250, width: 150, height: 20, color: "brown" },
  { x: 400, y: 200, width: 100, height: 20, color: "brown" }
];


อธิบายการทำงาน:

  • platforms เป็นอาร์เรย์ที่เก็บข้อมูลของแพลตฟอร์มทั้งหมดในเกม
  • กำหนดค่าตำแหน่ง (x, y), ขนาด (width, height), และสี (color) ให้แต่ละแพลตฟอร์ม

4. การควบคุมตัวละคร

document.addEventListener("keydown", (e) => {
  if (e.key === "ArrowRight") player.dx = player.speed;
  if (e.key === "ArrowLeft") player.dx = -player.speed;
  if (e.key === "ArrowUp" && player.onGround) {
    player.dy = player.jumpPower;
    player.onGround = false;
  }
});

document.addEventListener("keyup", (e) => {
  if (e.key === "ArrowRight" || e.key === "ArrowLeft") player.dx = 0;
});


อธิบายการทำงาน:

  • ตรวจจับการกดปุ่มลูกศร (keydown) เพื่อให้ตัวละครเคลื่อนที่ไปทางซ้าย, ขวา และกระโดด
  • keyup ใช้เพื่อลดความเร็ว (dx) เมื่อปล่อยปุ่ม
  • ArrowUp ใช้กระโดด โดยจะกระโดดได้เมื่อ onGround เป็น true

5. การอัปเดตและแสดงผลเกม

function update() {
  player.dy += player.gravity;
  player.x += player.dx;
  player.y += player.dy;

  if (player.y + player.height > canvas.height) {
    player.y = canvas.height - player.height;
    player.dy = 0;
    player.onGround = true;
  }
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = player.color;
  ctx.fillRect(player.x, player.y, player.width, player.height);

  platforms.forEach(platform => {
    ctx.fillStyle = platform.color;
    ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
  });
}

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


อธิบายการทำงาน:

  • update() อัปเดตค่าตำแหน่งของตัวละครและใช้แรงโน้มถ่วง
  • draw() วาดตัวละครและแพลตฟอร์มลงบน Canvas
  • gameLoop() เรียกใช้ update() และ draw() อย่างต่อเนื่องโดยใช้ requestAnimationFrame()

6. การตรวจจับการชน

platforms.forEach(platform => {
  if (
    player.x < platform.x + platform.width &&
    player.x + player.width > platform.x &&
    player.y + player.height > platform.y &&
    player.y + player.height - player.dy < platform.y + 10
  ) {
    player.y = platform.y - player.height;
    player.dy = 0;
    player.onGround = true;
  }
});


อธิบายการทำงาน:

  • ตรวจสอบว่าตัวละครชนกับแพลตฟอร์มหรือไม่
  • ถ้าชนกับพื้นของแพลตฟอร์ม จะหยุดการตก (dy = 0) และกำหนด onGround = true

สรุป

เกมแพลตฟอร์มนี้มีโครงสร้างหลักที่ครบถ้วน ตั้งแต่การสร้าง Canvas, โหลด sprite, การควบคุมตัวละคร, การแสดงผล และการตรวจจับการชนกับแพลตฟอร์ม คุณสามารถนำโค้ดนี้ไปพัฒนาต่อยอดได้ เช่น เพิ่มศัตรู, ไอเท็ม หรือระบบคะแนน!

ตัวอย่างเกม

     
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Platform Game</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            font-family: Arial, sans-serif;
        }
        #menu {
            text-align: center;
        }
        #menu h1 {
            font-size: 3rem;
            margin-bottom: 20px;
        }
        #menu button {
            padding: 15px 30px;
            font-size: 1.2rem;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        #menu button:hover {
            background-color: #45a049;
        }
        canvas {
            border: 1px solid black;
            display: none; /* ซ่อน Canvas จนกว่าเกมจะเริ่ม */
        }
        #gameOverModal {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 20px;
            border-radius: 10px;
            z-index: 1000;
        }
        #gameOverModal h2 {
            margin: 0;
        }
        #gameOverModal button {
            margin-top: 20px;
            padding: 10px 20px;
            background-color: red;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        #gameOverModal button:hover {
            background-color: darkred;
        }
    </style>
</head>
<body>
    <div id="menu">
        <h1>Platform Game</h1>
        <button id="startButton">Start Game</button>
    </div>
    <canvas id="gameCanvas" width="800" height="600"></canvas>
    <div id="gameOverModal">
        <h2>Game Over!</h2>
        <p>Your score: <span id="finalScore"></span></p>
        <button id="restartButton">Restart Game</button>
    </div>
    <script>
        // JavaScript สำหรับควบคุมหน้าเมนูและเริ่มเกม
        const menu = document.getElementById('menu');
        const startButton = document.getElementById('startButton');
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const gameOverModal = document.getElementById('gameOverModal');
        const restartButton = document.getElementById('restartButton');
        const finalScoreDisplay = document.getElementById('finalScore');

        startButton.addEventListener('click', () => {
            menu.style.display = 'none'; // ซ่อนหน้าเมนู
            canvas.style.display = 'block'; // แสดง Canvas
            resetGame(); // เริ่มเกม
        });

        // ส่วนที่เหลือของโค้ดเกม (ตามโค้ดเดิมของคุณ)
        const player = {
            x: 50,
            y: canvas.height - 150 - 150,
            width: 150,
            height: 150,
            speed: 5,
            velocityX: 0,
            velocityY: 0,
            jumping: false,
            sprite: {
                Idle: [],
                Run: [],
                RunLeft: [],
                Jump: [],
                Dead: []
            },
            currentSprite: [],
            frameIndex: 0,
            facingLeft: false,
            lastFrameTime: 0,
        };

        const background = {
            x: 0,
            image: new Image(),
            speed: 2.5
        };

        let items = [];
        const itemImages = [];
        const itemScores = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
        let itemInterval;
        let score = 0;

        let enemies = [];
        const enemyImages = [];
        let enemyInterval;
        let hp = 5;

        let animationFrameId;
        let isGameRunning = false;

        const frameDuration = 1000 / 16;

        background.image.src = './img/background.png';
        background.image.onload = () => {
            console.log("Background image loaded successfully.");
        };
        background.image.onerror = () => {
            console.error("Failed to load background image.");
        };

        function loadSprite(spriteType, count) {
            for (let i = 1; i <= count; i++) {
                const img = new Image();
                img.src = './img/' + spriteType + '(' + i + ').png';
                img.onload = () => {
                    player.sprite[spriteType].push(img);
                    console.log(`Loaded ${spriteType} sprite: ${img.src}`);
                    if (spriteType === 'Idle' && player.sprite[spriteType].length === count) {
                        player.currentSprite = player.sprite.Idle;
                        update(performance.now());
                    }
                };
                img.onerror = () => {
                    console.error(`Failed to load image: ${img.src}`);
                };
            }
        }

        function loadItemImages(count) {
            for (let i = 1; i <= count; i++) {
                const img = new Image();
                img.src = './img/item' + i + '.png';
                img.onload = () => {
                    itemImages.push(img);
                    console.log(`Loaded item image: ${img.src}`);
                };
                img.onerror = () => {
                    console.error(`Failed to load item image: ${img.src}`);
                };
            }
        }

        function loadEnemyImages(count) {
            for (let i = 1; i <= count; i++) {
                const img = new Image();
                img.src = './img/enemy' + i + '.png';
                img.onload = () => {
                    enemyImages.push(img);
                    console.log(`Loaded enemy image: ${img.src}`);
                };
                img.onerror = () => {
                    console.error(`Failed to load enemy image: ${img.src}`);
                };
            }
        }

        loadSprite('Idle', 10);
        loadSprite('Run', 8);
        loadSprite('Jump', 10);
        loadSprite('Dead', 10);

        loadItemImages(12);
        loadEnemyImages(4);

        function drawBackground() {
            if (background.image.complete) {
                ctx.drawImage(background.image, background.x, 0, canvas.width, canvas.height);
                if (background.x < 0) {
                    ctx.drawImage(background.image, background.x + canvas.width, 0, canvas.width, canvas.height);
                } else if (background.x > 0) {
                    ctx.drawImage(background.image, background.x - canvas.width, 0, canvas.width, canvas.height);
                }
                if (background.x + canvas.width < canvas.width) {
                    ctx.drawImage(background.image, background.x + canvas.width, 0, canvas.width, canvas.height);
                }
            } else {
                console.warn("Background image not loaded.");
            }
        }

        function drawPlayer() {
            const sprite = player.currentSprite[player.frameIndex];
            if (sprite && sprite.complete) {
                if (player.facingLeft) {
                    ctx.save();
                    ctx.translate(player.x + player.width, player.y);
                    ctx.scale(-1, 1);
                    ctx.drawImage(sprite, 0, 0, player.width, player.height);
                    ctx.restore();
                } else {
                    ctx.drawImage(sprite, player.x, player.y, player.width, player.height);
                }
            } else {
                console.warn("Player sprite not loaded or undefined.", sprite);
            }
        }

        function drawItems() {
            items.forEach(item => {
                if (item.image && item.image.complete) {
                    ctx.drawImage(item.image, item.x, item.y, 50, 50);
                } else {
                    console.warn("Item image not loaded or undefined.", item);
                }
            });
        }

        function drawEnemies() {
            enemies.forEach(enemy => {
                if (enemy.image && enemy.image.complete) {
                    ctx.drawImage(enemy.image, enemy.x, enemy.y, enemy.width, enemy.height);
                } else {
                    console.warn("Enemy image not loaded or undefined.", enemy);
                }
            });
        }

        function drawText() {
            ctx.font = '24px Arial';
            ctx.fillStyle = 'red';
            ctx.fillText(`Score: ${score}`, 20, 30);

            ctx.fillStyle = 'green';
            ctx.fillText(`HP: ${hp}`, canvas.width - 100, 30);
        }

        function updateBackground() {
            if (player.velocityX > 0 && player.x > canvas.width / 2 - player.width) {
                background.x -= player.velocityX * background.speed;
                player.x = canvas.width / 2 - player.width;
            } else if (player.velocityX < 0 && player.x <= 100) {
                background.x -= player.velocityX * background.speed;
                player.x = 100;
            }
            if (background.x <= -canvas.width) {
                background.x = 0;
            } else if (background.x >= canvas.width) {
                background.x = 0;
            }
        }

        function updateItems() {
            items.forEach(item => {
                item.x -= background.speed;
            });
            items = items.filter(item => item.x + 50 > 0);
        }

        function updateEnemies() {
            enemies.forEach(enemy => {
                const enemySpeed = player.jumping ? 10 : 8;
                enemy.x -= enemySpeed;
                console.log(enemySpeed)
            });
            enemies = enemies.filter(enemy => enemy.x + enemy.width > 0);
        }

        function checkCollisions() {
            items.forEach((item, index) => {
                if (player.x - 50 < item.x &&
                    player.x + player.width > item.x &&
                    player.y < item.y &&
                    player.y-50 + player.height > item.y) {
                    score += item.score;
                    items.splice(index, 1);
                }
            });

            enemies.forEach((enemy, index) => {
                if (player.x + 80 < enemy.x + enemy.width &&     // ด้านขวาของผู้เล่น > ด้านซ้ายของศัตรู
                    player.x + player.width > enemy.x + 80 &&    // ด้านซ้ายของผู้เล่น < ด้านขวาของศัตรู
                    player.y < enemy.y + enemy.height&&          // && ด้านล่างของผู้เล่น > ด้านบนของศัตรู
                    player.y + player.height > enemy.y) {        // ด้านบนของผู้เล่น < ด้านล่างของศัตรู
                    hp -= 1;
                    enemies.splice(index, 1);
                    if (hp <= 0) {
                        showGameOver();
                    }
                }
            });
        }

        function update(currentTime) {
            if (!isGameRunning) return;

            const deltaTime = currentTime - player.lastFrameTime;
            if (deltaTime >= frameDuration) {
                player.frameIndex = (player.frameIndex + 1) % player.currentSprite.length;
                player.lastFrameTime = currentTime;
            }

            player.x += player.velocityX;
            player.y += player.velocityY;

            if (player.x < 0) {
                player.x = 0;
            } else if (player.x + player.width > canvas.width) {
                player.x = canvas.width - player.width;
            }

            if (player.jumping) {
                player.velocityY += 0.25;
                if (player.y + player.height >= canvas.height - 150) {
                    player.y = canvas.height - 150 - player.height;
                    player.velocityY = 0;
                    player.jumping = false;
                    player.currentSprite = player.velocityX !== 0 ? player.sprite.Run : player.sprite.Idle;
                }
            }

            updateBackground();
            updateItems();
            updateEnemies();
            checkCollisions();

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawBackground();
            drawPlayer();
            drawItems();
            drawEnemies();
            drawText();

            animationFrameId = requestAnimationFrame(update);
        }

        document.addEventListener('keydown', (e) => {
            if (e.code === 'ArrowRight') {
                player.velocityX = player.speed;
                player.facingLeft = false;
                player.currentSprite = player.sprite.Run;
                startItemGeneration();
            } else if (e.code === 'ArrowLeft') {
                player.velocityX = -player.speed;
                player.facingLeft = true;
                player.currentSprite = player.sprite.Run;
                clearItemGeneration();
            } else if (e.code === 'ArrowUp' && !player.jumping) {
                player.velocityY = -9.5;
                player.jumping = true;
                player.currentSprite = player.sprite.Jump;
            }
        });

        document.addEventListener('keyup', (e) => {
            if (e.code === 'ArrowRight' || e.code === 'ArrowLeft') {
                player.velocityX = 0;
                player.currentSprite = player.velocityY !== 0 ? player.sprite.Jump : player.sprite.Idle;
                clearItemGeneration();
            }
        });

        function createItem() {
            const itemImage = itemImages[Math.floor(Math.random() * itemImages.length)];
            const itemScore = itemScores[Math.floor(Math.random() * itemScores.length)];
            const item = {
                image: itemImage,
                x: canvas.width,
                y: 150,
                score: itemScore
            };
            items.push(item);
        }

        function createEnemy() {
            const enemyImage = enemyImages[Math.floor(Math.random() * enemyImages.length)];
            const enemyWidth = 100;
            const enemy = {
                image: enemyImage,
                x: canvas.width,
                y: canvas.height - 150 - 100,
                width: enemyWidth,
                height: 100
            };
            enemies.push(enemy);
        }

        function startItemGeneration() {
            clearItemGeneration();
            const randomInterval = Math.floor(Math.random() * 5000) + 4000;
            itemInterval = setInterval(() => {
                createItem();
                const nextInterval = Math.floor(Math.random() * 5000) + 4000;
                clearInterval(itemInterval);
                itemInterval = setTimeout(startItemGeneration, nextInterval);
            }, randomInterval);
        }

        function startEnemyGeneration() {
            enemyInterval = setInterval(createEnemy, 8000);
        }

        function clearItemGeneration() {
            if (itemInterval) {
                clearInterval(itemInterval);
                clearTimeout(itemInterval);
                itemInterval = null;
            }
        }

        function clearEnemyGeneration() {
            if (enemyInterval) {
                clearInterval(enemyInterval);
                enemyInterval = null;
            }
        }

        function resetGame() {
            score = 0;
            hp = 5;
            items = [];
            enemies = [];
            player.x = 50;
            player.y = canvas.height - 150 - 150;
            player.velocityX = 0;
            player.velocityY = 0;
            player.jumping = false;
            player.currentSprite = player.sprite.Idle;
            background.x = 0;
            clearItemGeneration();
            clearEnemyGeneration();
            startItemGeneration();
            startEnemyGeneration();
            gameOverModal.style.display = 'none';
            isGameRunning = true;
            update(performance.now());
        }

        function showGameOver() {
            isGameRunning = false;
            cancelAnimationFrame(animationFrameId);
            clearItemGeneration();
            clearEnemyGeneration();
            finalScoreDisplay.textContent = score;
            gameOverModal.style.display = 'block';
        }

        restartButton.addEventListener('click', resetGame);

        startItemGeneration();
        startEnemyGeneration();
        player.lastFrameTime = performance.now();
        update(player.lastFrameTime);
    </script>
</body>
</html>

    


ขอให้สนุกกับการพัฒนาเกม! 

แสดงความคิดเห็น (0)
ใหม่กว่า เก่ากว่า