Можете да се свържете с мен във facebook, twitter и google+

   
     Call 24 Hours: 1.888.222.5847

Javascript + Canvas = Snake на 196 реда

От доста време си мисля да направя един пост – колко лесно е да си направим класическата игра “Змия” (или в моя случай “червей”) използвайки Javascript, Canvas и доста простичък HTML/CSS. Ето какъв ще е крайния резултат:

ДЕМО

Общ преглед

За да реализираме нашата игра, трябва първо да се запознаем с canvas елемента, който е добавен в HTML5. Това представлява специален елемент, върху който можем да рисуваме 2D графики. Първо ще добавим един такъв елемент и ще създадем върху него една решетка, по която ще се движи нашата змия. Освен canvas елемента съм добавил и малко html и css за да може нашата игра да гали окото.

<html>
<head>
    <title>The worm game</title>
    <link rel="stylesheet" type="text/css" href="styles/default.css">
</head>
<body>
	<p>Welcome to the WORM GAME</p>
	<button id='startButton'>Start Game</button>
	<div class="gameWrapper">
		<div id='hiScores'>
			High scores:
			<div id="top10"></div>
		</div>
<!-- тук всъщност е нашия canvas елемент -->
	    <canvas id='wormGame' width='600' height='400'></canvas>
	    <div id="score"></div>
    </div>
</body>
</html>

 

Това е всичкия html, който трябва да напишем – от тук нататък запретваме ръкави и започваме с Javascript.

Игрова логика

Нека първо си създадем основния файл на играта, който аз съм кръстил wormify.js. Първо в него ще добавим основните константи, които са ни необходими. Обаче, тъй като в Javascript не съществува такова понятие като константа ще именуваме нашите променливи с главни букви, за да знаем, че трябва само да използваме техните стойности, но не и да ги променяме.

В променливите запазваме canvas-а и елементите, които ще пазят текущия резултат, както и топ резултатите. Също така създаваме и BLOCK_SIZE, който ще определя големината на една клетка в нашия canvas. Тази константа ще ни показва какъв е размера на една игрова клетка. Единствената друга по-интересна променлива е CONTEXT, която пази т.нар. canvas контекст, върху който ще се извърша реалното рисуване на игровите елементи.

var GAME_CANVAS = document.getElementById('wormGame');
    var CURRENT_SCORE_ELEMENT = document.getElementById('score');
    var TOP_SCORES_ELEMENT = document.getElementById('top10');
    var BLOCK_SIZE = 10;
    var GAME_WIDTH = GAME_CANVAS.clientWidth;
    var GAME_HEIGHT = GAME_CANVAS.clientHeight;
    var CONTEXT = GAME_CANVAS.getContext('2d');
    var NUMBER_OF_APPLES = 3;
    var PLAYER_SCORE = 0;
    var TOP_SCORES_TO_DISPLAY = 10;

 

Следващото нещо, което ни трябва е игрово блочке. Това ще бъде всеки един елемент, който искаме да изрисуваме – ябълка, главата и опашката на змията. Единственото което трябва да има всяко блокче са координати, които трябва да са в нашата мрежа – в конкретния пример позволяваме координатите ни да са само на такива, които се делят на 10, защото така сме решили да разделим логически canvas-а. Както и проста фунция, която генерира ябълки (блокове) на произволни места върху нашата решетка

var gameBlock = function (xPosition, yPosition) {
        if (xPosition % BLOCK_SIZE !== 0 || yPosition % BLOCK_SIZE !== 0) {
            throw new Error("The block coordinates must be within the canvas grid!");
        }
 
        this.xPosition = xPosition;
        this.yPosition = yPosition;
    }
 
