Все мы любим игры. Но когда дело доходит до их создания, многие думают, что это удел избранных — тех, кто знает C++ или Unity наизусть. А что если я скажу, что можно сделать полноценную аркаду или головоломку прямо в браузере, используя обычный JavaScript и один элемент <canvas>? Звучит как магия, но это чистая правда. В этой статье я расскажу, как сделать первые шаги в геймдеве без боли и страха, используя только то, что уже есть в твоём браузере.

Digital-студия WNDER

Почему Canvas, а не что-то серьёзное?

Когда я начинал, мне казалось, что для игр нужен какой-то мощный движок. Но Canvas — это как чистый лист бумаги. Ты сам рисуешь каждый кадр, каждую фигуру. Это даёт полный контроль и понимание того, как работают игры под капотом. Плюс, Canvas работает везде — на компьютере, планшете, телефоне. Не нужно ничего устанавливать, просто открыл браузер и пишешь код.

Конечно, для огромных 3D-миров Canvas не подойдёт. Но для 2D-платформеров, змейки, тетриса, арканоида — это идеальный инструмент. И знаешь, что самое приятное? Ты можешь сделать игру, которая будет работать даже на старом ноутбуке, не нагружая его.

Давай сразу к делу. Создадим простую игру — поймай падающий шарик. Это классика, на которой удобно учиться.

Настройка сцены: создаём Canvas

Canvas — это HTML-элемент, который выглядит как прямоугольная область. Вся магия происходит через JavaScript: мы получаем контекст рисования и начинаем творить. Представь, что это холст художника, а кисти — это код.

Сначала создадим HTML-страницу с элементом <canvas>. Не забудь указать ширину и высоту — иначе он будет размером 300 на 150 пикселей по умолчанию, что маловато для игры.

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Поймай шарик</title>
    <style>
        canvas {
            border: 2px solid #333;
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    <canvas id="game" width="600" height="400"></canvas>
    <script src="game.js"></script>
</body>
</html>

Теперь в файле game.js мы получим наш холст и контекст. Контекст — это объект, через который мы рисуем. Есть два типа: 2d и webgl. Для нас пока нужен только 2D.

// Получаем элемент canvas по его ID
const canvas = document.getElementById('game');
// Получаем 2D-контекст
const ctx = canvas.getContext('2d');

// Проверяем, что всё получилось
console.log(ctx); // Должен быть объект, а не null

Если ты видишь в консоли объект — всё работает. Если null, значит что-то не так с ID или браузер слишком старый. Но в современных браузерах проблем не будет.

Игровой цикл: сердце любой игры

Любая игра — это бесконечный цикл: отрисовали кадр, обновили логику, отрисовали следующий. Это называется игровой цикл. В JavaScript для этого есть специальная функция requestAnimationFrame. Она вызывает наш код перед каждой перерисовкой экрана, обычно 60 раз в секунду. Это идеально для плавной анимации.

Вот как выглядит базовый цикл:

function update() {
    // Здесь будем обновлять логику игры
    // Например, двигать шарик
}

function draw() {
    // Здесь будем рисовать всё на экране
}

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

// Запускаем цикл
gameLoop();

Обрати внимание: мы не используем setInterval. requestAnimationFrame сам подстраивается под частоту экрана и не тратит ресурсы, когда вкладка неактивна. Это экономно и правильно.

Лайфхак: если игра работает слишком быстро на мониторе 144 Гц, а ты хочешь, чтобы скорость была одинаковой везде, используй параметр времени, который передаёт requestAnimationFrame — это метка времени в миллисекундах. Двигай объекты на скорость * (дельта / 16.7), чтобы нормализовать скорость.

Рисуем персонажа и врага

Теперь давай нарисуем простые фигуры. Пусть у нас будет игрок — прямоугольная ракетка внизу, и враг — падающий шарик сверху. Для начала просто нарисуем их в статике.

// Игрок: ракетка
const player = {
    x: 250, // позиция по X
    y: 350, // позиция по Y (низ экрана)
    width: 80,
    height: 15,
    speed: 5
};

// Враг: шарик
const ball = {
    x: 300, // случайная позиция
    y: 0,
    radius: 10,
    speedY: 2, // скорость падения
    speedX: 1 // скорость по горизонтали (для разнообразия)
};

function draw() {
    // Очищаем экран
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Рисуем игрока (прямоугольник)
    ctx.fillStyle = '#4CAF50'; // зелёный
    ctx.fillRect(player.x, player.y, player.width, player.height);

    // Рисуем врага (круг)
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
    ctx.fillStyle = '#F44336'; // красный
    ctx.fill();
    ctx.closePath();
}

Что здесь происходит? fillRect рисует прямоугольник, а arc — круг. beginPath и closePath нужны, чтобы не смешивать фигуры. Цвет задаётся через fillStyle — это может быть любой CSS-цвет.

Запусти и посмотри: зелёная полоска внизу и красный шарик сверху. Это уже почти игра, но шарик не падает. Добавим движение.

Движение и управление

Чтобы шарик падал, нужно в функции update изменять его координату y. А игроком будем управлять с клавиатуры — стрелками влево и вправо. Для этого слушаем события нажатия клавиш.

let keys = {};

// Запоминаем, какие клавиши нажаты
document.addEventListener('keydown', (e) => {
    keys[e.code] = true;
});

document.addEventListener('keyup', (e) => {
    keys[e.code] = false;
});

function update() {
    // Управление игроком
    if (keys['ArrowLeft'] && player.x > 0) {
        player.x -= player.speed;
    }
    if (keys['ArrowRight'] && player.x + player.width < canvas.width) {
        player.x += player.speed;
    }

    // Движение шарика
    ball.y += ball.speedY;
    ball.x += ball.speedX;

    // Отскок от стенок по X
    if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.speedX = -ball.speedX;
    }

    // Если шарик упал вниз — вернуть наверх
    if (ball.y > canvas.height) {
        ball.y = 0;
        ball.x = Math.random() * canvas.width;
        // Можно увеличить скорость для сложности
    }

    // Проверка столкновения с игроком
    if (ball.y + ball.radius > player.y && 
        ball.y - ball.radius < player.y + player.height &&
        ball.x > player.x && ball.x < player.x + player.width) {
        // Отскок вверх
        ball.speedY = -ball.speedY;
        // Немного меняем направление по X для интереса
        ball.speedX = (Math.random() - 0.5) * 2;
    }
}

