«Танчики» на html5
Пишем свой игровой движок
После прошлого поста я что-то так проникся происходящим, что решил воссоздать ещё одну любовь детства. Но как говорил Маркус Персон: «Если вы не можете написать свой движок, то гавно вы, а не разработчики». Там, скорее всего, было как-о иначе, но суть такая. И да, с этой мыслью я в корне не согласен, но написать свой движок – задача, как минимум, интересная. Я решил, что аркадная игра для этого подходит, как ничто другое. По сути, некий аналог движка уже был реализован в тетрисе, но здесь нужна будет и какая-никакая физическая модель, и интеллект врагов и рендер всего этого безобразия в несколько слоёв и с ФПС побольше, чем 2 :)
Так что, приступим. Что нам нужно:
- Просчёт физики
- Работа ИИ агентов
- Считывание действий игрока
- Отрисовка результата работы предыдущих пунктов.
Для одного поста такая задачка звучит жирновато, поэтому, видимо, будет серия.
Я решил разделить рисования окружения (статичных объектов), врагов и игроков (динамических объектов) и UI на три разных канваса, которые просто повещены один поверх другого. Там можно будет проще и меньше перерисовывать.
Начинается наш код с проверки, готова ли страница к явлению миру нашего движка.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
Если готова, то давай же скорее всё проинициализируем
Тут всё просто:
function init() {
console.log('Battle City Remake - Initializing...');
// Инициализация рендерера
initRenderer();
// Очищаем все слои
clearBackground();
clearForeground();
clearUI();
// Рисуем границу игрового поля и отладочную сетку
renderGameFieldBorder();
renderDebugGrid();
console.log('Initialization complete. Starting game loop...');
// Запускаем game loop
requestAnimationFrame(gameLoop);
}
Но понятное дело, что кода тут мало, потому что всё вынесено в отдельные методы.
Есть ли среди них хоть что-то интересное?
Ну, renderDebugGrid и renderGameFieldBorder одним названием уже говорят, что там происходит.
Логично, что самое интересное происходит где-то тут requestAnimationFrame(gameLoop);
Но давайте сначала заглянем в инициализацию рендера
export function initRenderer() {
// Получаем canvas элементы
backgroundCanvas = document.getElementById('background-canvas');
foregroundCanvas = document.getElementById('foreground-canvas');
uiCanvas = document.getElementById('ui-canvas');
// Получаем контексты
backgroundCtx = backgroundCanvas.getContext('2d');
foregroundCtx = foregroundCanvas.getContext('2d');
uiCtx = uiCanvas.getContext('2d');
// Определяем DPR для HiDPI экранов
dpr = window.devicePixelRatio || 1;
// Настраиваем все три canvas
setupCanvas(backgroundCanvas, backgroundCtx);
setupCanvas(foregroundCanvas, foregroundCtx);
setupCanvas(uiCanvas, uiCtx);
console.log(`Renderer initialized (DPR: ${dpr})`);
}
function setupCanvas(canvas, ctx) {
// Физический размер canvas
canvas.width = CANVAS_WIDTH * dpr;
canvas.height = CANVAS_HEIGHT * dpr;
// Масштабируем контекст для компенсации DPR
ctx.scale(dpr, dpr);
// Отключаем сглаживание для пиксельной графики
ctx.imageSmoothingEnabled = false;
console.log(`Canvas ${canvas.id} setup: ${canvas.width}×${canvas.height} (logical: ${CANVAS_WIDTH}×${CANVAS_HEIGHT})`);
}
CANVAS_WIDTH и CANVAS_HEIGHT — это константы, они у меня в отдельном файлике лежат, там просто 800 на 600.
В целом, страничка готова к тому, чтоб рисовать всякое
Дёргаем requestAnimationFrame и отдаём её наш gameLoop
function gameLoop(currentTime) {
// Вычисляем deltaTime
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// Накапливаем время
accumulator += deltaTime;
// Fixed timestep - обновляем логику с фиксированным шагом
while (accumulator >= FRAME_TIME) {
update(FRAME_TIME);
accumulator -= FRAME_TIME;
}
// Рендерим независимо от update
render();
// Продолжаем loop
requestAnimationFrame(gameLoop);
}
Я решил фиксировать ФПС на 30
export const FPS = 30; // Фиксированный FPS
export const FRAME_TIME = 1000 / FPS; // ~33.33ms на кадр
Метод update содержит (ну, будет содержать) всю нашу игровую логику. Пока он только считает фпс и выводит его на экран для отладки.
function update(dt) {
// Пока пусто - здесь будет логика игры
// Подсчёт FPS для отладки
frameCount++;
fpsTimer += dt;
if (fpsTimer >= 1000) {
fps = frameCount;
frameCount = 0;
fpsTimer = 0;
// Обновляем FPS на странице
const fpsElement = document.getElementById('fps');
if (fpsElement) {
fpsElement.textContent = fps;
}
}
}
Ну и когда всё закончено, отрисовываем
function render() {
// Background canvas - перерисовывается только при изменении карты
// (пока только отладочная сетка, нарисованная в init)
// Foreground canvas - очищаем и рисуем сущности каждый кадр
clearForeground();
// TODO: Здесь будет рендер танков, пуль, эффектов
// UI canvas - очищаем и рисуем UI каждый кадр
clearUI();
renderUI();
}
То есть, по сути, ничего интересного, просто рисую полосочки и квадратики для UI
Результат выглядит вот так
Следующий пост будет посвящён второму этапу
И да, я упоролся и расписал себе полноценный план реализации на 13 этапов. Без ДизДока последнее время вообще не представляю как работать. А раз уж я в команде один, то сам себе и ТЗ пишу... Но лучше так, чем лепить полную отсебятину.
Вопросы замечания предложения в комментариях жду с нетерпением)
А пойду пилить обновление для Лиспублики)
Это ты намекаешь, что скоро появится раздел "игры" на лиспублике?
Это ты намекаешь, что скоро появится раздел "игры" на лиспублике?
Дурак на раздевание
Дурак на раздевание
Идея для следующей лиспубликанской сходки
Это ты намекаешь, что скоро появится раздел "игры" на лиспублике?
Комментарий удалён. Автор удалил свой аккаунт.
Комментарий удалён. Автор удалил свой аккаунт.
Обучение на повторении – это единственное верный путь. Учиться сразу на своём – это лишь упорствовать в собственных ошибках и заблуждениях, как слепой котёнок пытаясь найти миску с молоком, при учёте, что ты ещё не знаешь, как пахнет молоко.
Что касается написать движок для своей игры. Тоже не соглашусь. Если ты способен написать движок – ты способен написать движок. И не важной для какой игры. Этот опыт так же универсален, как умение держать в руках молот. При минимальном раскладе ты забьёшь гвоздь, при определённом – станешь кузнецом, а если придут орки, то можно и черепа начать крушить.
Ну а по поводу переделки старых игр, тут и соглашусь и нет. У меня нет цели повторить те самые эмоции. Дать что-то новое? Да, я планирую в самом конце добавить ультимативную киллерфичу, которая, скорее всего, никому нафиг не упрётся, но тем не менее. Я это делаю для развития себя как разработчика игр, для того, чтобы те, кому вопрос интересен, смогли почерпнуть что-то новое или полезное. А если в итоге ещё и получится играбельная версия, то, как будто, это будет ещё и приятным бонусом.
Обучение на повторении – это единственное верный путь. Учиться сразу на своём – это лишь упорствовать в собственных ошибках и заблуждениях, как слепой котёнок пытаясь найти миску с молоком, при учёте,...
Комментарий удалён. Автор удалил свой аккаунт.
Комментарий удалён. Автор удалил свой аккаунт.
Именно поэтому начинающие художники сразу рисуют свой шедевр, а не пресловутые кружочки и тысячную вазу.
Поэты сразу пишут поэму-эпос.
Пацаны в гараже собирают свой супер-кар, а не изучают, как работает впрыск, и в чём смысл трамблёра.
Повторение существующих игр – это прекрасный способ войти в гейм дев при наличии уже 300 раз выверенного геймплея, концепции и механик. Разработчик может сконцентрироваться непосредственно на воссоздании конкретных аскпеков, а не тратить полгода на написание диздока.
Брать готовый движок нужно в том случае, если ты просто хочешь сделать конкретную игру. Нет смысла разрабатывать свой велосипед, если ты хочешь сделать свою визуальную новеллу. Уже давно есть РенПай, или Юнити на худой конец. Это подход человека, который хочет получить конкретный результат. Он хочет рассказать миру свою историю.
Подход разработчика в умении написать свой движок – это развитие умения создать в итоге то, чего ещё не было. Это понимания принципов построения архитектуры. Ты не построишь небоскрёб, не выучив сопромат и не сделав по дороге три сотни курсовых, которые делали до тебя тысячи других студентов.
Именно поэтому начинающие художники сразу рисуют свой шедевр, а не пресловутые кружочки и тысячную вазу.
Поэты сразу пишут поэму-эпос.
Пацаны в гараже собирают свой супер-кар, а не изучают, как работает...
Комментарий удалён. Автор удалил свой аккаунт.
Комментарий удалён. Автор удалил свой аккаунт.
Так я и не говорил, что эта серия постов для человека в полным отсутствием фундамента)
Такой контент, в целом, я тоже могу создать без проблем, если это правда кому-то будет интересно, но тут рассчёт на то, что понимание в программировании уже имеется. Поэтому я не расписываю каждую строчку подробно, а просто рассказываю о приёмах, которыми пользуюсь и почему.
Так я и не говорил, что эта серия постов для человека в полным отсутствием фундамента)
Такой контент, в целом, я тоже могу создать без проблем, если это правда кому-то будет интересно, но тут рассчёт н...
Комментарий удалён. Автор удалил свой аккаунт.
О, я тоже когда-то давным давно танчики писал... на паскале!!!
О, я тоже когда-то давным давно танчики писал... на паскале!!!
ту-ду-ду-ду-ду..... *подозрительный взгляд*
Мой путь разработчика игры начался с того, что будучи первокурсников факультета прикладной математики я нашёл в папке с курсовиками по агоритмам работу третьекурсника. И это были танчики на паскале...
ту-ду-ду-ду-ду..... *подозрительный взгляд*
Мой путь разработчика игры начался с того, что будучи первокурсников факультета прикладной математики я нашёл в папке с курсовиками по агоритмам работу треть...
🤣🤣🤣 не, не я ) я вроде максимум на втором на паскале писал. С третьего у нас уже делфи начался
Комментарий