КДПВ:
Вложение 68602
(Пре-альфа арканоида растет там же, где и тетрис на гитхабе)
Вид для печати
КДПВ:
Вложение 68602
(Пре-альфа арканоида растет там же, где и тетрис на гитхабе)
tnt23, а какое строение экрана у Океана?
256 строк по 512 пикселей в строке в монохромном режиме, и 256 пикселей в строке в цветном режиме. Экран начинается с адреса 4000h и растет с левого верхнего угла вниз до 40FFh, затем следующая колонка 4100h-41ffh и так далее. В монохромном режиме один бит - один пиксель.
В цветном режиме цвет каждого пикселя задается двумя битами: одним с адреса 4000h (если взять для примера верхний левый угол), а другим с адреса 4100h. Получается, что в цветном режиме есть два цветовых битплана, которые "перемешаны" друг с другом: один битплан занимает адреса 40xxh, 42xxh, 44xxh..., второй занимает адреса 41xxh, 43xxh, ... .
По поводу рисования спрайтов. Сейчас для будущего Арканоида взята без изменений процедура PaintBitmap из Тетриса, которая просто выводит на экран цветной битмап 8x8 пикселей:
В аккумуляторе передаются биты плоскостей, которые нужно нарисовать. Сам битмап кодируется просто: в памяти последовательно идут 8 байт для первого битплана, следом за ними 8 байт для второго битплана. Если установлен бит 0 в аккумуляторе, то выводятся (копируются) байты для первого битплана, для установленного бита 1 - то же для второго битплана.Код:; *************************************************
; PaintBitmap - нарисовать битмап 8х8
; HL - адрес битмапа
; BC - X и Y
; A - биты плоскостей
; *************************************************
Скрытый текст
Код:; *************************************************
; PaintBitmap - нарисовать битмап 8х8
; HL - адрес битмапа
; BC - X и Y
; A - биты плоскостей
; *************************************************
PaintBitmap
di
push bc
push de
push a
; Отключаем ПЗУ для доступа к экранному ОЗУ
mvi a, ENROM
out BANKING
push hl
lxi h, SCREEN
mov d, b
mvi e, 0
dad d ; hl = SCREEN + X*256
mvi d, 0
mov e, c
dad d ; hl = hl + Y
pop de ; de = адрес битмапа
pop a ; плоскости
Plane1
rrc
jnc Plane2
call Copy8
Plane2
rrc
jnc PlaneDone
; Второй план битмапа
push h
lxi h, 8
dad d
xchg
; Перейдем ко второму плану экрана
pop h
inr h
call Copy8
PlaneDone
; Включаем ПЗУ обратно
xra a
out BANKING
pop de
pop bc
ei
ret
Copy8
push h
push d
push a
mvi c, 8
PBLoop ldax d
mov m, a
inx d
inx h
dcr c
jnz PBLoop
pop a
pop d
pop h
ret
[свернуть]
PaintBitmap, как всякая сделанная на коленке процедура, проста и вместе с тем далека от эффективности. По уму ее бы переделать хотя бы на работу со стеком для быстрого копирования в экранную память, но это не главная проблема. Что было хорошо для Тетриса, уже мало подходит для Арканоида с необходимостью ловли коллизий нескольких спрайтов, причем по возможности на пиксельном уровне.
По мотивам обсуждения с ivagor переделал простой вывод мячика на более сложный, буферно-слоёный. Теперь мячик рисуется не прямиком на экран, а в отдельный буфер 8x16 пикселей:
- сначала в буфер рисуется фон. Пока он исключительно черный, но в перспективе ничто не мешает вместо нулей подложить изображение размером с игровое поле или вообще во весь экран;
- затем в буфер отрисовываются кирпичи из ближайших к мячику окрестностей;
- затем в буфер отрисовываются несуществующие пока спрайты несуществующих еще врагов;
- и апофеозом по OR туда лепится мячик. Я уже морально созрел добавить маску мячика (в виде множества фаз) и лепить его в буфер по AND маски и OR спрайта.
Полученный бутерброд уже отдается битблиту для вывода на экран. В светлом недалеком будущем это, конечно же, будет делаться с оглядкой на положение луча.
Вложение 68712
Сейчас все это довольно громоздко, изобилует копипастой, и к тому жеизотропноанизотропно (работает хорошо, когда мячик летит вправо, и хуже, если влево). Также вызывает изжогу нагромождение кода по определению, какие части каких кирпичей когда отрисовывать, и какие кирпичи выбивать при разных положениях мячика.
Вопрос времени.
tnt23, признайся, ты специально провоцируешь мою занудную компоненту? :) Если есть различие по направлениям, то это анизотропия.
Откуда я помню этот ненужный термин
1. Анизотропное шоссе у Стругацких.
2. Каждый год много лет долбил про диффузное (однородное и изотропное) звуковое поле, в итоге даже сам запомнил.
В продолжение закадровой дискуссии про новую обработку приращений по X и Y. Все же вариант с "задержками" на мой взгляд менее гибкий и плавный по сравнению с фиксированной точкой. Например приращение 1.25 (5/4) в варианте с задержками будет приводить к рывкам на 5 точек с заметными паузами между ними, а при использовании фиксированной точки будут 3 приращения на 1 и одно приращение на 2, т.е. заметно плавнее.
Мне представлялось оправданным использовать набор фиксированных углов вместо произвольных. Вырожденный вариант, понятно, только 1 к 1 (45 градусов), более играбельный - наборы 1/2, 1/3, 1/4 и 1/5. Можно подобрать максимально точно аппроксимирующие углы, кратные 15 градусам.
Вопрос плавности движения остается открытым.
В данном случае лучше оперировать конкретными цифрами. Прикинул на матлабе.
Углы от 0 до 90 градусов с шагом 15: 0 15 30 45 60 75 90
Синусы этих углов: 0 0.2588 0.5000 0.7071 0.8660 0.9659 1.0000
Косинусы: 1.0000 0.9659 0.8660 0.7071 0.5000 0.2588 0.0000
Приблизительные аппроксимации дробями (для синусов, для косинусов, понятное дело, аналогично, но в другом порядке): [0 1/4 1/2 8/11 19/22 1 1]
Хочу обратить внимание, что 8/11 и 19/22 будут плохо (и очень плохо) обрабатываться с "задержками" и вполне приемлемо с фиксированной точкой, по крайней мере с точностью дробной части в байт. Для такой точности можно даже и получше аппроксимировать.
- - - Добавлено - - -
Вместо 8/11 и 19/22 можно использовать 3/4 и 4/5, но это все равно плохо.
По-моему, это интуитивно все то же рисование по Брезенхему, иносказательно.
Почему вариант с задержками будет тормозить, не пойму.
(16-битная арифметика в 8080 так себе)
Это скорее DDA
Про тормоза я не говорил, но я писал про рывки с паузами в случае использования "задержек" для дробей вида 3/4, 4/5 и тем более 8/11 и 19/22, т.е. у которых числитель (величина "рывка") много больше единицы (знаменатель, который фактически "задержка", конечно еще больше, т.к. мы говорим о дробях <=1, а дроби >1 легко получить масштабированием).
ivagor, спасибо за ссылку на DDA, почитаю-подумаю.
Будучи выраженным аудиовизуалом, накликал пикселей в graf (https://github.com/timtashpulatov/ok...aster/graf.asm), чтобы лучше осмыслялось:
Вложение 68719
Я правильно понимаю, что других способов растеризации траектории мячика, движущегося равномерно и прямолинейно, кроме приведенных на картинке исчезающе мало? И вопрос только в том, нужны ли здесь операции с фиксированной точкой (я вовсе не против их попробовать) или, поскольку результат один фиг растровый, можно сразу обойтись восьмибитными целыми.
tnt23, я не понял, что ты нарисовал. Можно визуализировать лучи с шагом 15 градусов по вышеприведенным приращениям, только программировать не хочется.
Я нарисовал траектории движения объекта (пикселя или спрайта) для разных соотношений dx/dy. Снизу вверх: 2/1, 3/1, 4/1 и 5/1.
- - - Добавлено - - -
Пришлось полезть в гугл за воспоминаниями об арктангенсе. Соответствующие вышеприведенным соотношениям углы вышли 26, 18, 14 и 11 градусов. Набор более живенький, чем просто 45 :)
Мячик отлетает как шальной, поэтому может быть есть смысл сделать какой-то разумный минимум и сфокусироваться на других аспектах игры. А детализацию углов оставить на потом, если хватит сил.
Есть важный аспект, он вроде как-то всеми подразумевается, но я не уверен, что все было проговорено вслух. Шарик имеет широкий диапазон скоростей. От вообще чуть ли не пиксель на кадр (если объесться (-S-) ) до омгвтфbbq что это было. Это усложняет использование алгоритма Брезенхема, хотя не запрещает: вместо интервала времени между шагами равного единице берем дробный интервал (насколько я понимаю, это то, что ivagor называет вариантом с задержками). Если шарик делает больше пикселя за кадр, что по-моему очень часто случается, понадобится по нескольку итераций на кадр.
DDA может быть предпочтительней, потому что можно учесть все сразу в коэффициентах и считать координаты за одну итерацию на кадр. Это немного невосьмибитно, но 16-битное сложение не такое уж страшное у 8080.
Я думаю, что взвесив все, я бы сделал что-то среднее. DDA рассчитывать для минимальной скорости, а скорость -- это будет число итераций DDA на кадр. В этом случае коэффициенты должны поместиться в 8 бит и вообще вся арифметика 8-битная. При том, что углы наклона дискретные, все коэффициенты могут быть просчитаны заранее и забиты в код уже в виде готовых констант.
Псевдокод, совсем не похожий на Си. Входные данные: x_direction, y_direction ∈ [-1,1], dda_table_x[], dda_table_y[] - коэффициенты приращений такие, что [0,1) -> [0,255). angle_index - индекс угла в табличке коэффициентов. Подразумевается, что строго горизонтальные и строго вертикальные полеты мяча невозможны. ball_x, ball_y - координаты мяча.
Я практически уверен, что вместо dda_table_x[] и dda_table_y[] и одного индекса угла, можно сделать dda_table[] и два индекса угла. Но это будет оптимизация шкуры неубитого медведя, поэтому пока в сторону.Код:for (int i = 0; i < speed; ++i) {
x_accu += dda_table_x[angle_index];
if (x_accu & 0x100) { /* carry */
ball_x += x_direction;
x_accu &= 0xff; /* clear carry */
}
y_accu += dda_table_y[angle_index];
if (y_accu & 0x100) { /* carry */
ball_y += y_direction;
y_accu &= 0xff; /* clear carry */
}
}
Озвучу, то что я предлагал tnt23 - использовать арифметику с фиксированной точкой в варианте байт целый и байт дробный, т.е. фактически дробные числа для перевода в этот формат нужно умножить на 256. Например .75*256=192, 12.345=примерно 3160 и т.д. Для позиционирования при отрисовке используем только целую часть, т.е. старший байт. Единственная 16-битная операция, которая требуется - сложение. И так удачно получилось, что у 8080 есть команда dad. Надо ли писать пример?
ivagor, а как учитывается скорость мячика?
Вот эти 0.75 или 12.345 - это и есть скорость шарика по одной из координат.
- - - Добавлено - - -
Подумал, что возможно ты разделяешь угол и скорость и про скорость спросил в этом смысле. Фактически я про это уже написал раньше, когда перечислял косинусы и синусы, которые <=1, а разные скорости можно получить масштабируя (делением или умножением или даже по таблице) эти коэффициенты.
То есть масштабируя, внося таким образом скорость в коэффициенты.
У моего третьего варианта с псевдокодом по-моему есть пара важных преимуществ по сравнению с более "прямолинейными" подходами: все предварительные расчеты делаются на этапе компиляции, все сложения 8-битные + перенос. Очевидный недостаток - итеративность. Может быть например получится слишком много итераций в среднем, тогда 16-битный FP и сложные предварительные расчеты окажутся более практичными.
svofski, у тебя фактически тоже арифметика с фиксированной точкой, но частный случай, когда коэффициенты <1. И масштабирование ты делаешь сложением вместо умножения или набора таблиц (я бы сам скорее всего сделал набором таблиц, по крайней мере в ротозумере именно так делал). В сухом остатке одно значимое отличие - 8-битное сложение вместо 16-битного. На мой взгляд оно того не стоит - некоторое усложнение и замедление (когда скорость>1) программы в обмен на 8-битные таблицы, но это только мое мнение.
ivagor, да, все верно. 8 бит только дробная часть, а целая часть оседает в координате через флаг переноса. В общем это просто немного другое представление 16-битной координаты и если мы твой и мой способы запишем строго формально, то получим эквивалентные выражения.
Таблицы это не обязательно, просто с ними получается короче путь до первого осязаемого результата.
В твой вариант я бы все же одну хаку добавил. Когда 255 - сразу прибавляем единицу к x_accu (или y_accu). Или так - перед тем, как использовать dda_table_x (или y) для сложения прибавляем туда единицу, в этом случае в dda_table надо хранить коэффициенты уменьшенными на единицу.
Программка заставляет нижний левый угол экрана испускать лучи добра. Процедуру Round закомментил, т.к. ее влияние практически не заметно, но можно раскомментить при желании.
Небольшой оффтоп. Периодически возникает срач на тему мнемоник 8080 и z80, какие лучше. Так вот 8080 имеет инструкцию ana l, а у z80 такой инструкции нет.
- - - Добавлено - - -
Забыл режим при выходе восстановить. Можно использовать Esc60
Denn, в качестве платы за оффтопик сконверти это изображение в битмап 256x256 с цветовой палитрой RGB для "Океана".
tnt23, на Океане всё равно не откроется страница форума, увы.. :-р
Пока обдумывается вопрос с приращениями, сделал болванку для работы со списком выбиваемых бонусов.
http://sensi.org/~tnt23/ok240/bonus.png
Список бонусов - фиксированной длины, каждый элемент списка состоит из типа бонуса (0 - пустой слот), скорости падения, и координат X и Y. При выбивании очередного кирпича в списке ищется пустой слот, в который заносится (в будущем рандомный) тип бонуса, координаты его месторождения, и скорость падения (пока одинаковая для всех, но для живости будет разная).
Выглядит уже довольно потешно: https://www.youtube.com/watch?v=nFD_...ature=youtu.be
tnt23, бонусы - это хорошо, но вот мигания шарика меня смущают. Понимаю, что это эмулятор и в нем к развертке не привязаться, но все же мигание на мой взгляд слишком сильное.
ivagor, меня тоже моргание огорчает, а еще медленность перерисовки всей движухи. Хотя я там и делал привязку к лучу, но на эмуляторе не работает.
То, что я вижу, соответствует большой скорости мячика. В принципе, на таких скоростях уже все равно работает спинной мозг без особого участия сложных органов чувств.
А в реале привязка к лучу есть?
Решил сам попробовать, будет мигать или нет. Это просто тест, все сделано максимально тупо и очень медленно, но не мигает.
Но чудес нет и на скролле без привязки к развертке не все гладко. Надеюсь или b2m добавит в emu бит ГК (или КГ? забыл) или Pyk добавить эмуляцию океана в Emu80.
Чуть изменил проверку луча (JZ вместо JNZ в простом коде ожидания бита в порту 41h), на реале мячик сечется в верхних ~20 растровых строках, на остальном экране не мерцает.
Надо разнести формирование битмапов и их вывод, конечно. Может, тоже сделать очередь обновленных битмапов для вывода и обслуживать ее по одному за обратный ход.
Железобетонный вариант (для реала) - использовать 2 экранные страницы и переключать по биту ГК. Но программу придется сильно переделывать.