Здесь мы используем объект keys для хранения состояния клавиш. Это простой и надёжный способ. Шарик при столкновении с ракеткой отскакивает вверх, а если упал вниз — появляется сверху.

Попробуй запустить код. Теперь ты можешь ловить шарик ракеткой. Это уже игра! Конечно, не хватает счёта и проигрыша, но это легко добавить.

Обработка столкновений и счёт

Столкновения — это ключевая механика. В нашем случае мы проверяем пересечение прямоугольника и круга. Но есть более простой способ — использовать окружность вокруг шарика и прямоугольник ракетки. Мы уже сделали это приблизительно, но можно улучшить.

Добавим счёт. Пусть за каждое отбивание мы получаем очко. А если шарик упал — игра заканчивается. Для этого заведём переменную score и gameOver.

let score = 0;
let gameOver = false;

function update() {
    // ... предыдущий код ...

    // Проверка столкновения
    if (ball.y + ball.radius > player.y && 
        ball.y - ball.radius < player.y + player.height &&
        ball.x > player.x && ball.x < player.x + player.width) {
        ball.speedY = -Math.abs(ball.speedY); // всегда вверх
        score++;
        // Можно увеличить скорость
        ball.speedY += 0.1;
    }

    // Если упал вниз
    if (ball.y - ball.radius > canvas.height) {
        gameOver = true;
    }
}

function draw() {
    // Очищаем экран
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Рисуем игрока и шарик
    // ...

    // Рисуем счёт
    ctx.fillStyle = '#000';
    ctx.font = '20px Arial';
    ctx.fillText('Счёт: ' + score, 10, 30);

    // Если игра окончена
    if (gameOver) {
        ctx.fillStyle = 'rgba(0,0,0,0.5)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = '#fff';
        ctx.font = '40px Arial';
        ctx.fillText('Игра окончена!', canvas.width/2 - 150, canvas.height/2);
        ctx.font = '20px Arial';
        ctx.fillText('Нажми R для рестарта', canvas.width/2 - 100, canvas.height/2 + 40);
    }
}

// Рестарт по клавише R
document.addEventListener('keydown', (e) => {
    if (e.code === 'KeyR' && gameOver) {
        // Сбросить всё
        score = 0;
        gameOver = false;
        ball.y = 0;
        ball.x = Math.random() * canvas.width;
        ball.speedY = 2;
    }
});

Теперь у нас есть полноценная игра со счётом, условием проигрыша и рестартом. Это уже можно показать друзьям.

Полезные советы для новичков

Создание игр — это итеративный процесс. Не пытайся сразу сделать идеально. Вот несколько правил, которые я вывел за годы практики:

  • Начинай с малого. Сделай змейку, а не RPG с открытым миром. Маленький успех лучше большого провала.
  • Используй константы. Выноси скорость, размеры в отдельные переменные. Так проще настраивать баланс.
  • Не бойся ошибок. Ошибки — это часть процесса. Читай сообщения в консоли, они часто подсказывают решение.
  • Разделяй код. Логика, отрисовка, обработка ввода — лучше держать в разных функциях.
Правило: не пытайся писать весь код сразу. Сначала сделай так, чтобы игра работала с минимальным функционалом, а потом улучшай. Это поможет избежать перегрузки и быстрее увидеть результат.

Что дальше?

Теперь ты знаешь основы. Можно добавить звуки через Web Audio API, спрайты вместо кружочков, уровни сложности, анимацию частиц. Canvas позволяет многое. Если захочешь углубиться — изучи физику, игровые паттерны, или переходи на библиотеки типа Phaser, но уже с пониманием, как всё работает.

В итоге, создание игр на Canvas — это увлекательный способ прокачать навыки программирования. Ты учишься думать алгоритмически, работать с анимацией, обрабатывать события. И всё это с мгновенным результатом — ты видишь, как твоя игра оживает.

Так что открывай редактор, пиши код и не бойся экспериментировать. Удачи!

Студия WNDER