var generateApple = function () {
        var appleXPosition = Math.round((Math.random() * GAME_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE;
        var appleYPosition = Math.round((Math.random() * GAME_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE;
        return new gameBlock(appleXPosition, appleYPosition);
    }

 

Вече след като сме създали такова понятие като игрово блокче можем и да си дефинираме змия(или червей). Змията ще има блокче за глава, масив от блокчета за тяло, посока на движение, както и една проста функция за придвижване. Придвижването ще става като изтриваме последния блок от тялото и добавяме нов там, където за последно е била главата. След това ще преместим главата на новата позиция.

var Worm = function (startLength, startX, startY) {
        this.head = new gameBlock(startX, startY);
        wormBody = new Array(startLength - 1);
        for (var i = 0; i < startLength - 1; i++) {
            wormBody[i] = new gameBlock(startX - BLOCK_SIZE * (i + 1), startY);
        }
 
        this.getWormBody = function () {
            return wormBody;
        }
 
        this.direction = {
            x: 1,
            y: 0
        }
 
        this.move = function () {
            wormBody.splice(wormBody.length - 1, 1);
            wormBody.unshift(new gameBlock(this.head.xPosition, this.head.yPosition));
            this.head.xPosition += this.direction.x * BLOCK_SIZE;
            this.head.yPosition += this.direction.y * BLOCK_SIZE;
        }
    }

 

Следващото нещо, което можем да направим е начин елементите да се изрисуват на игралното поле. Дефинираме функцията drawElements, която приема масив от елементи и цвят и ги изрисува върху нашия canvas:

var drawElements = function (elements, color) {
        CONTEXT.fillStyle = color;
        for (var i = 0; i < elements.length; i++) {
            CONTEXT.fillRect(elements[i].xPosition, elements[i].yPosition, BLOCK_SIZE, BLOCK_SIZE);
        }
    }

 

След като вече имаме змия, която може да се движи, както и генерираме ябълки ни е нужно да имаме някакъв начин да разберем, кога змията се е сблъскала с нещо или е изяла ябълка.

var detectCollision = function (gameElements) {
        var wormHead = gameElements['worm'].head;
        var wormBody = gameElements['worm'].getWormBody();
        var apples = gameElements['apples'];
 
// проверка дали змията не е ударила някоя от стените
        var hitVerticalWall = (wormHead.xPosition < 0 || wormHead.xPosition > GAME_WIDTH);
        var hitHorizontalWall = (wormHead.yPosition < 0 || wormHead.yPosition > GAME_HEIGHT);
        if (hitVerticalWall || hitHorizontalWall) {
            endGame();
            return;
        }
 
// проверка дали змията не се е самоизяла
        for (var i = 0; i < wormBody.length; i++) {
            if (wormHead.xPosition === wormBody[i].xPosition && wormHead.yPosition === wormBody[i].yPosition) {
                endGame();
                return;
            }
        }
 
// проверка дали главата на змията не е попаднала върху ябълка
// и тогава увеличаваме дължината и генерираме нова ябълка
        for (var i = 0; i < apples.length; i++) {
            if (apples[i].xPosition === wormHead.xPosition && apples[i].yPosition === wormHead.yPosition) {
                var newTailX = wormBody[wormBody.length - 1].xPosition * 2 - wormBody[wormBody.length - 2].xPosition;
                var newTailY = wormBody[wormBody.length - 1].yPosition * 2 - wormBody[wormBody.length - 2].yPosition;
                wormBody.push(new gameBlock(newTailX, newTailY));
                apples.splice(i, 1);
                apples.push(generateApple());
                PLAYER_SCORE += 20;
            }
        }
    }

 

Остана да измислим как да контролираме змията – ами доста проста – създаваме функция с един switch, който проверява дали не е натисната някоя от стрелките на клавиатурата и променя посоката на движение на змията:

var keyboardControlWorm = function (worm) {
        document.onkeydown = function (event) {
            event = event || window.event;
            switch (event.keyCode) {
                // left
                case 37:
                    if (worm.direction.x != 1 && worm.direction.y != 0) {
                        worm.direction.x = -1;
                        worm.direction.y = 0;
                    }
                    break;
                    // up
                case 38:
                    if (worm.direction.x != 0 && worm.direction.y != 1) {
                        worm.direction.x = 0;
                        worm.direction.y = -1;
                    }
                    break;
                    // right
                case 39:
                    if (worm.direction.x != -1 && worm.direction.y != 0) {
                        worm.direction.x = 1;
                        worm.direction.y = 0;
                    }
                    break;
                    // down
                case 40:
                    if (worm.direction.x != 0 && worm.direction.y != -1) {
                        worm.direction.x = 0;
                        worm.direction.y = 1;
                    }
                    break;
            }
        }
    }

Вече сме готови да съберем всички отделни парчета от играта заедно. Повечето игри използват един доста популярен шаблон – Game Loop Pattern. Той не представлява абсолютно нищо специално – просто един цикъл, който отговаря за непрекъснатото изпълнение на игровата логика. Ще използваме setInterval функцията в Javascript, която ни позволява точно такова повтаряемо изпълнение през даден времеви интервал. Също така добавяме няколко прости функции, които ни помагат да контролираме играта (например за изтриване на игровото поле и за обновяване на резултатите)

var gameLoopControl;
 
    var startGame = function () {
        clearBoard();
        PLAYER_SCORE = 0;
        var playerWorm = new Worm(6, 40, 20);
        var wormBody = playerWorm.getWormBody();
        updateTopScores();
 
// връзване на змията с игровите контроли
        keyboardControlWorm(playerWorm);
 
// масив, в който ще пазим всички игрови елементи
        var gameElements = [];
        gameElements['worm'] = playerWorm;
        gameElements['apples'] = [];
        for (var i = 0; i < NUMBER_OF_APPLES; i++) {
            gameElements['apples'].push(generateApple());
        }
        clearInterval(gameLoopControl);
// основния game loop
        gameLoopControl = setInterval(function () {
            clearBoard();
            drawElements([playerWorm.head], 'green');
            drawElements(wormBody, 'black');
            drawElements(gameElements['apples'], 'red');
            playerWorm.move();
            detectCollision(gameElements);
            PLAYER_SCORE++;
            updateScore();
        }, 100);
    }
 
    var endGame = function () {
        clearInterval(gameLoopControl);
        var playerName = prompt('Dang! You are dead, man! Your score is: ' + PLAYER_SCORE + '. Please enter yo name:');
        if (playerName === null) {
            playerName = 'nameless';
        }
        localStorage.setItem(PLAYER_SCORE, playerName);
        updateTopScores();
    }
 
    var updateScore = function () {
        CURRENT_SCORE_ELEMENT.innerText = 'Current score: ' + PLAYER_SCORE;
    }
 
    var updateTopScores = function () {
        clearTopScores();
        var sortedScores = Object.keys(localStorage).sort(function (a, b) {
            return b - a;
        });
 
        for (var i = 0; i < TOP_SCORES_TO_DISPLAY; i++) {
            var currentScore = sortedScores[i];
            if (currentScore && currentScore !== undefined) {
                var scoreDiv = document.createElement('div');
                scoreDiv.innerText = localStorage[currentScore] + ' : ' + currentScore;
                TOP_SCORES_ELEMENT.appendChild(scoreDiv);
            }
        }
    }
 
    var clearTopScores = function () {
        while (TOP_SCORES_ELEMENT.firstChild) {
            TOP_SCORES_ELEMENT.removeChild(TOP_SCORES_ELEMENT.firstChild);
        }
    }
 
    var clearBoard = function () {
        CONTEXT.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
    }

Довършителни работи

Нашата змия е почти готова. Обаче с текущия код правим нещо, което е лоша практика – замърсяваме глобалния namespace, т.е. имаме много глобални променливи. Можем много лесно да решим този проблем с т.нар. revealing module pattern в Javascript – създаваме една самоизвикваща се функция, които ни показва само нещата, които ни трябват, а именно startGame и updateTopScores:

var wormify = (function () {
//
// всичкият код, който написахме до момента
//
 
return {
	startGame: startGame,
	updateTopScores: updateTopScores
}
})();

 

Остава да вържем нашия html с игровата логика. Крайния index.html изглежда по следния начин:

<html>
<head>
    <title>The worm game</title>
    <link rel="stylesheet" type="text/css" href="styles/default.css">
</head>
<body onload='displayScores()'>
	<p>Welcome to the WORM GAME</p>
	<button id='startButton'>Start Game</button>
	<div class="gameWrapper">
		<div id='hiScores'>
			High scores:
			<div id="top10"></div>
		</div>
	    <canvas id='wormGame' width='600' height='400'></canvas>
	    <div id="score"></div>
    </div>
    <script type="text/javascript" src='scripts/wormify.js'></script>
    <script type="text/javascript">
    	var startButton = document.getElementById('startButton');
    	startButton.addEventListener('click', function() {
    		wormify.startGame();
    	});
 
    	function displayScores() {
    		return wormify.updateTopScores();
    	}
    </script>
</body>
</html>

 

Това е всичко! Нашата игра е напълно готова, а пълния сорс код може да намерите тук:

SOURCE
Post Tagged with ,

Вашият коментар

Вашият email адрес няма да бъде публикуван Задължителните полета са отбелязани с *


− 5 = едно

Можете да използвате тези HTML тагове и атрибути: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>