Pull to refresh

Canvas шаг за шагом: ПОНГ

Reading time5 min
Views53K
Сегодня попробуем написать небольшую игру Понг используя html5 тег canvas. Те кто не хочет читать пост тот может сразу ИГРАТЬ.
Если верить Википедии, то можно узнать что Pong является простейшим симулятором настольного тенниса. Небольшой квадратик, заменяющий пинг-понговый мячик, двигается по экрану по линейной траектории. Если он ударяется о периметр игрового поля или об одну из нарисованных ракеток, то его траектория изменяется в соответствии с углом столкновения.
Геймплей состоит в том, что игроки передвигают свои ракетки вертикально, чтобы защищать свои ворота. Игрок получает одно очко, если ему удаётся отправить мячик за ракетку оппонента…

Мы попытаемся реализовать игру так что бы в неё можно было играть при помощи мыши, а оппонентом управлял компьютер. Итак приступим. Для начала определимся с тем что, для полного понимания происходящего в этой записи желательно ознакомиться с постом описывающим основы.

Наш мегапроект будет состоять из двух файлов, соответственно pong.htm и pong.js которые собственно нужно создать и сохранить в одной папке.
Содержимое html файла:
<html>
    <head>    
        <meta charset = "utf-8">
        <title>html5Pong</title>
        <script src="pong.js"></script>
    </head>
    <body>
        <canvas id="pong">wtf?!</canvas>
        <script>init()</script>
    </body>
</html>

Собственно вся механика игры будет вынесена в файл pong.js и вносим в него первые строки нашей будущей игры:
// Инициализация переменных
function init() {
    canvas = document.getElementById("pong");
    canvas.width = 480; // задаём ширину холста
    canvas.height = 320; // задаём высоту холста
    context = canvas.getContext('2d');
    draw();
}
// Отрисовка игры
function draw() {
    context.fillStyle = "#000";
    context.fillRect(0, 0, 480, 320);
}
init();

Если открыть html фал браузером то можно будет увидеть работу этого скрипта который собственно закрашивает наш холст в чёрный цвет.

Игровые объекты


Все игровые объекты в Понге представляют собой прямоугольники и это существенно облегчит нашу задачу. Зададим небольшой класс Rect который будет содержать все нужные поля для отрисовки прямоугольника, а так же метод draw:
function rect(color, x, y, width, height) {
    this.color = color; // цвет прямоугольника
    this.x = x; // координата х
    this.y = y; // координата у
    this.width = width; // ширина
    this.height = height; // высота
    this.draw = function() // Метод рисующий прямоугольник
    {
        context.fillStyle = this.color;
        context.fillRect(this.x, this.y, this.width, this.height);
    }
}

Теперь слегка изменим содержимое функций инициализации и отрисовки добавив объекты игрового поля, игроков, «шарика»
// Инициализация переменных
function init() {
    // объект который задаёт игровое поле
    game = new rect("#000", 0, 0, 480, 320);
    // Ракетки-игроки
    ai = new rect("#fff", 10, game.height / 2 - 40, 20, 80);
    player = new rect("#fff", game.width - 30, game.height / 2 - 40, 20, 80);
    // количество очков
    ai.scores = 0;
    player.scores = 0;
    // наш квадратный игровой "шарик"
    ball = new rect("#fff", 40, game.height / 2 - 10, 20, 20);
    canvas = document.getElementById("pong");
    canvas.width = game.width;
    canvas.height = game.height;
    context = canvas.getContext("2d");
    draw();
}
// Отрисовка игры
function draw() {
    game.draw(); // рисуем игровое поле
    // рисуем на поле счёт
    context.font = 'bold 128px courier';
    context.textAlign = 'center';
    context.textBaseline = 'top';
    context.fillStyle = '#ccc';
    context.fillText(ai.scores, 100, 0);
    context.fillText(player.scores, game.width-100, 0);
    for (var i = 10; i < game.height; i += 45) // линия разделяющая игровое поле на две части
   {
        context.fillStyle = "#ccc";
        context.fillRect(game.width/2 - 10, i, 20, 30);
    }
    ai.draw(); // рисуем левого игрока
    player.draw(); // правого игрока
    ball.draw(); // шарик
}

Если теперь открыть файл, то можно увидеть все элементы нашей игры.

Да будет жизнь


