Русский
Русский
English
Статистика
Реклама

Стоит ли в играх перерисовывать только ту часть CANVAS, которая изменилась? Или проще стереть все и нарисовать заново?

Привет. Меня зовут Александр Птичкин. Вот уже 8 лет я обучаю созданию мультфильмов и анимации; 3 года из них я посвятил разработке своего игрового 2.5D HTML5 движка под названием PointJS. За 8 лет вращения в этой индустрии я наработал много материала, которым хочу поделиться тут с вами в блоге. Это мой первый пост на Хабр. Судите строго :) Ваши предложения по улучшению дальнейших статьей пишите в комментариях. Поехали!

Меня часто спрашивают, почему я в своем игровом движке PointJS перерисовываю весь CAVAS заново, а не обновляю только ту часть, на которой произошли изменения.

В этой статье я не претендую на абсолютную истину - как говорится, сколько людей столько и мнений. Я лишь поделюсь своими мыслями, идеями и наработками, которые подвигли меня склониться к полной перерисовке CANVAS. Буду рад в комментариях услышать ваши мнения по этому поводу, чтобы на их основании оптимизировать движок. Ведь в общении рождается истина.

Поначалу звучит очень логично. Где то на краю экрана пробежал вражеский персонаж, и только ту часть CANVAS мы и хотим перерисовать, сэкономив ресурсы. Это было настолько правдоподобно, что я за ночь изрисовал лист A4 в поисках оптимального решения этой задачи. И вот что получилось.

Перерисовывать часть CANVAS мы можем только при выполнении двух условий:

  1. Игровой фон должен быть разбит на части и не являться целым изображением. Судите сами: если фон - целое изображение, то его все равно придется где-то перерисовать. И раз оно цельное, то перерисовать придется полностью, а значит, ни о какой перерисовке отдельной части CANVAS речи быть не может. Дальше в статье вы поймете, что я имею в виду и почему так

  2. Камера в игре должна стоять. Потому что если камера движется, то и вся сцена движется, а значит, всю ее и надо перерисовать

Уже два этих условия выше отсекают уйму игр и механик. А мы еще даже не начали разбираться.

Итак, с фоном для игры мы определились. Как его подготовить, сейчас расскажу.

Я беру готовую сцену из Maya (персонажей, фоны и анимацию я создаю в программе Autodesk Maya, а потом рендерю в формате 2D). Это облегчает дальнейшую работу со сценой и смену ракурса. Но об этом в другом посте.

Затем отключаю все элементы сцены, кроме земли, и вывожу ее изображение. Рендерю с разрешением 3000 на 3000px.

Но на изображении также есть пустые png-области, которые не нужны. Можно было, конечно, поправить в Maya, но мне лень. Я удаляю их с помощью скрипта на php. Таким образом на выходе у меня ровное изображение размером 2786 на 1998. Да, кстати, я написал уйму полезных скриптов для ускорения работы и для того, чтобы не использовать Photoshop, а делать все быстрее и проще. Получился такой собственный Photoshop, но на php :)

Вообще идея разбить фоновое изображение и другие большие объекты на части уже давно используется в играх (и не только). Например, разработчики Diablo 2 делали то же самое. Ведь у них все было растровое (спрайты), и была создана лишь иллюзия 3D. А идея в следующем. Фоновое изображение может быть 10000px на 10000px, экраны же пользователей чаще всего 1920 на 1080px (мобильные и того меньше). Т.е. за пределами экрана, где мы ничего не видим, все равно будет происходить перерисовка фона. На рисунке СИНЕЕ - это рамка камеры в игре (рамка камеры равна размеру экрана монитора пользователя). Все, что за рамкой, мы видеть не должны.

Нам надо от этого избавляться, ведь лишняя перерисовка - лишняя нагрузка. Даже программа анимации Anime Studio Pro (Moho) начинает висеть, если части тела персонажа состоят из картинок 1920 на 1080 (в программе мы их, конечно, уменьшим, но оригиналы-то никуда не денутся и вес файла тоже). Для человеческой руки достаточно иметь изображение 300 на 300px. Что уж говорить о браузерной игре...

Давайте большое изображение разобьем в Photoshop на равные квадраты или прямоугольники (равные, чтобы потом при сборке не устраивать себе танцы с бубном). Например, разобьем на 8 строк и 8 столбиков. Так мы получили 8*8 = 64 прямоугольника размером 348 на 250px. Как разбить изображение в Photoshop, у меня рассказано в этом уроке.

Теперь у нас есть много картинок gif, из которых можно воссоздать, как пазл, изображение фона. Теперь давайте соберем этот пазл на javascript в нашей игре.

Для этого переименуем все файлы в папке, присвоив им числовые названия, начиная с 1 или 0 (как вам удобно.) Я бы делал с 1, так как потом надо будет делать цикл, начиная с 0, и сам 0 выпадет, потому что к переменной при первой итерации добавится 1. Руками переименовывать бессмысленно и долго, а у нас все-таки 21 век на дворе. На MAC Book я это делаю за один клик в программе Name Manager. Например, есть папка с фотографиями под именем img. Я просто перетаскиваю эту папку на экран программы, указываю номер первой фотографии и жму Rename.

