Вражеские пули, нацеленные на игрока на C64
Я сканирую Интернет и старые книги C64 на вопрос, не находя ответа, так что в конце концов мне просто пришлось опубликовать его здесь. Мне нравятся старые добрые времена кодирования на C64, и хотя я сейчас не программирую игру на этой платформе, я хотел бы знать, как некоторые аппаратные ограничения были преодолены в то время.
Во всех книгах и руководствах по современному игровому программированию способ найти правильное направление для запуска вражеских пуль по отношению к игроку состоит в использовании векторной математики с плавающими элементами, более или менее похожей на этот псевдокод:
bullet_velocity = (player.position - bullet.position).normalize();
Теперь, принимая во внимание ограничения C64, массовое использование таблиц синуса для скорости, которое я видел в исходных кодах, и, может быть, я отвлекся, но я никогда не видел ни слова о векторной математике при чтении старых книг C64 или программ с комментариями от программистов C64 Мне интересно, как же цель была достигнута в то время.
Пожалуйста, ответьте, у меня есть тысячи подобных сомнений, но, отвечая на этот вопрос, возможно, я смогу найти ответ для остальных!:-)
РЕДАКТИРОВАТЬ: Просто примечание: примеры игр C64 с пулями для игроков являются Silkworm и Cybernoid.
2 ответа
Предположим, что вы довольны достаточно малым количеством направлений вывода, это проще всего сделать через справочную таблицу. Например, для 64 выходных направлений, захватить вектор (x, y)
от источника к месту назначения, и если оба положительны, сдвиньте оба влево, пока один из них не заполнит знаковый бит, затем сформируйте четырехбитный индекс таблицы из двух верхних бит каждого и найдите выходной вектор.
Предполагая, что вы в 160x200, я думаю, вам нужно будет отбросить немного точности, прежде чем войти.
Зеркально отразить, чтобы иметь дело с другими квадрантами. Предполагая 8,8 фиксированной точки для местоположений объекта и скорости пули 1, это таблица поиска в 32 байта.
Итак, наивно, что-то вроде
xPosYPos:
; assuming ($00) has the positive x difference
; and ($01) has the positive y difference
lda $00
ora $01
shiftLoop:
asl $00
asl $01
asl a
bpl shiftLoop
; load A with table index
lda #$00
rol $00
rol a
rol $00
rol a
rol $01
rol a
rol $01
rol a
; look up vector
tax
lda xVec, x
; ... put somewhere ...
lda yVec, x
; ... put somewhere ...
... с более разумным решением, возможно, включающим что-то вроде:
lda $00
ora $01
asl a
bmi shift1
asl a
bmi shift2
... etc ...
shift1:
... etc, but you can shift directly to form
the table index rather than going to all the work
of shifting the vector then piecing together from
the top bits ...
Или вы можете создать 256-байтовую таблицу поиска для поиска обычного адреса на основе x|y
(который всегда будет максимум 127, потому что они оба положительны) и переходят непосредственно к сдвигу, не считая биты.
Грунтовка по объектам и точкам:
Предполагая, что вы находитесь в режиме 160x200, вы можете сохранить каждый компонент местоположения объекта в виде одного байта. Итак, один байт для x, один байт для y. Многие игры вместо этого сохраняют каждое местоположение в виде двух байтов. Итак, всего четыре байта для x и y.
Один из этих байтов такой же, как в однобайтовом формате - это целочисленная позиция. Другой является дробной частью. Таким образом, если у вас есть позиция и скорость (и интеграция Эйлера), вы делаете 16-битное прибавление скорости к позиции. Тогда вы просто используете старший байт для позиции.
Обычно это называется арифметикой с фиксированной точкой. В отличие от числа с плавающей запятой, положение внутри целого числа, где лежит десятичная точка, является фиксированным. В описанной здесь схеме всегда восемь битов.
Так, например, чтобы добавить смещение только с количеством байтов:
clc
lda xPosition
adc xVelocity
sta xPosition
sta SomeHardwareRegisterForSpritePosition
Чтобы добавить смещение со схемой с фиксированной точкой:
clc
lda xFractionalPosition
adc xFractionalVelocity
sta xFractionalPosition
lda xPosition
adc xVelocity
sta xPosition
sta SomeHardwareRegisterForSpritePosition
Преимущество состоит в том, что теперь ваш вектор скорости может составлять 1/25 пикселя в любом направлении. Так, например, вы можете сохранить скорость, которая говорит о том, что каждый кадр вашей пули будет перемещаться на один пиксель влево и на 32/256-ые пикселя вниз. И все, что стоит для перемещения этого маркера с точностью до субпикселя, - это дополнительная пара байтов памяти на вектор и дополнительная пара АЦП.
С вышеупомянутым предложением вы получите вектор от источника к месту назначения, вычитая один байт одного из одного байта другого. Результатом будут два отдельных байта, оба из которых будут дробными частями выходных данных. Так, например, вы можете решить запустить пулю с вектором (87/256, 239/256), то есть под углом 20 градусов.
Вы также можете (ab) использовать алгоритм рисования линий Брезенхэма, чтобы нацеливать ваши пули, причем начальные координаты "линии" - это координаты противника, а конечные - координаты вашего корабля. Однако вместо добавления пикселей в линию вы просто перетягиваете маркер на текущую позицию на каждой итерации.