Конечно всё на данном этапе выглядит очень даже неплохо, но статичная картинка не то что нам нужно. Поэтому сейчас мы займёмся «оживлением» шарика. Для этого создадим новую функцию play () в которую вынесем вызов функции draw (), а саму play будем вызывать из init () посредством таймера, а именно setInterval (play, 1000 / 50). Координату y игрока мы привяжем к координате мыши передвигающейся по холсту. В функции update содержатся изменения которые следут произвести, такие как координаты мыши и прочие радости. Что бы не запутаться в произведенных действиях ниже код всего того файла pong.js который у нас должен выйти:
// класс определяющий параметры игрового прямоугольника и метод для его отрисовки
function rect(color, x, y, width, height) {
    this.color = color;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.draw = function() {
        context.fillStyle = this.color;
        context.fillRect(this.x, this.y, this.width, this.height);
    };
}
// движение игрока
function playerMove(e) {
    var y = e.pageY;
    if (player.height / 2 + 10 < y && y < game.height - player.height / 2 - 10) {
        player.y = y - player.height / 2;
    }
}
// отрисовка игры
function draw() {
    game.draw(); // рисуем игровое поле
    // рисуем на поле счёт
    context.font = 'bold 128px courier';
    context.textAlign = 'center';
    context.textBaseline = 'top';
    context.fillStyle = '#ccc';
    context.fillText(ai.scores, 100, 0);
    context.fillText(player.scores, game.width - 100, 0);
    for (var i = 10; i < game.height; i += 45)
    // линия разделяющая игровое поле на две части
    {
        context.fillStyle = "#ccc";
        context.fillRect(game.width / 2 - 10, i, 20, 30);
    }
    ai.draw(); // рисуем левого игрока
    player.draw(); // правого игрока
    ball.draw(); // шарик
}
// Изменения которые нужно произвести
function update() {
    // меняем координаты шарика
    ball.x += ball.vX;
    ball.y += ball.vY;

}
function play() {
    draw(); // отрисовываем всё на холсте
    update(); // обновляем координаты
}
// Инициализация переменных
function init() {
    // объект который задаёт игровое поле
    game = new rect("#000", 0, 0, 480, 320);
    // Ракетки-игроки
    ai = new rect("#fff", 10, game.height / 2 - 40, 20, 80);
    player = new rect("#fff", game.width - 30, game.height / 2 - 40, 20, 80);
    // количество очков
    ai.scores = 0;
    player.scores = 0;
    // наш квадратный игровой "шарик"
    ball = new rect("#fff", 40, game.height / 2 - 10, 20, 20);
    // скорость шарика
    ball.vX = 2; // скорость по оси х
    ball.vY = 2; // скорость по оси у
    canvas = document.getElementById("pong");
    canvas.width = game.width;
    canvas.height = game.height;
    context = canvas.getContext("2d");
    canvas.onmousemove = playerMove;
    setInterval(play, 1000 / 50);
}


Игровые столкновения


Самое интересное начнётся сейчас, нам нужно научить шарик не вылетать за пределы игрового поля, а так же соприкасаться с ракетками, для этого я написал небольшую функцию которая возвращает истинное значение если два игровых объекта соприкасаться. Ниже её код
function collision(objA, objB) {
    if (objA.x+objA.width  > objB.x &&
        objA.x             < objB.x+objB.width &&
        objA.y+objA.height > objB.y &&
        objA.y             < objB.y+objB.height) {
            return true;
        }
        else {
            return false;
            }
    }

Теперь что бы шарик не просто улетал за пределы игрового поля нам надо немного скорректировать функцию update, а именно 
function update() {
    // меняем координаты шарика
    // Движение по оси У
    if (ball.y<0 || ball.y+ball.height>game.height) {
        // соприкосновение с полом и потолком игрового поля
        ball.vY = -ball.vY;
    }
    // Движение по оси Х
    if (ball.x<0) {
        // столкновение с левой стеной
        ball.vX = -ball.vX;
        player.scores ++;
    }
    if (ball.x+ball.width>game.width) {
        // столкновение с правой
        ball.vX = -ball.vX;
        ai.scores ++;
    }
    // Соприкосновение с ракетками
    if ((collision(ai, ball) && ball.vX<0) || (collision(player, ball) && ball.vX>0)){
        ball.vX = -ball.vX;
    }
    // приращение координат
    ball.x += ball.vX;
    ball.y += ball.vY;
}

Теперь в нашу игру практически можно играть, мячик летает правильно, ракетки его могут отбивать, оппонент правда у нас слегка труп, но это мы поправим, небольшой функцией

function aiMove() {
    var y;
    // делаем скорость оппонента на две единицы меньше чем скорость шарика
    var vY = Math.abs(ball.vY) - 2;
    if (ball.y < ai.y + ai.height/2) {
        y = ai.y - vY;
    }
    else {
        y = ai.y + vY;
    }
    if (10 < y && y < game.height - ai.height - 10) {
        ai.y = y;
    }
}

Вызов функции следует раместить в update.

Итог


Собственно вот и написали, осталось лишь немного подлатать и вот он полноценный понг. Статистику сыгранных игр мы будем так же как и счёт выводить на экран, а изменения скорости привяжем к клику мыши. По ссылке можно найти ПОНГ с простой статистикой, полный комментариев.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+41
Comments22

Articles

Change theme settings