Если будете делать руками, не перепутайте порядок! Когда Photoshop рендерил нам разбитый фон, он делал это тоже в определенном порядке, последовательно проходя циклом строки, а потом столбцы. И если вы будете ставить нумерацию кое-как, то потом нужные куски пазла могут оказаться где угодно. Кстати это неплохой способ зашифровать игру (или усложнить ее чтение), создав свой порядок сборки фоток из папки. Языку Javascript без разницы, в каком порядке пробегать столбики и строки, так что лишней нагрузки вы не добавите. Вернемся к примеру: у нас есть последовательность от 0 до 64 следовательно, 64 кусочка.

Далее пишем на javascript простой код, который соберет все это в единое изображение.

Для этого стоит создать массив под названием fon = [], в который впоследствии поместить все изображения в нужном порядке, и создать переменную xy, которая в будущем станет номером фотографии (xy будет принимать участие в формировании ссылки на путь к файлу).

var xy = 0, fon = [];

Затем создаем два цикла, один из которых будет идти по X и добавлять фотографии по горизонтали, а другой - по Y, переходить на новую строку и тем самым добавлять фотографии по вертикали.

for (var x=0; x<8; x++) {for (var y=0; y<8; y++) {  }}

Цикл по x<8 говорит о том, что изображение было разбито на 8 строк. А цикл по y - что было 8 столбиков. Умножив 8 на 8, мы должны точно получить количество картинок в папке img. А их напомню 64.

При каждой итерации этих циклов мы будем прибавлять единичку к xy и добавлять в массив fon выбранное определенным образом изображение.

xy++;fon.push(изображение1);

В игровом движке PointJS есть метод, который добавляет в игру изображение game.newImageObject({ });. Отмечу, что подробнее о том, как устроен движок, о его командах и уроки по нему вы можете посмотреть в соответствующем плей листе. Итак, теперь мы будем каждый раз добавлять в массив fon новый game.newImageObject.

Давайте посмотрим на код целиком:

