раз ты спрайты по очереди рисуешь может все таки метод поспрайтовой рисовки?
ну когда выбирается сектор с нахлестом вокруг спрайта и все что там есть рисуется
Вид для печати
Ага. С памятью под спрайты проблем нет. Сейчас распределение пока такое:
Банк0_0000-7ffff - спрайты героя и половины врагов.
Банк0_8000-ffff - тайлы уровня
Банк1_A000-dfff - текущий клип.
Еще много банок свободных.
Спрайты со сдвигом на 2+маска
- - - Добавлено - - -
Ага, не идеальное решение. Но если я правильно понимаю твой способ то там может произойти тоже самое.
Кстати способ рисовать спрайты не за один кадр я решил из-за скорости отрисовки. Не хватает у меня времени на всех.
- - - Добавлено - - -
jerri, на отрисовку всего за один кадр у меня не хватает времени кадра. Но возможно стоит покопать в эту сторону если с горизонтальными секторами не взлетит.
Если ты не запрещаешь прерывания, то что значит "не хватает времени на всех"?
svofski, да, я не запрещаю. но я стараюсь уложиться в кадр чтобы пониматься где у меня луч бежит и обходить его. Возможно я не понял твою идею.
- - - Добавлено - - -
к сожалению у меня не получается уложится в 25 fps при отрисовке всех спрайтов. сейчас основная масса спрайтов (скелеты) рисуется 12.5fps. кадый спрайт рисуется в 3 плоскости с маской.
Я к тому, что может быть просто рисовать как рисуется и все, не думать пока о мелочах. Нормально обогнать луч тяжело. Ты еще устанешь от этого, а потом переделаешь что-то в игре и все придется заново придумывать.
Meanwhile, такая безумная идея -- что если пожертвовать 8К для слоя маски? На самом деле меньше, потому что не весь экран ведь задействован. Жирно конечно, но открываются дополнительные возможностипятачка
Назовем его слой Ъ. Вначалѣ Ъ пустъ.
Кадр 1.
Затираем группу спрайтов 1 в предыдущей позиции. Затираем только те биты, которые не установлены на соответствующем месте в слое Ъ (в первом кадре это все биты)
Рисуем группу спрайтов 1 в новой позиции и OR-им маску в слой Ъ.
Кадр 2.
Затираем группу спрайтов 2 в предыдущей позиции. Затираем только те биты, которые не установлены на соотв. месте в слое Ъ (тут мы обходим стороной то, что было отрисовано в первом кадре)
Рисуем группу спрайтов 2 в новой позиции и OR-им маску в слой Ъ.
... так продолжается столько, сколько предусмотрено промежуточных фаз ...
Кадр X межфазный. Чистим слой Ъ и все начинается сначала.
jerri, скорость привязана к прерываниям. Если апдейт+ draw не уложились в кадр, то в следующий раз апдейт выполнится столько раз сколько прошло прерываний с прошлого раза.
Интересно. Если я правильно понимаю, то если чистить слой ъ целиком, то спрайты в кадрах 2,3 и остальных межфазных кадрах потеряют возможность быть корректно сбленджеными так как при новой фазе могут быть стёрты. Или я что-то упустил?
- - - Добавлено - - -
В прерывании держать апдейт для меня означает что я не смогу ничего нарисовать когда луч вверху экрана в области бордюра.
Сейчас обработчик выполняется в середине кадра. В начале кадра рисуется то что в нижних позициях, а после апдейта рисуется то что нужно в верхних позициях. Таким образом я избавляюсь от пересечения с лучем.
- - - Добавлено - - -
По поводу моего метода разделения области отрисовки на 4 вертикальные области.
svofski, да, ты прав что спрайты могут скучковаться в одной области. Но для меня будет ок такие случаи имхо. На msx и nes тоже есть подобное ограничение. Спрайты начинают моргать. Если не получится это излечить, то думаю это ок.
Нет, маска в Ъ блокирует только затирание. У тебя беда в том, что межфазное затирание может затереть предыдущую фазу. Эта маска как раз не дает этого сделать.
Затиралка маскирует затирание по маске из Ъ.
Рисовалка делает ИЛИ своей маски в Ъ. В остальном она рисуется как раньше. Блендится со всем предыдущим.
Может быть правда можно обойтись просто пометкой блоков 8х8, как говорит jerri, но чего-то я пока не соображу как.
Про первые межфазные кадры понятно что маска будет блокировать. Но как она поможет в первом кадре новой фазы если она будет очищена? Вот это я не пойму.
- - - Добавлено - - -
В методе Jerry меня смущает оверхед на установку/восстановление стека, и других параметров спрайта каждый раз для маленького блока 8*8. Но возможно это как-то тоже можно избежать.
- - - Добавлено - - -
svofski, твой способ это я так понимаю некое подобие стенсил буфера для стирания спрайтов. в идеале он должен хранить отрисованные в него маски всех присутствующих на экране спрайтов. Но как его готовить корректно (удалять из него то что уже не на экране) не каждый кадр я что-то не вижу.
- - - Добавлено - - -
в теории этот стенсил буфер можно готовить каждый кадр для всех спрайтов которые не нужно перерисовать в текущем кадре. Затем для спрайта который нужно отрисовать в текущем кадре удаляется с экрана его старая версия по маске стенсила чтобы не испортить те спрайты которые не нужно перерисовать. Затем на экран рисуется этот спрайт с маской чтобы не запортить уже нарисованные спрайты. Если в текущем кадре нужно обновить ещё один спрайт, то нужно в стенсил буфер нарисовать маску спрайта который на предыдущем шаге отрисовал на экран.
- - - Добавлено - - -
Можно попробовать для начала стирать спрайты не блоком как сейчас, а по маске спрайта. С учётом того что спрайты двигаются на 2 пикселя, то по идее глюки где не восстановиливается изображение после стерания будет небольшим.
В первом кадре новой фазы она будет пустая, потому что в первом кадре новой фазы ей нечего защищать.
Слово стенсил подходит. Чистить его надо будет не каждый кадр, а после отрисовки всех спрайтов. Допустим у тебя все разбито на 4 кадра:
Начали. Отрисовали кадр 1, 2, 3, 4 (все это время маска там копится, не давая затиралке из кадра 2 затереть то, что было нарисовано в кадре 1). Очистили. Отрисовали кадр 1, 2, 3, 4. Очистили. итд
Первому кадру не надо стирать с маской, потому что там гарантировано пусто, а последнему незачем рисовать в маску, потому что она будет стерта сразу после отрисовки. На этом можно сэкономить.
svofski, приведу пример который меня смущает. У нас два спрайта один перекрывает второй частично. Первый был нарисован в первой межфазе. Второй в последней межфазе.
Теперь мы очищаем стенсил маску.
Начинается новая фаза. Как стереть старый первый спрайт не поломав второй если у нас нет информации в стенсиле где он нарисован?
Да, так пожалуй не выйдет. Первая интерфаза затрет последнюю.
Сделал как описал выше. То есть просто стирать по маске в старых позициях, потом рисовать по маске в новых. С разбиением на два вертикальных участка получилось почти избавиться от моргания когда луч догоняет отрисовку. Но что-то оно все равно не очень хорошо получилось, поэтому записывать видео пока не стал.
Завтра с утра ещё покумекаю.
Стирание по маске и рисование по маске одного спрайта выходит конечно очень накладно. Возможно можно ускорить для случаев когда спрайты не перекрывают друг друга, но как это определить быстро пока идей немного.
- - - Добавлено - - -
Можно ещё упростить отрисовку с если не вытирать по маске (ana), а только делать ora. Будет пошустрее. Плюс такого подхода не нужно хранить маску через байт с цветом. Экономия памяти под спрайты и вытирать по маске будет чуть быстрее.
N спрайтов.
Есть карта тайлов, что-то типа 32х20. Вначале там все -1 (или какой-то еще признак отсутствия спрайта в клетке).
Дополнительно таблица флагов: непересекается[N] - вначале все true, оптимизим.
цикл i по всем спрайтам
Берешь спрайт i. Определяешь какие клетки он занимает и -
Если во всех клетках спрайта i пусто:
записываешь в них индекс спрайта i.
иначе, если там не пусто:
непересекается[i] = false
из каждой клетки под спрайтом i берешь число m (они могут быть разные), записываешь в таблицу флагов непересекается[m] = false
Когда фон чёрный, быстрее всего, конечно, выводить и стирать xor-ом. Другое дело, устроят ли артефакты при пересечении спрайтов...
b2m, у меня данные спрайтов лежат на квазидиске. В момент отрисовки я их обязательно должен прочитать в регистровую пару bc. При таком способе xor тоже быстрее? Мне видится что нет, но может я что-то упускаю.
А как xor тут вообще поможет? Он работает только если четное число раз ксорим сами с собой одно и то же. Даже если бы тут был черный фон, ксоры между разными спрайтами все перексорят к чортям.
1) 1 й спрайт xor 2, 2) 1 xor 2 xor 3, 3) 1 xor 2 xor 3 xor 4
1 xor 2 xor 3 xor 4 xor 4=1 xor 2 xor 3 - вернулись к шагу 2)
1 xor 2 xor 3 xor 3=1 xor 2 - вернулись к шагу 1)
Но в местах наложения нескольких спрайтов на экране каша.
Самое крутое ксоренье мне запомнилось из clrs b2m'a. Я на самом деле до сих пор не понял, как это было сделано =)
В общем четное количество ксоров чего угодно расксорятся потом обратно. Но в промежутке-то на их месте получается полный фарш. А с цветами так и совсем печалька.
svofski, если я все правильно понимаю то xor это инверсия по маске, поэтому и проворачивается фарш обратно при двойном xor. :) Только нужно держать цвет инвертированным.
О, понял. Действительно будет быстрее, так как маскирование при отрисовке нового спрайта не нужно делать. А старый спрайт стирать с той же скоростью.
- - - Добавлено - - -
Размышляя о том куда копать дальше понял что преследуя изначальную задумку загнал себя в тупик.
Изначально я придумал что у каждого спрайта есть счётчик показывающий в какие кадры его нужно рисовать, а в какие нет. Это цикличный байт типа 00010001 для монстра и 01010101 для героя. В каждый кадр этот счётчик скролился в право и если 1 попадал во флаг переноса, то мы рисуем.
Это дало возможность рисовать спрайты с разным fps, но привело к тому что непонятно как их корректно блендить. Плюс нет двойной буферизации. Это я решил излечить меняя порядок отрисовки. В итоге как и говорил
svofski, это сложно контролировать. То есть это работает если на экране с 6-8 спрайтов, но не работает с 2.
Дальше я планировал добавить другие штуки Аля пули и т.п. что ещё больше усложнит убегание от луча. Плюс ко всему этому ещё то что этот подход хоть техническии и не запрещает прерывания, но все же полагается на них чтобы работало убегание от луча. В итоге я экономлю перформанс на разном fps для спрайтов, но трачу его на синхронизацию с прерываниями и иду по пути очень сложного и не стабильного рендера. В общем все взвесил и решил что попробую сделать с двойной буферизацией, если будет ещё медленнее или хуже, то просто забью на бленд спрайтов и буду рисовать без маски и с запасом чтобы новый спрайт стирал свою старую копию на экране.
parallelno, а это обязательно, чтобы движущиеся объекты проходили "насквозь" друг друга ?
Если я правильно понял обсуждаемую проблему то есть вариант сделать - что два движущихся объекта столкнувшись расходятся в разные стороны, т.о. нет перекрытия спрайтов. Так сделано во многих играх. Такой подход упростит задачу.
блин 30 лет технологий и снова сочиняем велосипеды, самокаты и прочие велобеги.
1 синхронизация по прерываниям.
1.1
50 раз в секунду выполняются прерывания.
выбираем кратность и например со скоростью 1 через 2 выполняем обработку всех персонажей на экране.
результат обаботки записываем в один из двух списков выводимых спрайтов
1.2
во внешнем рендере выбираем один из двух списков и отрисовываем все спрайты по списку выбранным способом
плюсы - стабильная скорость персонажей, стабильная скорость героя.
Теперь об отрисовке
На примере игры Jack the Nipper 2
вывод спрайтов с маской поверх фоновой графики и других спрайтов
также возможен спрайт за предметами Фореграунда
Титульный экран
Вложение 77735
состоит если присмотреться из блоков 32*32 которые состоят из элементов 8*8
Вложение 77736
карта экрана представляет собой массив элементов
и дополнительную таблицу действий где указываются элементы которые нужно восстановить на экране или элементы которые нужно отрисовать из набора модифицированных элементов
при отрисовке персонажей создается модифицированный набор элементов куда копируются элементы фона поверх которых накладываются спрайты.
на экран выводятся либо модифицированные элементы, либо фоновые которые надо восстановить
Вложение 77737
metamorpho, ага, спасибо. Но хотелось бы иметь возможность пересечения спрайтов.
- - - Добавлено - - -
jerri, спасибо за почву для размышления.
Изобретение велосипедов эта один из элементов вдохновения для меня, так что все ок! :)
Нет, что OR, что XOR, работают одинаково быстро. Я к тому, что при использовании XOR при выводе спрайтов не нужно заботиться о пересечении спрайтов. К тому-же, используется одна и та-же процедура, как для вывода, так и для стирания (главное стирать в тех-же координатах).
Привет всем...
Обычно мерцание спрайтов - буфером устраняется...
Но тут видать - вариант особый...
Для особого варианта - можно сделать так...
1. Делаем карту экранов.
То есть - каждый экран делим на области.
Экран задействован не весь, поэтому делим через массив...
2. Смещаем персонажей - не по номерам,
а по областям экрана, в которых они находятся...
3. Между областями экрана по периметру -
делаем буферные области, которые обсчитываются
по-другому, чтобы устранить мерцание спрайтов
между областями экрана...
???
А вот в exolon кстати как сделано? Там тупой xor вроде если смотреть на спрайты. Ну тайлы да, иной раз сверху перекрывают (иной раз нет). И плохо не выглядит и быстро, зачетно.
Правда в нем нет background-a ...
- - - Добавлено - - -
Буфера скушают много памяти, 16 цветов ведь. Если полностью буферизировать. Там и так на прешифт спрайтов потратится ого..
Jerri собственно всё расписал как делается с буферизацией и раскладкой по кускам буферов обновлять/не обновлять/обновлять, но потом. Но имхо спек-то побыстрее да и он "одноцветный"
Раз уж все высказывают свои предпочтения -- я больше за полноцвет. Просто потому что на Векторе мало полноцветных игр. Ради этого я готов потерпеть немножко мерцание и артифаченье.
Имхо на векторе не так уж мало многоцветных игр с моргающими спрайтами.
Но много и хороших годных игр. Я сам вот не знал что вообще был такой компьютер как Вектор оказывается (а он смотрю очень крут для тех времен). Детство с БК-Синклер. И да, спрайты моргали во многих играх. Считалось вполне нормальным в те годы. Челендж мол сделать чтобы не моргали и не потерять полноцветность и скорость. Я не смог так на УКНЦ. Полноцветно только штук 5 спрайтов 16х16 с бэкграундом на 25fps вышло. Но .. многое можно пофиксить геймдизайном же.
Если игра увлекательная и красивая, то если там мельтешит где-то иногда, это не страшно. Даже на NES все время всякая хрень выскакивает в играх там и сям. Геймплей важнее.
Привет всем...
Думаю без буфера - спрайты будут мерцать...
Пусть померцают...
???
Там сложнее другое. Я знаю этот набор картинок, это называется 2d pixel dungeon asset pack, увы - самое сложное рисовать картинки для игры самому (тайлы/спрайты), если игру не передирать с чего-то - это дело застрянет на полпути (рисовать 8-бит графоний не все умеют, программерам это сложно до ужасу, рисовал паука в 4-х цветах почти полдня и он получился говеным, после чего руки начали опускаться)
Размышляя о том как организовать рендер с двойной буферизацией родил вот такой псевдокод ниже. Сильно смущают большие накладные расчеты. Можно конечно попробовать заменить tileGfxBuffer на связный список чтобы при отрисовке пропускать незадействованные тайлы, но не уверен что это сильно поможет.
Возможно есть элегантный и быстрый способ оргинизовать тайловый рендер, поделитесь знанием.
- - - Добавлено - - -Код:; =====================================================================
; data
; a pointer to tileGfx in the tileGfxBuffer
currentTileGfxPtr:
.word TEMP_ADDR
; a table of tileGfx pointers
tileGfxPtrTable:
.storage ROOM_TILES_W * ROOM_TILES_H * ADDR_LEN, 0
; an array of tileGfx
tileGfxBuffer:
.storage TILE_GFX_MAX * TILE_GFX_LEN, 0
; struct tileGfx:
; tilePtr - a pointer to a tile in tileGfxPtrTable. if it's 0, that means tileGfx is vacant
; tileGfxData - graphics data. if it's empty, that means this tile needs to be erased from the screen.
; =====================================================================
; tile rendering routine
currentTileGfxPtr = an address of the first tileGfx in tileGfxBuffer that has tilePtr = 0
for every visible sprite
for an every sprite tile
calculate a tile index N in tileGfxPtrTable
if tileGfxPtrTable[N] == 0:
tileGfxPtrTable[N] = currentTileGfxPtr
currentTileGfxPtr->tilePtr = &tileGfxPtrTable[N]
draw a tile w/o a mask into currentTileGfxPtr->tileGfxData
currentTileGfxPtr = an address of the next tileGfx in tileGfxBuffer that has tilePtr = 0
else:
draw a tile with a mask into currentTileGfxPtr->tileGfxData
for every tileGfx in tileGfxBuffer
if tileGfx.tilePtr == 0:
continue
if tileGfx.tileGfxData is not empty:
draw tileGfxData on the screen.
tileGfx.tileGfxData = empty
else:
erase tile on the screen.
tileGfx->tilePtr = 0 ; set 0 a tileGfx pointer in tileGfxPtrTable
tileGfx.tilePtr = 0
Уточню что tileGfx.tileGfxData = empty не означает стирание данных, а только маркировка их как стертых.
Структуру tileGfxData не привожу так как до конца ещё не определился, но там как минимум есть флаг говорящий что данные есть или данных нет.
- - - Добавлено - - -
Поясню как это работает если код выше не очень понятен.
У нас есть таблица адресов на структуры tileGfx.
Эта таблица имеет столько элементов сколько нужно тайлов на экране.
У нас есть массив tileGfxBuffer из элементов tileGfx.
Отрисовка состоит из двух этапов.
Первый это заполнить массив tileGfxBuffer данными спрайтов которые хотим отрисовать.
На втором этапе мы копируем данные из tileGfxBuffer на экран, или стираем с экрана старые спрайты.
Таблица tileGfxPtrTable нужна что быстрее найти элемент tileGfx в массиве tileGfxBuffer в который нужно скопировать данные спрайта.
Если честно, то я не совсем понял идею.
Напиши пожалуйста что скрывается под многоточия и и знаками вопроса. :)
- - - Добавлено - - -
Два списка выводимых спрайтов для того чтобы не пересекаться с измененными данными обработки всех персонажей?
А что включает в себя таблица действий? Можешь прописать по подробнее, пожалуйста? :)
- - - Добавлено - - -
Ещё было бы здорово если бы ты описал подробнее из каких элементов состоит карта экрана и структуру набора модифицированных элементов.
Если есть возможность переключать экраны:
Рисуешь тайлы в оба экрана изначально. Затем рисуешь в например первый экран спрайты уж там с маской без маски как угодно. Помечаешь битом в таблице тайлов куски 16х16 скажем которые были затронуты прорисовкой спрайтов. Переключаешь экран туда. Затем восстанавливаешь тайлы которые были помечены и были под спрайтами. Рисуешь аналогично на второй экран, помечаешь затронутые куски, переключаешь экран туда, затем оттуда тоже восстанавливаешь затронутый бэкграунт.. ну ит.д.
Если нет возможности переключать экраны:
Рисуешь то-же самое в куске памяти. Помечаешь какие тайлы затронули нарисованные спрайты уже двумя битами типа 11. Выводишь на экран куски из буфера которые были затронуты (помечены первым или вторым битом). Восстанавливаешь тайлы помеченные первым битом при этом перемещая его во второй. (это будет нужно если спрайт "прыгнет" через тайл чтобы вывести на экран все-же восстановленный тайл бекграунда потом хотя мол туда ничего не рисовалось)
У меня нет возможности переключать экраны, поэтому спрошу про твой второй подход.
Скажи пожалуйста а почему помечать какие тайлы затронули битами типа 11 лучше чем в моем варианте? И ещё вопрос, в этом варианте не используется буфер тайлов? Все делается в "куске памяти"? Какая его структура?
А в тайловом буфере ведь все-равно будешь хранить информацию в битах мол тут проходимо, тут непроходимо, тут кислоту кто-то разлил на пол и наступить нельзя, тут дыра в полу и провалишься скажем на нижний уровень если не перепрыгнуть и т.д.
То-есть весь экран будет поделен скажем на 16х16 куски в которых будут # тайлов пола собственно (бекграунда) и плюс еще какая-то битовая информация. Можно сделать что в эту битовую информацию до кучи пишется бит "сюда рисовали спрайт в этот квадратик и его надо вывести на экран и потом восстановить взад тем тайлом который там был до этого и перевести бит изменений во второй" и второй бит "сюда рисовали что-то на прошлом кадре, но сейчас спрайт убежал и восстанавливать ничего не надо, а просто вывести на экран этот кусок 16х16 и обнулить бит".
(с буферизацией будет тормозно ибо скажем спрайт находится посредине и затрагивает 4 тайла вокруг себя - их придется все четыре перерисовывать на экран сначала, а затем прорисовывать в буфер обратно взад уже без спрайтов и это будет оверхед прорисовки, но что поделать, зато нормально выглядеть станет)