В общем два вечера я мучился с asm 8080 и понял, что нифига он не удобнее 6502.
Во-первых, тут нет индексных регистров. И это боль.
Например, чтобы нарисовать такой паттерн из тайлов, в 6502 мне бы понадобилась таблица указателей на строки карты ( в каждом байте которой указан номер тайла) и координаты X и Y. И все бы решилось индексными регистрами и самомодифицирующимся кодом.
Здесь сложнее. Я перепробовал все варианты, и в итоге остановился на, как по мне, самом нормальном.
Данные в карте указываются как адреса начала тайлов (16 бит). Координат нет. Вместо них одна 16-ти битная переменная с адресом начала отрисовки карты.
Кроме того, приходится хранить размеры тайлов и размеры экрана в четырех восьмибитных переменных, чтобы можно было контролировать процесс отрисовки. А отрисовка по X и Y происходит только путем прибавления смещения к нужному адресу (addr+map_width+x).
Осложняется все довольно малым набором удобных команд. Например, нельзя передавать 16-ти битные регистровые пары в другие пары. Есть только одна команда: xchg, которая меняет местами пары DE и HL.
Нельзя сдвигать 16-битные пары, только аккумулятор. И так далее.
Но есть и положительные стороны. Карту можно сделать любого размера, как и определить любое количество тайлов. Лишь бы памяти хватило :)
Во-первых, тут нет индексных регистров. И это боль.
Например, чтобы нарисовать такой паттерн из тайлов, в 6502 мне бы понадобилась таблица указателей на строки карты ( в каждом байте которой указан номер тайла) и координаты X и Y. И все бы решилось индексными регистрами и самомодифицирующимся кодом.
Здесь сложнее. Я перепробовал все варианты, и в итоге остановился на, как по мне, самом нормальном.
Данные в карте указываются как адреса начала тайлов (16 бит). Координат нет. Вместо них одна 16-ти битная переменная с адресом начала отрисовки карты.
Кроме того, приходится хранить размеры тайлов и размеры экрана в четырех восьмибитных переменных, чтобы можно было контролировать процесс отрисовки. А отрисовка по X и Y происходит только путем прибавления смещения к нужному адресу (addr+map_width+x).
Осложняется все довольно малым набором удобных команд. Например, нельзя передавать 16-ти битные регистровые пары в другие пары. Есть только одна команда: xchg, которая меняет местами пары DE и HL.
Нельзя сдвигать 16-битные пары, только аккумулятор. И так далее.
Но есть и положительные стороны. Карту можно сделать любого размера, как и определить любое количество тайлов. Лишь бы памяти хватило :)
🔥5❤🔥1
Пришло время возвращаться к игре! И я наконец-то приступил к написанию кода для сохранения игры.
Это оказалось не очень тривиальной задачей. Для записи на дискету используются команды Kernal (местный BIOS).
Сперва надо определить логический номер файла с помощью команды SETLFS, его название с помощью SETNAM. Открыть файл с помощью OPEN. А потом еще открыть канал для вывода с помощью CHKOUT.
На этом настройка заканчивается. Теперь можно побайтово записывать информацию на дискету с помощью CHROUT.
А потом еще надо закрыть файл и все каналы. Для этого используются CLOSE и CLRCHN
Но вот проблема. Если провернуть такую штуку несколько раз, то на дискету запишутся несколько файлов с одинаковыми названиями. Да, Commodore не умеет по дефолту перезаписывать файлы. Для этого надо поколдовать с его именем. Более того, в этом же имени надо указать формат файла и то, что мы хотим с ним делать.
Изначально имя файла хранилось у меня в памяти вот так:
Теперь так:
Здесь @: - это управляющий символ для перезаписи уже имеющегося файла. Seq - расширение файла. А W означает, что в этот файл мы хотим писать информацию.
Почему же я хочу использовать побайтовую запись, ведь в Kernal есть функция SAVE, которая позволяет записывать в файл сразу массив информации? Ответ банальный. Все данные, которые надо сохранять, находятся в разных участках памяти, а переносить все в одно место я не хочу, потому что тогда запутаюсь в коде.
P.S. А еще я нарисовал аптечку и броню и почти доделал первый уровень=)
Это оказалось не очень тривиальной задачей. Для записи на дискету используются команды Kernal (местный BIOS).
Сперва надо определить логический номер файла с помощью команды SETLFS, его название с помощью SETNAM. Открыть файл с помощью OPEN. А потом еще открыть канал для вывода с помощью CHKOUT.
На этом настройка заканчивается. Теперь можно побайтово записывать информацию на дискету с помощью CHROUT.
open_save_file
lda #2
ldx #8
ldy #2
jsr SETLFS
lda #10
ldx #<save_file
ldy #>save_file
jsr SETNAM
jsr OPEN
ldx #2
jsr CHKOUT
rts
А потом еще надо закрыть файл и все каналы. Для этого используются CLOSE и CLRCHN
close_save_file
lda #2
jsr CLOSE
jsr CLRCHN
rts
Но вот проблема. Если провернуть такую штуку несколько раз, то на дискету запишутся несколько файлов с одинаковыми названиями. Да, Commodore не умеет по дефолту перезаписывать файлы. Для этого надо поколдовать с его именем. Более того, в этом же имени надо указать формат файла и то, что мы хотим с ним делать.
Изначально имя файла хранилось у меня в памяти вот так:
save_file
text "sv"
Теперь так:
save_file
text "@:sv,seq,w"
Здесь @: - это управляющий символ для перезаписи уже имеющегося файла. Seq - расширение файла. А W означает, что в этот файл мы хотим писать информацию.
Почему же я хочу использовать побайтовую запись, ведь в Kernal есть функция SAVE, которая позволяет записывать в файл сразу массив информации? Ответ банальный. Все данные, которые надо сохранять, находятся в разных участках памяти, а переносить все в одно место я не хочу, потому что тогда запутаюсь в коде.
P.S. А еще я нарисовал аптечку и броню и почти доделал первый уровень=)
🔥6👌2❤🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Закончил с сохранением и загрузкой. Загрузка работает примерно как сохранение, только вместо функций CHKOUT и CHROUT используются CHKIN и CHRIN. А еще пришлось новое название файла в памяти сохранять с параметром Write.
Но, в целом, ничего сложного.
Файл сохранения занимает аж 2064 байта. Туда помещается все, что изменяется в ходе игры:
— адрес имени уровня (проверяется в начале загрузки. Если адрес не совпадает с текущим, сперва с дискеты подгружается нужный уровень);
— Параметры игрока, его положение, ключи, оружие и вещи в инвентаре;
— Из состава карты загружается собственно массив карты (чтобы отслеживать изменения блоков и спрайтов), триггеры (они меняют флаги в процессе игры) и мини-карта (надо отслеживать, какие блоки игрок уже видел).
Была мысль выделить в памяти место, куда бы помещались только изменения, но память жалко, а вот место на дискете не очень. К тому же это дополнительные расчеты.
P.S. Видео ускорил в 10 раз, потому что все таки save/load не очень быстрый процесс😅
P.S.S А еще нашел баг четырехмесячной давности, из-за которого оружие в левой части экрана криво разукрашивалось
Но, в целом, ничего сложного.
Файл сохранения занимает аж 2064 байта. Туда помещается все, что изменяется в ходе игры:
— адрес имени уровня (проверяется в начале загрузки. Если адрес не совпадает с текущим, сперва с дискеты подгружается нужный уровень);
— Параметры игрока, его положение, ключи, оружие и вещи в инвентаре;
— Из состава карты загружается собственно массив карты (чтобы отслеживать изменения блоков и спрайтов), триггеры (они меняют флаги в процессе игры) и мини-карта (надо отслеживать, какие блоки игрок уже видел).
Была мысль выделить в памяти место, куда бы помещались только изменения, но память жалко, а вот место на дискете не очень. К тому же это дополнительные расчеты.
P.S. Видео ускорил в 10 раз, потому что все таки save/load не очень быстрый процесс😅
P.S.S А еще нашел баг четырехмесячной давности, из-за которого оружие в левой части экрана криво разукрашивалось
👍3❤🔥1🔥1
Наконец-то начал работать над звуком. И это та еще дичь, тем более, что я совсем ничего не смыслю в музыке.
Оказалось, есть куча трекеров, в которых можно писать звуки и музыку для Commodore 64. Я скачал SID Factory и немного охренел от того, как это сложно.
Но речь не об этом.
Я написал очень кривой семпл для открытия двери, а потом долго разбирался, как засунуть его в игру.
Оказалось, что все не так сложно, хотя информации в сети на самом деле не много.
На выходе из трекера формируется файл с расширением .sid. В этом файле может быть зашито до 256 звуков или треков. Более того, в этом файле есть и код проигрывателя.
Первым делом надо организовать прерывание, которое каждый кадр будет вызывать функцию Play. Я поместил данные начиная с адреса $C000, а эта функция находится по адресу $C006. Вышло что-то такое:
Чтобы начать проигрывать музыку, надо вне прерывания вызвать функцию инициализации. В моем случае она находится в самом начале файла по адресу $C000. Предварительно надо выбрать номер трека. Для этого я написал еще одну функцию.
Но возникла такая проблема. При загрузке данных с дискеты прерывания не работают, поэтому последняя нота (если играет зацикленная музыка), начинает проигрываться бесконечно до завершения загрузки. Поэтому и пришлось добавить в прерывание переменную для проверки на проигрывание музыки и написать функцию, которая при выключении музыки дополнительно выключает контрольные регистры звукового чипа.
А еще оказалось, что в трекере любую музыку или звук можно зациклить, а можно остановить после одного проигрывания. И это поможет отделять одноразовые звуки (типа выстрела) от фоновой музыки.
Как будто бы все это криво, но вроде работает=) Буду развлекаться дальше.
Оказалось, есть куча трекеров, в которых можно писать звуки и музыку для Commodore 64. Я скачал SID Factory и немного охренел от того, как это сложно.
Но речь не об этом.
Я написал очень кривой семпл для открытия двери, а потом долго разбирался, как засунуть его в игру.
Оказалось, что все не так сложно, хотя информации в сети на самом деле не много.
На выходе из трекера формируется файл с расширением .sid. В этом файле может быть зашито до 256 звуков или треков. Более того, в этом файле есть и код проигрывателя.
Первым делом надо организовать прерывание, которое каждый кадр будет вызывать функцию Play. Я поместил данные начиная с адреса $C000, а эта функция находится по адресу $C006. Вышло что-то такое:
irq_play_music
lda sound_playing
beq @exit_irq
jsr play_sound
@exit_irq
lda #210
sta $d012
lda #<irq_set_text_screen
sta $0314
lda #>irq_set_text_screen
sta $0315
asl $d019
jmp exit_from_irq
Чтобы начать проигрывать музыку, надо вне прерывания вызвать функцию инициализации. В моем случае она находится в самом начале файла по адресу $C000. Предварительно надо выбрать номер трека. Для этого я написал еще одну функцию.
start_sound
jsr init_sound
lda #1
sta sound_playing
rts
Но возникла такая проблема. При загрузке данных с дискеты прерывания не работают, поэтому последняя нота (если играет зацикленная музыка), начинает проигрываться бесконечно до завершения загрузки. Поэтому и пришлось добавить в прерывание переменную для проверки на проигрывание музыки и написать функцию, которая при выключении музыки дополнительно выключает контрольные регистры звукового чипа.
stop_sound
lda #0
sta sound_playing
sta gate_v1
sta gate_v2
sta gate_v3
rts
А еще оказалось, что в трекере любую музыку или звук можно зациклить, а можно остановить после одного проигрывания. И это поможет отделять одноразовые звуки (типа выстрела) от фоновой музыки.
Как будто бы все это криво, но вроде работает=) Буду развлекаться дальше.
🔥7🐳2👾2
Получил от двух человек отзыв, что система разных действий в бою вообще не работает. Типа защищаться и уклоняться не имеет смысла. Так и оказалось. У игрока просто нет необходимости делать что-то другое кроме обычной атаки.
Пока что решил эту проблему так.
Если враг решает совершить сильную или аккуратную атаку, то в первом случае его Attack умножается на 4, а во втором — Accuracy умножается на 4.
Если при сильной атаке не поставить блок, то игрок рискует остаться практически без жизней. Но можно попробовать уклониться и вообще не получить урон.
При аккуратной атаке враг практически гарантированно попадет. Тогда тоже можно минимизировать урон либо попытаться уклониться.
Например, когда я тестировал такой подход, то вышла ситуация:
У врага остается 2 HP, и он входит в режим подготовки к сильной атаке. Я понадеялся на случай и атаковал его. Конечно же я промахнулся.
Ему повезло больше, и сильной атакой он снес у меня аж 25 HP.
А вот если бы я поставил блок или уклонился, все могло бы пойти иначе.
В общем, попробую пока так. Если не пойдет, буду думать дальше.
А еще я нарисовал спрайт шотгана=)
Пока что решил эту проблему так.
Если враг решает совершить сильную или аккуратную атаку, то в первом случае его Attack умножается на 4, а во втором — Accuracy умножается на 4.
Если при сильной атаке не поставить блок, то игрок рискует остаться практически без жизней. Но можно попробовать уклониться и вообще не получить урон.
При аккуратной атаке враг практически гарантированно попадет. Тогда тоже можно минимизировать урон либо попытаться уклониться.
Например, когда я тестировал такой подход, то вышла ситуация:
У врага остается 2 HP, и он входит в режим подготовки к сильной атаке. Я понадеялся на случай и атаковал его. Конечно же я промахнулся.
Ему повезло больше, и сильной атакой он снес у меня аж 25 HP.
А вот если бы я поставил блок или уклонился, все могло бы пойти иначе.
В общем, попробую пока так. Если не пойдет, буду думать дальше.
А еще я нарисовал спрайт шотгана=)
🔥2
9 месяцев vs. несколько часов😂
А на самом деле я просто решил отвлечься от ассемблера и покодить на чем-то более человечном, поэтому запустил Godot.
И я не придумал ничего лучше, чем перенести свою игру на современное железо.
Конечно, оригинал в приоритете, а это я просто поразвлекался, но результат мне очень даже нравится.
Поэтому появилась мысль портировать игру, когда закончу разработку.
Процесс это не очень сложный, поэтому почему бы и нет=)
А сделал я это все в 3D. Просто перенес всю карту по блокам и уменьшил разрешение до 320*200 пикселей. Ну а все алгоритмы у меня уже есть, и переписать их на GDScript вообще ничего не стоит.
А на самом деле я просто решил отвлечься от ассемблера и покодить на чем-то более человечном, поэтому запустил Godot.
И я не придумал ничего лучше, чем перенести свою игру на современное железо.
Конечно, оригинал в приоритете, а это я просто поразвлекался, но результат мне очень даже нравится.
Поэтому появилась мысль портировать игру, когда закончу разработку.
Процесс это не очень сложный, поэтому почему бы и нет=)
А сделал я это все в 3D. Просто перенес всю карту по блокам и уменьшил разрешение до 320*200 пикселей. Ну а все алгоритмы у меня уже есть, и переписать их на GDScript вообще ничего не стоит.
🔥6❤🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Наконец-то добавил функцию побега из битвы. Она слишком уж банальная, но мне кажется большего не надо.
Вероятность побега составляет 1/3. По сути, сбежать можно в любой момент, но смысла в этом особо нет, только если это не последний шанс уцелеть в битве.
Если постоянно убегать, не будут копиться опыт и уровни, и на следующих картах игрок будет страдать.
Вероятность побега составляет 1/3. По сути, сбежать можно в любой момент, но смысла в этом особо нет, только если это не последний шанс уцелеть в битве.
Если постоянно убегать, не будут копиться опыт и уровни, и на следующих картах игрок будет страдать.
🔥3
Выставил на всеобщее обозрение демо-версию игры. В ней доступен только первый уровень, и его можно полностью пройти. Для этого потребуется эмулятор, желательно Vice, а инструкция есть по ссылке.
Вообще я оценил текущее состояние игры и, на самом деле, я сделал уже даже больше задуманного минимума. Теперь остается только рисовать, рисовать и ещё рисовать. И карты делать. И звуки с музыкой (спасите).
Если и возвращаться к коду, то для исправления багов и каких-нибудь новых фишек, которые я возможно придумаю:)
https://alexlogachev.itch.io/commodore-64-dungeon-crawler-prototype
Вообще я оценил текущее состояние игры и, на самом деле, я сделал уже даже больше задуманного минимума. Теперь остается только рисовать, рисовать и ещё рисовать. И карты делать. И звуки с музыкой (спасите).
Если и возвращаться к коду, то для исправления багов и каких-нибудь новых фишек, которые я возможно придумаю:)
https://alexlogachev.itch.io/commodore-64-dungeon-crawler-prototype
itch.io
Decade of Ruin (C64) by Alex Logachev
Pseudo 3D raycasting Dungeon Crawer for the famous Commodore 64
👍8❤🔥2
https://dtf.ru/retro/2893361-devyat-mesyacev-assemblera-ili-kak-ya-vse-eshe-pishu-igru-na-commodore-64
Написал небольшую статью про текущий прогресс. Но это не главное.
Я осознал, что надо добавить в игру боссов, от которых нельзя убежать.
Сейчас в игре есть возможность сбежать с поля боя с вероятностью в 1/3. Но если постоянно сбегать, то на следующих уровнях игрок будет страдать.
Но я подумал, а что если игрок будет вообще всегда сбегать, и таким образом пройдет игру. Для такого и надо добавить боссов, чтобы к определенному моменту, если игрок не накопил нужное количество опыта и уровней, то он банально не сможет пройти игру.
Одна проблема - боссов надо добавлять не в данные карты, в основной код. Например, добавить новый триггер, который переносит игрока на арену с единственным боссом. Тогда и спрайты боссов придется зашивать в код.
В общем, пока думаю над этим=)
Написал небольшую статью про текущий прогресс. Но это не главное.
Я осознал, что надо добавить в игру боссов, от которых нельзя убежать.
Сейчас в игре есть возможность сбежать с поля боя с вероятностью в 1/3. Но если постоянно сбегать, то на следующих уровнях игрок будет страдать.
Но я подумал, а что если игрок будет вообще всегда сбегать, и таким образом пройдет игру. Для такого и надо добавить боссов, чтобы к определенному моменту, если игрок не накопил нужное количество опыта и уровней, то он банально не сможет пройти игру.
Одна проблема - боссов надо добавлять не в данные карты, в основной код. Например, добавить новый триггер, который переносит игрока на арену с единственным боссом. Тогда и спрайты боссов придется зашивать в код.
В общем, пока думаю над этим=)
DTF
Девять месяцев ассемблера или как я все еще пишу игру на Commodore 64... — Ретро на DTF
👍7
This media is not supported in your browser
VIEW IN TELEGRAM
К вопросу о боссах.
Был выбор между двумя костылями.
Первый костыль
Зашить код и данные боссов прямиком в код игры. Оказалось, что это не очень выгодно. На дискете данные врагов каждого уровня у меня лежат в формате, который нельзя было бы использовать для боссов. Это потребовало бы копирования некоторых функций с чуть измененным функционалом. А еще 512 байт на изображения и 36 байт на параметры, не считая таблицы указателей.
Я решил не идти этим путем.
Второй костыль
Использовать уже имеющихся на дискете врагов в качестве боссов. У каждого врага есть тип (от 0 до 15). Соответственно, верхние 4 бита не используются.
Я использовал 7-ой бит для обозначения, что враг является боссом.
Также я добавил проверку на этот бит при случайном выборе врага. Если бит включён, то такой враг не встретится игроку при прохождении уровня.
Потом пришлось ввести еще одну переменную, которая отвечает за состояние битвы с боссом. Если переменная больше нуля, то в цикле проверяется, какой из 4-ех врагов является боссом, и битва начинается. То есть на весь уровень может быть только один уникальный босс.
В итоге все это вышло довольно костыльно, но зато заняло всего пару десятков байт.
Два отличия от битвы с обычным врагом:
– В начале битвы пишется сообщение "Boss fight!".
– Кнопка Flee не работает. По сути, всегда пишется сообщение, что побег не удался. Ради этого все и затевалось. Теперь игрок не сможет постоянно бегать от врагов, иначе в какой-то момент он будет страдать на боссе.
В общем, я планирую сделать два-три уровня с боссами. На 8-10 планируемых уровней, думаю, достаточно.
P.S. Сейчас у меня на каждый уровень есть 24 текстовых блока по 96 символов. Итого 2304 байта. Но, как показала практика, уже два уровня я использую не более 10 блоков. То есть, теоретически, можно сократить количество текста на уровень, и запихнуть больше уровней на дискету:)
Был выбор между двумя костылями.
Первый костыль
Зашить код и данные боссов прямиком в код игры. Оказалось, что это не очень выгодно. На дискете данные врагов каждого уровня у меня лежат в формате, который нельзя было бы использовать для боссов. Это потребовало бы копирования некоторых функций с чуть измененным функционалом. А еще 512 байт на изображения и 36 байт на параметры, не считая таблицы указателей.
Я решил не идти этим путем.
Второй костыль
Использовать уже имеющихся на дискете врагов в качестве боссов. У каждого врага есть тип (от 0 до 15). Соответственно, верхние 4 бита не используются.
Я использовал 7-ой бит для обозначения, что враг является боссом.
Также я добавил проверку на этот бит при случайном выборе врага. Если бит включён, то такой враг не встретится игроку при прохождении уровня.
Потом пришлось ввести еще одну переменную, которая отвечает за состояние битвы с боссом. Если переменная больше нуля, то в цикле проверяется, какой из 4-ех врагов является боссом, и битва начинается. То есть на весь уровень может быть только один уникальный босс.
В итоге все это вышло довольно костыльно, но зато заняло всего пару десятков байт.
prepare_fight_state
; прверка на сражение с боссом
lda boss_state
beq @regular_enemy
; ищем босса в списке врагов
ldx #0
@find_boss_loop
lda enemy_types,x
and #%10000000
bne @boss_found
inx
jmp @find_boss_loop
@regular_enemy
; случайный враг из 4
jsr get_random_number
lda rnd_1
ldy #4
jsr mod
tax
; проверяем врага на флаг босса
lda enemy_types,x
and #%10000000
bne @next_try
@boss_found
jsr prepare_enemy_for_fight
inc game_state
@next_try
jmp main_loop
Два отличия от битвы с обычным врагом:
– В начале битвы пишется сообщение "Boss fight!".
– Кнопка Flee не работает. По сути, всегда пишется сообщение, что побег не удался. Ради этого все и затевалось. Теперь игрок не сможет постоянно бегать от врагов, иначе в какой-то момент он будет страдать на боссе.
В общем, я планирую сделать два-три уровня с боссами. На 8-10 планируемых уровней, думаю, достаточно.
P.S. Сейчас у меня на каждый уровень есть 24 текстовых блока по 96 символов. Итого 2304 байта. Но, как показала практика, уже два уровня я использую не более 10 блоков. То есть, теоретически, можно сократить количество текста на уровень, и запихнуть больше уровней на дискету:)
🔥4❤1
Нарисовал текстуры для третьего уровня. Это будет довольно темный уровень к кучей ответвлений. И еще есть желание сделать двери, которые ведут сперва в одно место, а потом в другое. Движок такое позволяет.
А ещё вчера с другом тестировали игру. Посмотрел я на все это и понял, что надо бы чуть упростить. Игра как будто бы получается довольно линейной без возможности нормального исследования уровня, пока игрок не прокачается.
Буду думать, как это проблему решить:)
А ещё вчера с другом тестировали игру. Посмотрел я на все это и понял, что надо бы чуть упростить. Игра как будто бы получается довольно линейной без возможности нормального исследования уровня, пока игрок не прокачается.
Буду думать, как это проблему решить:)
👍2🔥2
Ну, это было не сложно )
Детали крепятся хорошо, ничего не разваливается.
Но...
Буква B на джойстиках перевернутая. Причем на обоих. То есть они обе детали сделали одинаковыми, и обе кверху ногами. Конечно издалека это не заметно, но картину все таки портит :(
Детали крепятся хорошо, ничего не разваливается.
Но...
Буква B на джойстиках перевернутая. Причем на обоих. То есть они обе детали сделали одинаковыми, и обе кверху ногами. Конечно издалека это не заметно, но картину все таки портит :(
❤🔥3😱2