var xy = 0, fon = []; for (var x=0; x<8; x++) {for (var y=0; y<8; y++) {  xy++;    fon.push(      game.newImageObject({         file : "img/" + xy + ".gif", // путь к файлу         x : 0 + 348*y, // y это шаг. сначала изобаржени будет по x на 400, потом на 400*2 потом н 400*3         y : 0 + 250*x, // тоже смое и по оси y         w : 348,          h : 250      })    )  }};

Давайте посмотрим на строку file : "img/" + xy + ".gif". Благодаря конкатенации строк и тому, что xy у нас переменная, которая всегда увеличивается на 1, мы формируем в ней динамический путь к изображениям:

file : "img/" + xy + ".gif", // путь к файлу // оригинал// то что вышлоfile : "img/" + 1 + ".gif"// img/1.gif file : "img/" + 2 + ".gif"// img/2.giffile : "img/" + 3 + ".gif"// img/3.giffile : "img/" + 4 + ".gif"// img/4.gif//и так далее...

Получили следующее. Это уже мы смотрим в окне браузера

Теперь я предлагаю вам для наглядности чуть подпортить фон и сделать в формуле выше w=346, а h=248. Это нужно, чтобы между клетками появился зазор 2px, и мы смогли увидеть, из чего все же этот фон состоит.

var xy = 0, fon = []; for (var x=0; x<8; x++) {for (var y=0; y<8; y++) {  xy++;    fon.push(      game.newImageObject({         file : "img/" + xy + ".gif", // путь к файлу         x : 0 + 348*y, // y это шаг. сначала изобаржени будет по x на 400, потом на 400*2 потом н 400*3         y : 0 + 250*x, // тоже смое и по оси y         w : 346,          h : 248      })    )  }};

Это, разумеется, еще не все. Пока мы никак не оптимизировали игру - мы лишь расчленили и заново собрали одно изображение. И даже наоборот: еще больше нагрузили компьютер циклами. А теперь сделаем интересную операцию - будем рисовать только те квадраты, которые видим на экране (т.е. которые попадают в рамку камеры в движке). Для тех, кто не знает, за отрисовку всего, что есть в движке, отвечает команда draw();. Напишем условие:

for (var i in fon) {if (fon[i].isInCamera()) fon[i].draw();}

Смотрим, что получилось. Все блоки, что не попали в камеру (на рисунке они прозрачные), больше не рисуются. Вот это и есть первая оптимизация фонового изображения. Дальнейшие изображения я не смогу показать из окна браузера, так как рамка камеры - это и есть размер экрана, а я физически не могу показать то, что находится за экраном. Но я могу нарисовать это, например, в After Effects. Еще одна полезнейшая программа для жизни аниматора и монтажа.

Однако, если вы помните, мы говорили о другом - как очищать CANVAS? Целиком или кусочком?. Сейчас мы провернули хорошую подготовительную работу, чтобы четко ответить на этот вопрос. Потому что предварительный ответ мы получили в начале статьи: если камера движется, то картинка сменяется полностью и тогда придется рисовать все без исключения.

Приступим. Я предлагаю разместить на нашем верно отрисованном фоне несколько объектов: персонажей, дома и деревья. Теперь представим ситуацию: мы пьем чай и не играем, следовательно, наш персонаж не двигается. Однако вокруг него бегает другой персонаж (наверняка с дурными намерениями). Вот я нарисовал траекторию, по которой он пробежал (сделал за ним шлейф):

Посмотрим, какие блоки он задел и что надо перерисовать на сцене. Эти участки я выделил красным.

По рисунку видно, какую область надо перерисовать (она красная). Все остальное можно не трогать, так как оно не изменилось. Как бы теперь я построил алгоритм, который смог бы определить затронутые блоки и участки, нуждающиеся в перерисовке?

Обсудим на уровне идеи. Допустим, мы создадим квадрат/прямоугольник по размеру бегущего персонажа. Хотя нет, даже не так. Зачем создавать, когда можно просто вызывать StaticBox() этого объекта? Да, в движке есть команда getStaticBox(); - область загруженного изображения, равная размерам этого изображения. Теперь напишем простое условие, в котором будем проверять, каких объектов сцены коснулся этот StaticBox();. Вы помните, что у нас уже был массив fon = [];? А помните, мы загружали деревья и дома? Их нельзя разместить в массиве fon. Значит, для них создадим отдельный массив obj, и будем проверять сразу оба массива. Итак, пишем код:

var collisionFon = []; // создали массив в котором будем хранить всех с кем столкнулись из фонаvar collisionObj = []; // создали массив в котором будем хранить всех с кем столкнулись из obj for (var i in fon) {if (player2.isStaticIntersect(fon[i].getStaticBox())) {collisionFon.push(fon[i])  }}for (var i in obj) {if (player2.isStaticIntersect(obj[i].getStaticBox())) {collisionObj.push(obj[i])  }}

Уже на этой стадии обработка циклов может отнять у компьютера много ресурсов. Однако мы двигаемся дальше. Я специально на картинке выше провел персонажа так, чтобы он не коснулся дома, но коснулся клетки фона. Зачем? Все просто: если перерисовать в этом месте фон, а потом персонажа, не тронув дома, то фон окажется выше этого дома

И вот главный вывод статьи:

Значит нам нужно проверять еще и родительские связи!

Перерисовав фон и персонажа, нам следует узнать, а не перекроет ли этот фон еще что-нибудь. Т.е. необходимо пробежаться циклом по всем элементам, что попали в массивы collisionFon и collisionObj, и посмотреть, каких еще объектов они касаются на сцене, а точнее - какие объекты находятся над ними. В моем движке есть функция проверки оси Z. По сути там реализована эмуляция оси Z через сравнение осей Y, но сейчас это не столь важно. Самое главное, что это большой блок, который тоже будет поглощать ресурсы. Ведь теперь все блоки, которые предполагается перерисовать, нужно собрать в общий массив, чтобы сравнить их оси Y, чтобы выяснить, кто из них выше, а кто ниже. Короче, я даже формулы писать не буду. Думаю, вы поняли: расчеты будут громоздкими и нагрузят компьютер больше, чем если бы мы просто стерли весь CANVAS и нарисовали его заново. Я даже сомневаюсь, что смогу их провести, а в том, что это безотказно отработает в MMORPG-играх, вообще не уверен. С учетом всех наложений и косвенных (не на прямую) попаданий на здания, к красной зоне я добавил и зеленю область которую тоже нужно перерисовать.

При этом вы заметили, что в примере пробежал только один вражеский персонаж. И мы рассмотрели лишь частный случай, когда игрок стоит на месте, а камера не двигается, чтобы выполнить условие выше. А сколько движухи получилось! Даже если мы все же сделаем расчеты, придется перерисовывать почти половину CANVAS. Так зачем тратить столько сил, менять механику движка, обработав при этом небольшой частный случай и в итоге перерисовав полCANVAS? Я еще понимаю, если бы перерисовке подверглись два-три блока, но, кажется даже при самом простом варианте малой кровью не отделаться. Впрочем, решение, конечно, за вами!


Что ж, на этом я завершаю свою первую статью. Надеюсь, вы не очень утомились. Если есть какие-то мнения по поводу вышеизложенного - пишите в комментариях, обсудим. Повторюсь, в общении рождается истина. А она нужна, чтобы улучшать движок. Всем спасибо и до встречи в новых статьях.

Автор текста и всех материалов статьи: Александр Птичкин, основатель образовательного проекта Mult-uroki. Написано специально для habr.com. Копирование или иное использование материала без письменного разрешения автора запрещено.

Источник: habr.com
К списку статей
Опубликовано: 15.03.2021 16:12:10
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Программирование

Разработка игр

Html

Создание игры

Html5

Игровые движки

Как сделать игру

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru