Разработка браузерных игр — это создание игр, которые запускаются во вкладке браузера без установки: логика пишется на JavaScript или TypeScript (либо компилируется в WebAssembly), картинка выводится через Canvas 2D, WebGL или WebGPU, звук — через Web Audio API. Инструменты для этого бывают трех уровней: библиотека рендеринга, игровой фреймворк и полноценный движок с редактором и экспортом в Web.
Содержание
- Что устарело в советах про браузерные игры
- Мини-словарь: что рисует картинку в браузере
- Движки и библиотеки для браузерных игр
- Как выбрать движок под задачу
- Первая браузерная игра на canvas без движка
- Как тот же пример выглядит в движке
- Дорожная карта до опубликованной игры
- Если не получилось: типовые проблемы
- Выводы
- Где применяется / связь с практикой
- FAQ
Ниже — как отличить эти уровни, какой инструмент брать под свою задачу и как с нуля написать первую игру на canvas, чтобы понять, что именно движок делает за вас. Пример проверен в Google Chrome 153 на 24.09.2026.
Что устарело в советах про браузерные игры
Если вы встречали советы делать браузерные игры на Flash, PHP или Pygame, их стоит отложить:
- Flash — Adobe прекратила поддержку Flash Player в конце 2020 года, современные браузеры его не запускают.
- PHP работает на сервере. Он может хранить аккаунты и таблицу рекордов, но саму игру в браузере не рисует.
- Python и Pygame создают окно на компьютере, а не страницу. Запустить Python-игру в браузере можно через WebAssembly-сборки интерпретатора, но это нишевый путь, а не стандарт индустрии.
Основной язык клиентской части браузерной игры — JavaScript (или TypeScript, который компилируется в JavaScript). Исключение — движки вроде Unity и Godot: вы пишете на C# или GDScript, а экспорт упаковывает игру в WebAssembly.
Мини-словарь: что рисует картинку в браузере
| Технология | Что это | Когда встречается |
|---|---|---|
| Canvas 2D | API для рисования 2D-фигур и картинок на элементе <canvas> |
Простые 2D-игры без движка |
| WebGL / WebGL 2 | Доступ к видеокарте из браузера | 2D с тысячами спрайтов, 3D; основа большинства движков |
| WebGPU | Более новый графический API, преемник WebGL | Тяжелая графика и вычисления; поддержка в браузерах растет |
| WebAssembly (Wasm) | Бинарный формат, в который компилируются C++, C#, Rust | Экспорт из Unity, Godot и других «настольных» движков |
Упрощенно: Canvas 2D и WebGL — это «холст», а движок — все, что поверх него: сцены, спрайты, физика, звук, загрузка ресурсов, ввод. Библиотека может закрывать только часть этого списка.
Движки и библиотеки для браузерных игр
| Инструмент | Уровень | Язык | Для чего |
|---|---|---|---|
| Phaser | 2D-фреймворк | JavaScript / TypeScript | Аркады, платформеры, головоломки: сцены, физика, анимации, звук из коробки |
| PixiJS | 2D-рендерер | JavaScript / TypeScript | Быстрая отрисовка спрайтов; логику и физику добавляете сами |
| Three.js | 3D-библиотека | JavaScript / TypeScript | 3D-сцены, визуализации, небольшие 3D-игры; игровые системы собираете сами |
| Babylon.js | 3D-движок | TypeScript / JavaScript | 3D-игры с физикой, инспектором сцены, поддержкой WebXR |
| PlayCanvas | 3D-движок с онлайн-редактором | JavaScript | Командная работа над 3D в браузере |
| Godot | Движок с редактором | GDScript, C# | 2D и 3D, экспорт в Web — одна из целевых платформ |
| Unity 6 | Движок с редактором | C# | Экспорт существующего проекта в Web; сборка тяжелее JS-движков |
Главное различие: Phaser, PixiJS, Three.js и Babylon.js изначально сделаны для веба, а Godot и Unity экспортируют в браузер «настольную» игру через WebAssembly. Отсюда разница в размере загрузки: сам JS-фреймворк при сжатии занимает порядка сотен килобайт, а экспорт из движка несет с собой среду выполнения и обычно весит несколько мегабайт и больше. Точная цифра зависит от проекта, ресурсов и сжатия, поэтому ее измеряют на своей сборке.
Unreal Engine 5 в этот список не входит: официального экспорта в HTML5 у него нет.
Как выбрать движок под задачу
| Задача | Что взять | Когда иначе |
|---|---|---|
| Первая 2D-игра, хочется понять основы | Canvas 2D без движка, затем Phaser | Если нужен результат за вечер без кода — визуальные конструкторы вроде Construct |
| 2D-аркада или головоломка для веба и мобильного браузера | Phaser | Много сложной графики и свой игровой цикл — PixiJS |
| 3D в браузере | Babylon.js | Нужна гибкая 3D-графика без игровых систем — Three.js |
| Игра нужна и в Steam, и в браузере | Godot или Unity с экспортом в Web | Если веб — главная платформа, JS-движок даст меньший размер загрузки |
| Уже знаете C# и Unity | Unity, платформа Web | Проект на Godot с C# в Web не экспортируется — см. раздел о типовых проблемах |
Практическое правило: если браузер — основная платформа, начинайте с JS-инструментов. Если браузер — одна из нескольких платформ, берите движок с редактором и сразу проверяйте размер и скорость загрузки веб-сборки.
Первая браузерная игра на canvas без движка
Прежде чем брать движок, полезно один раз собрать игру руками: так понятно, что такое игровой цикл, ввод и столкновения. Пример — «Уклонись»: сверху падают блоки, вы двигаете платформу стрелками, игра считает время до первого столкновения.
Сохраните код в файл index.html и откройте двойным щелчком — сервер для этого примера не нужен.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Уклонись</title>
<style>
body { margin: 0; height: 100vh; display: grid; place-items: center; background: #111; }
canvas { background: #222; }
</style>
</head>
<body>
<canvas id="game" width="480" height="640"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// 1. Ввод: храним нажатые клавиши, а не реагируем на каждое событие
const keys = new Set();
addEventListener('keydown', (e) => {
if (['ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) e.preventDefault();
keys.add(e.code);
if (e.code === 'Space' && state.over) reset();
});
addEventListener('keyup', (e) => keys.delete(e.code));
addEventListener('blur', () => keys.clear()); // окно потеряло фокус: keyup может не прийти
// 2. Состояние игры
let state;
function reset() {
state = {
player: { x: W / 2 - 20, y: H - 60, w: 40, h: 20, speed: 300 }, // px в секунду
rocks: [], spawnIn: 0, time: 0, over: false,
};
}
// 3. Столкновение прямоугольников (AABB)
function overlap(a, b) {
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}
// 4. Логика: все движения умножаем на dt (секунды с прошлого кадра)
function update(dt) {
if (state.over) return;
const p = state.player;
if (keys.has('ArrowLeft')) p.x -= p.speed * dt;
if (keys.has('ArrowRight')) p.x += p.speed * dt;
p.x = Math.max(0, Math.min(W - p.w, p.x));
state.time += dt;
state.spawnIn -= dt;
if (state.spawnIn <= 0) {
const size = 20 + Math.random() * 30;
state.rocks.push({ x: Math.random() * (W - size), y: -size, w: size, h: size,
vy: 150 + state.time * 10 });
state.spawnIn = Math.max(0.25, 0.8 - state.time * 0.01);
}
for (const r of state.rocks) r.y += r.vy * dt;
state.rocks = state.rocks.filter((r) => r.y < H);
if (state.rocks.some((r) => overlap(p, r))) state.over = true;
}
// 5. Отрисовка: каждый кадр рисуем сцену заново
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#4caf50';
const p = state.player;
ctx.fillRect(p.x, p.y, p.w, p.h);
ctx.fillStyle = '#e57373';
for (const r of state.rocks) ctx.fillRect(r.x, r.y, r.w, r.h);
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.fillText(`Время: ${state.time.toFixed(1)} с`, 10, 30);
if (state.over) ctx.fillText('Игра окончена. Пробел - заново', 90, H / 2);
}
// 6. Игровой цикл
let last = performance.now();
function frame(now) {
const dt = Math.min((now - last) / 1000, 0.1); // не больше 0,1 с после паузы вкладки
last = now;
update(dt);
draw();
requestAnimationFrame(frame);
}
reset();
requestAnimationFrame(frame);
</script>
</body>
</html>
Что вы увидите: темное поле 480×640, зеленую платформу внизу и красные квадраты, которые падают все быстрее. Стрелки двигают платформу, в левом верхнем углу растет таймер. После столкновения появляется надпись «Игра окончена. Пробел — заново», пробел запускает новую попытку.
Разбор по блокам
- Ввод. Клавиши складываются в
Set, аupdateкаждый кадр проверяет, что зажато. Так движение плавное и не зависит от автоповтора клавиатуры.preventDefault()не дает стрелкам и пробелу прокручивать страницу. Обработчикblurочищает набор: если переключиться в другое окно с зажатой стрелкой, событиеkeyupуйдет туда, и без очистки платформа продолжит ехать сама. - Состояние. Все, что меняется, лежит в одном объекте
state. Перезапуск — это простоreset(). - Столкновения.
overlapпроверяет пересечение прямоугольников по осям (AABB). Для кругов и повернутых фигур нужна другая проверка — именно это движки берут на себя. - Логика. Скорость задана в пикселях в секунду и умножается на
dt— время с прошлого кадра. - Отрисовка. Canvas не хранит объекты: каждый кадр поле очищается и рисуется заново.
- Цикл.
requestAnimationFrameвызываетframeперед каждой перерисовкой экрана.dtограничен 0,1 с, чтобы после возврата на свернутую вкладку блоки не «телепортировались».
Частая ошибка: скорость привязана к кадрам
Новички часто пишут p.x += 5 в каждом кадре. На мониторе 60 Гц это работает, но requestAnimationFrame обычно вызывается с частотой обновления экрана, и на 120 или 144 Гц игра ускоряется. Проверка в Node.js:
// fps-check.js: где окажется игрок через 1 секунду удержания клавиши
function simulate(fps, perFrame) {
let x = 0;
const dt = 1 / fps;
for (let i = 0; i < fps; i++) {
if (perFrame) x += 5; // неверно: шаг привязан к кадру
else x += 300 * dt; // верно: скорость в пикселях в секунду
}
return Math.round(x);
}
for (const fps of [60, 120, 144]) {
console.log(`${fps} Гц: шаг за кадр = ${simulate(fps, true)} px, шаг по dt = ${simulate(fps, false)} px`);
}
Результат прогона (Node.js 22 и 24):
60 Гц: шаг за кадр = 300 px, шаг по dt = 300 px
120 Гц: шаг за кадр = 600 px, шаг по dt = 300 px
144 Гц: шаг за кадр = 720 px, шаг по dt = 300 px
Исправление — то, что уже сделано в игре: скорость в единицах в секунду и умножение на dt. Для игр с физикой, где важна повторяемость, используют фиксированный шаг симуляции (например, 1/60 с) с накоплением времени — так устроены физические движки.
Как тот же пример выглядит в движке
| Что писали руками | Что дает Phaser или другой фреймворк |
|---|---|
Set нажатых клавиш |
Готовый менеджер ввода: клавиатура, мышь, касания, геймпад |
overlap для прямоугольников |
Физика с телами, гравитацией, отскоками и группами столкновений |
fillRect цветных блоков |
Спрайты, атласы, анимации, камера |
Цикл на requestAnimationFrame |
Сцены с методами загрузки, создания и обновления |
| Ничего | Загрузчик ресурсов, звук, масштабирование под экран |
Поэтому путь «сначала canvas, потом движок» не лишний: после ручного примера документация Phaser или Babylon.js читается как описание знакомых вещей.
Дорожная карта до опубликованной игры
- Собрать «Уклонись» и добавить в нее одну механику: жизни, бонусы или рекорд в
localStorage. - Переписать игру на выбранном движке со спрайтами и звуком.
- Добавить управление касаниями (Pointer Events) и масштабирование под экран телефона.
- Собрать проект сборщиком (например, Vite), проверить размер загрузки и время до первого кадра.
- Выложить на статический хостинг и проверить в двух браузерах и на телефоне.
Артефакт в конце — ссылка на работающую игру, которую можно показать. Сколько времени займет путь, зависит от опыта в JavaScript: без базы языка движок будет казаться набором магических вызовов.
Про безопасность: все, что выполняется в браузере, игрок может изменить через инструменты разработчика. Рекорд в localStorage годится для себя, но общая таблица лидеров должна проверять результаты на сервере.
Если не получилось: типовые проблемы
| Симптом | Причина | Что делать |
|---|---|---|
| Белый или черный экран | Ошибка в коде | Открыть консоль разработчика (F12) и прочитать первую ошибку |
| Стрелки прокручивают страницу | Нет preventDefault() |
Вызывать его для игровых клавиш |
| После переключения окна платформа едет сама | Клавиша осталась в наборе нажатых, keyup не пришел |
Очищать набор клавиш по событию blur |
| Картинка размыта на ноутбуке или телефоне | Плотность пикселей экрана выше 1 | Умножить размер canvas на devicePixelRatio, а CSS-размер оставить прежним |
| Звук не играет | Политика автозапуска браузеров | Запускать звук после первого клика или нажатия клавиши |
| Игра на модулях или сборка движка не открывается двойным щелчком | Ограничения file:// |
Запустить локальный сервер: python3 -m http.server 8000 и открыть http://localhost:8000 |
| Экспорт Godot 4 не стартует на хостинге | Многопоточная сборка требует заголовков cross-origin isolation | Включить заголовки COOP/COEP на сервере или экспортировать без поддержки потоков |
| Сборка Unity грузится с ошибкой распаковки | Сервер не отдает правильный Content-Encoding для сжатых файлов |
Настроить заголовки сервера или включить в Unity запасной вариант распаковки |
| Проект Godot на C# не экспортируется в Web | Экспорт C#-проектов Godot 4 в Web не поддерживается | Писать веб-версию на GDScript |
Выводы
- Клиентская часть браузерной игры пишется на JavaScript или TypeScript либо экспортируется из движка в WebAssembly; Flash, PHP и Pygame для этого не подходят.
- PixiJS и Three.js — библиотеки отрисовки, Phaser и Babylon.js — игровые фреймворки и движки для веба, Godot и Unity — движки с экспортом в Web и более тяжелой загрузкой.
- Если браузер — основная платформа, начинайте с Phaser (2D) или Babylon.js (3D); если одна из нескольких — с Godot или Unity.
- Первую игру полезно собрать на чистом canvas: цикл
requestAnimationFrame, ввод, столкновения и движение с учетомdt.
Где применяется / связь с практикой
Освойте тему на практике
Любой браузерный движок — это JavaScript-библиотека, и с ним работаешь через те же конструкции: объекты, массивы, функции, события, модули. Если код игры из примера пока читается с трудом, начните с базы языка на курсе «JavaScript-разработчик. Базовый уровень». Разобрать отдельные темы с преподавателями можно на открытых уроках Otus.
FAQ
Можно ли сделать браузерную игру на TypeScript?
Да. Phaser, PixiJS, Three.js и Babylon.js поставляются с типами, а сборщик компилирует TypeScript в JavaScript, который выполняет браузер.
Нужен ли сервер для браузерной игры?
Для одиночной игры хватает статического хостинга. Сервер нужен для аккаунтов, таблицы рекордов с проверкой результатов и мультиплеера (обычно через WebSocket или WebRTC).
Пойдет ли браузерная игра на телефоне?
Да, если заложить управление касаниями и масштабирование под экран. Тяжелые WebAssembly-сборки из движков на слабых телефонах загружаются и работают заметно медленнее JS-игр, поэтому их проверяют на реальных устройствах.



