Данная статья планируется как пошаговый обзор создания простой JavaScript-игры класса “Ball and Paddle” на Canvas. Примерами такой игры могут послужить старые DOS-е игры наподобие таких - Ball and Paddle.
Пример кода из этой статьи взят из видео-курса достаточно известного Интернет-ресурса, посвященного фронтенд-разработке - Udemy.
Почему Canvas и почему игра? Лично для меня процесс познания JavaScript сильно облегчается благодаря Canvas - так интереснее. А создание игры на Canvas - это еще интереснее!
Итак, с чего начнем? Дальше в меру своих сил буду стараться детально пошагово рассказывать, что делает тот или иной кусок кода. И начнем с базового набора - создания Canvas.
Базовый Canvas
HTML-разметка страницы будет предельно простой:
<body>
<canvas id="canvas"></canvas>
<script src="script.js"></script>
</body>
В JavaScript’е создадим две глобальные переменные - одну для элемента Canvas, вторую - для 2d-контекста Canvas. Когда parser браузера построит DOM-дерево документа (событие
), инициализируем обе переменные, выполним проверку удачного получения 2d-контекста Canvas и если проверка будет пройдена успешно, то динамически зададим размеры Canvas:1
DOMContentLoaded
var canvas = null;
var ctx = null;
window.addEventListener('DOMContentLoaded', function () {
canvas = document.querySelector('#canvas');
ctx = canvas.getContext('2d');
if ( ctx ) {
canvas.width = 800;
canvas.height = 500;
}
}, false);
Базовые элементы игры
Основа Canvas была создана в предыдущем шаге. В этом шаге создадим три фигуры, которые будут учавствовать в игре. Таковыми фигурами будут:
- фон игры
- мячик (ball)
- площадка (paddle)
Ниже я приведу JavaScript-код создания всех трех элементов, но сам код комментировать не буду, так как он очень простой и относится к основам Canvas:
var canvas = null;
var ctx = null;
window.addEventListener('DOMContentLoaded', function () {
canvas = document.querySelector('#canvas');
ctx = canvas.getContext('2d');
if ( ctx ) {
canvas.width = 800;
canvas.height = 500;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'firebrick';
ctx.beginPath();
ctx.arc(50, 50, 10, 0, 360*Math.PI/180, true);
ctx.fill();
ctx.closePath();
ctx.fillStyle = '#fff';
ctx.fillRect(100, canvas.height-40, 100, 10);
}
}, false);
Живой результат вышеприведенного кода можно посмотреть на этой странице - Lesson1-1. Это то, что должно получиться и что послужит заготовкой для игры.
Анимация мячика
В этом шаге предстоит сделать более интересные вещи. Во-первых, мы сделаем так, чтобы мячик начал двигаться как по-горизонтали, так и по-вертикали. А во-вторых, сделаем так, чтобы он вел себя как настоящий резиновый мячик - при ударе о стену отскакивал от нее и мчался в противоположном направлении.
Сделать это достаточно просто. Для этого нам понадобится одна из так называемых тайминговых функций JavaScript -
. А также немного воображения.1
setInterval()
Анимация мячика будем делать по-простому принципу, по которому делается любой мультфильм или кино - мячик будет отрисовываться с заданной частотой (
), но каждый раз в новой позиции. В результате будет создаваться иллюзия его движения. Каждая новая позиция мячика - это его координата по оси X или Y с новым значением соответственно.1
1000/frames
Чтобы мячик двигался достаточно быстро, изменять значения координат (
и 1
ballX += ballStepX
) мячика по оси X и Y будем с определенным шагом (1
ballY += ballStepY
и 1
ballStepX
) - допустим, со значениями 5 или 6:1
ballStepY
var canvas = null;
var ctx = null;
var frames = 24;
var ballX = 50;
var ballY = 50;
var ballStepX = 5;
var ballStepY = 6;
var ballRadius = 10;
window.addEventListener('DOMContentLoaded', function () {
canvas = document.querySelector('#canvas');
ctx = canvas.getContext('2d');
if ( ctx ) {
canvas.width = 800;
canvas.height = 400;
setInterval( function () {
ballX += ballStepX;
ballY += ballStepY;
if ( ballX < 0 || ballX > canvas.width) {
ballStepX *= -1;
}
if ( ballY < 0 || ballY > canvas.height ) {
ballStepY *= -1;
}
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'firebrick';
ctx.beginPath();
ctx.arc(ballX, ballY, ballRadius, 0, 360*Math.PI/180,true);
ctx.fill();
ctx.closePath();
ctx.fillStyle = '#fff';
ctx.fillRect(100, canvas.height-40, 100, 10);
}, 1000/frames);
}
}, false);
Эффект отскакивания от стенок (как резиновый мячик) обеспечивает проверка условий в участке кода:
...
if ( ballX < 0 || ballX > canvas.width) {
ballStepX *= -1;
}
if ( ballY < 0 || ballY > canvas.height ) {
ballStepY *= -1;
}
...
Здесь все просто - при выполнении условия знак переменной
или 1
ballStepX
будет меняться на противоположный. В результате значение переменной 1
ballStepY
или 1
ballX
будет возрастать или уменьшаться. Как следствие, мячик будет двигаться в одну или в другую сторону.1
ballY
Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-2.
Двигаем paddle
В этом шаге нужно заставить двигаться paddle при помощи мыши. Для этого по событию
внутри элемента Canvas будем получать значение X-координаты курсора мыши. И передавать это значение элементу paddle, его X-координате левого верхнего угла. Тем самым мы заставим paddle двигаться. За все эти действия будет отвечать функция 1
mousemove
:1
mouseCoords()
...
function mouseCoords (event) {
var canvasOffset = canvas.getBoundingClientRect();
var htmlElement = document.documentElement;
mouseX = event.clientX - canvasOffset.left - htmlElement.scrollLeft;
paddleX = mouseX - paddleWidth/2;
}
...
Обратите внимание на последнюю строку функции -
. Переменная 1
paddleX = mouseX - paddleWidth/2;
необходима для того, чтобы при выходе за границы Canvas элемент paddle скрывался ровно на половину своей ширины.1
paddleX
Также не забудем создать переменные для paddle и передать их в код для отрисовки фигуры:
...
var paddleX = null;
var paddleWidth = 100;
var paddleHeight = 10;
var paddleOffset = 40;
...
ctx.fillStyle = '#fff';
ctx.fillRect(paddleX, canvas.height - paddleOffset, paddleWidth, paddleHeight);
...
Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-3. Подвигайте курсором мыши право-влево, чтобы увидеть эффект.
Мячик отскакивает от paddle
На этом этапе нужно сделать так, чтобы мячик отскакивал от paddle, когда последний оказывается на его пути. Выполнить эту задачу просто - ведь мячик уже отскакивает от “стен” Canvas. Следовательно, нужно научить мячик “видеть” еще и paddle.
Для этого сначала нужно опеределить внешние границы paddle - все его четыре стороны:
...
var paddleLeftEdge = paddleX;
var paddleRightEdge = paddleLeftEdge + paddleWidth;
var paddleTopEdge = canvas.height - paddleOffset;
var paddleBottomEdge = paddleTopEdge + paddleHeight;
...
Когда значения всех сторон будут определены, то можно будет подставить эти значения в условие - и дело сделано:
...
if ( ballX > paddleLeftEdge && ballX < paddleRightEdge && ballY > paddleTopEdge && ballY < paddleBottomEdge ) {
ballStepY *= -1;
}
...
Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-4. Подвигайте курсором мыши право-влево и постарайтесь поймать мячик с помощью paddle, чтобы увидеть эффект.
Угол отскока мячика
В этом шаге сделаем так, чтобы наша игра смотрелась более правильной с точки зрения физики и обычной природы. То есть, при разном угле попадания на paddle мячик должен отскакивать от него с разной скоростью. Чем острее угол падения, тем с большей скоростью отскакивает от paddle мячик.
Решается эта задача несколькими строками кода:
...
if ( ballX > paddleLeftEdge && ballX < paddleRightEdge && ballY > paddleTopEdge && ballY < paddleBottomEdge ) {
ballStepY *= -1;
var paddleCenter = paddleLeftEdge + paddleWidth/2;
var ballDistance = ballX - paddleCenter;
ballStepX = ballDistance * 0.35;
}
...
В первой строке
находится X-координата середины paddle. В строке 1
var paddleCenter = paddleLeftEdge + paddleWidth/2;
определяется расстояние, на котором мячик соприкоснулся с paddle относительно его середины. В строке 1
var ballDistance = ballX - paddleCenter;
полученная дистанция присваивается шагу приращения по оси Х мячика - 1
ballStepX = ballDistance * 0.35;
.1
ballStepX
Логично предположить, что чем больше величина дистанции точки соприкосновения мячика относительно середины paddle, тем выше новая скорость движения мячика по-горизонтали. Чтобы эта скорость не была слишком высокой, ее необходимо уменьшить, умножив на 0.35, к примеру.
Живой пример приведенного выше кода можно посмотреть и изучить на этой странице - Lesson1-5.
Оптимизация кода
На данный момент наша задача по построению игры практически решена. Но остался один организационный момент.
Заключается он в том, что код необходимо реорганизовать в отдельные функции. Такой код будет читаться и поддерживаться значительно лучше.
Одна из таких функций уже была создана ранее - это функция
. Давайте преобразуемся и весь оставшийся код подобным образом:1
mouseCoords()
...
function drawRect (leftX, leftY, boxWidth, boxHeight, boxFillColor) {
ctx.fillStyle = boxFillColor;
ctx.fillRect(leftX, leftY, boxWidth, boxHeight);
}
...
function drawBall(centerX, centerY, radius, fillColor) {
ctx.fillStyle = fillColor;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 360*Math.PI/180, true);
ctx.fill();
ctx.closePath();
}
...
function drawAll() {
drawRect(0, 0, canvas.width, canvas.height, '#000');
drawBall(ballX, ballY, ballRadius, 'firebrick');
drawRect(paddleX, canvas.height - paddleOffset, paddleWidth, paddleHeight, '#fff');
}
...
Готовый пример преобразованного в функции кода можно посмотреть на этой странице - Lesson1-6.
На этом все.