Делаем игру для ТРЦ Redshift
EXAPUNKS — новейшая программная игра от Zachtronics. Читайте журналы, пишите вирусы, взламывайте все подряд. Жизнь не становится лучше. Если вы еще не играли, то прекратите читать это прямо сейчас, идите купить и вернитесь через пару дней, когда освоитесь.
Для тех из вас, кто не заинтересован в игре (что с вами не так?!), но все же хочет дочитать до конца эту статью, вот небольшой контекст. В EXAPUNKS вы программируете EXA. EXA расшифровывается как EXecution Agent — небольшая программа, которая может перемещаться по сети и выходить из нее и выполнять код. Каждый EXA имеет регистры и код, может содержать один файл и может взаимодействовать с другими EXA внутри сети через глобальный регистр. Вы программируете EXA на языке ассемблера с очень ограниченным набором инструкций. EXA можно загружать практически в любую сеть, независимо от того, какое аппаратное обеспечение делает их чрезвычайно универсальными. В этой статье я опишу, как запрограммировать EXA для создания видеоигры для внутриигровой консоли под названием TEC Redshift.
Если вы читаете дальше, есть несколько легких спойлеров.
Хорошо, теперь приступим к взлому. Итак, Гаст дал нам портативную игровую консоль под названием Redshift (очень похожую на Gameboy в этой вселенной) и второй номер своего подпольного компьютерного журнала Trash World News. Мы должны выяснить, как запрограммировать игру для этого. Я хотел сделать что-то простое для своей первой игры Redshift, поэтому решил сделать аркадный шутер в стиле Space Invaders/Galaga. В моей игре используются три EXA: один управляет игроком, один управляет врагами или захватчиками, а третий управляет пулями игрока.
EXA 1 — Игрок
У игрока есть спрайт, он может двигаться в четырех направлениях и стрелять пулями. Чтобы нарисовать спрайт, мы сначала создаем файл, содержащий данные, описывающие, какие пиксели следует включать и выключать. Мы используем инструкцию DATA
, за которой следует ряд целых чисел, разделенных пробелами, для создания файла. Каждое целое число представляет собой трехзначное число: разряд сотен показывает, включен или выключен пиксель, разряд десятков представляет позицию x пикселя, а разряд единиц представляет позицию y пикселя. Спрайт имеет размер 10x10 пикселей.
DATA 000 010 020 130 140 DATA 150 160 070 080 090 DATA 001 011 121 131 141 DATA 151 161 171 081 091 DATA 002 012 122 132 142 DATA 152 162 172 082 092 DATA 003 013 023 033 143 DATA 153 063 073 083 093 DATA 004 014 024 034 144 DATA 154 064 074 084 094 DATA 005 015 025 135 145 DATA 155 165 075 085 095 DATA 006 116 126 136 146 DATA 156 166 176 186 096 DATA 107 117 027 037 147 DATA 157 067 077 187 197 DATA 108 018 028 138 048 DATA 058 168 078 088 198 DATA 109 019 029 139 049 DATA 059 169 079 089 199
Затем мы инициализируем выходной регистр столкновения EXA, чтобы другие EXA могли идентифицировать его при столкновении. Мы также размещаем спрайт в центре нижней части экрана Redshift и ищем начало удерживаемого файла EXA.
MARK INIT COPY 1 CO COPY 55 GX COPY 90 GY SEEK -9999
Затем мы читаем каждое последовательное значение из файла в регистр GP EXA, чтобы нарисовать спрайт. После каждой копии мы проверяем, достигли ли мы конца файла, и если нет, возвращаемся к метке SPRITE.
MARK SPRITE COPY F GP TEST EOF FJMP SPRITE
Основной цикл
Как только спрайт нарисован, мы готовы запустить основной цикл. Сначала мы проверяем пользовательский ввод, читая из аппаратных регистров Redshift. Если обнаружен ввод, мы переходим к соответствующей метке, чтобы обработать либо движение, либо стрельбу. Также проверяем на столкновение с врагом. Я опишу, как мы справляемся со столкновением позже. Для движения мы просто увеличиваем или уменьшаем позицию спрайта, записывая в регистр GX или GY. Вертикальное движение довольно бесполезно для этой конкретной игры, но я все же включил его. Чтобы выстрелить пулей, мы копируем позиции x и y спрайта в глобальный регистр M. Другой EXA прислушивается к значениям этого регистра и обрабатывает запуск пули. Я подробно опишу это в одном из следующих разделов. Мы копируем положение спрайта, чтобы EXA, который обрабатывает пулю, знал, откуда стрелять пулей. После обработки всех возможных пользовательских данных, коллизий и обновления позиции мы используем инструкцию WAIT
, чтобы приостановить выполнение до следующей синхронизации кадров (30 раз в секунду) и, наконец, перейти к метке INPUT, продолжая игровой цикл.
MARK INPUT LINK 800 NOTE SHOOTING SWIZ #PADB 1 X TEST X = 1 TJMP SHOOT NOTE COLLISION TEST CI = 2 TJMP GAMEOVER NOTE HORIZONTAL TEST #PADX = -1 TJMP MOVL TEST #PADX = 1 TJMP MOVR NOTE VERTICAL TEST #PADY = -1 TJMP MOVU TEST #PADY = 1 TJMP MOVD JUMP SYNC MARK MOVL SUBI GX 1 GX JUMP SYNC MARK MOVR ADDI GX 1 GX JUMP SYNC MARK MOVU SUBI GY 1 GY JUMP SYNC MARK MOVD ADDI GY 1 GY JUMP SYNC MARK SHOOT COPY GX M COPY GY M JUMP SYNC MARK SYNC LINK -1 WAIT JUMP INPUT
Столкновение
В случае столкновения с захватчиком мы переходим на метку GAMEOVER. Этот код перемещает спрайт игрока за пределы экрана и пишет текст «GAME OVER». Для этого мы повторяем EXA 8 раз, по одному для каждой буквы в «GAME OVER». Инструкция REPL
создает копию EXA и переходит к указанной метке в репликанте EXA. Каждый EXA отображает свою букву, а затем зацикливается, ничего не делая, пока пользователь не нажмет кнопку X, чтобы перезапустить игру. Исходный EXA переходит к метке RESET, ожидая, когда пользователь нажмет кнопку X, чтобы перезапустить игру. Когда пользователь нажимает X, EXA записывает значение в глобальный регистр M, чтобы уведомить EXA-захватчика о перезапуске игры.
MARK GAMEOVER COPY 35 GY REPL G REPL A REPL M REPL E1 REPL O REPL V REPL E2 REPL R COPY -99 GY JUMP RESET MARK G COPY 10 GX COPY 307 GP JUMP END MARK A COPY 20 GX COPY 301 GP JUMP END MARK M COPY 30 GX COPY 313 GP JUMP END MARK E1 COPY 40 GX COPY 305 GP JUMP END MARK O COPY 60 GX COPY 327 GP JUMP END MARK V COPY 70 GX COPY 322 GP JUMP END MARK E2 COPY 80 GX COPY 305 GP JUMP END MARK R COPY 90 GX COPY 318 GP MARK END TEST #PADB = 0 TJMP END HALT MARK RESET TEST #PADB = 0 FJMP NEWGAME JUMP RESET MARK NEWGAME COPY 1 M LINK -1 JUMP INIT
Вот и все для игрока EXA. Вот полный код, прежде чем мы перейдем к коду для захватчиков.
DATA 000 010 020 130 140 DATA 150 160 070 080 090 DATA 001 011 121 131 141 DATA 151 161 171 081 091 DATA 002 012 122 132 142 DATA 152 162 172 082 092 DATA 003 013 023 033 143 DATA 153 063 073 083 093 DATA 004 014 024 034 144 DATA 154 064 074 084 094 DATA 005 015 025 135 145 DATA 155 165 075 085 095 DATA 006 116 126 136 146 DATA 156 166 176 186 096 DATA 107 117 027 037 147 DATA 157 067 077 187 197 DATA 108 018 028 138 048 DATA 058 168 078 088 198 DATA 109 019 029 139 049 DATA 059 169 079 089 199 MARK INIT COPY 1 CO COPY 55 GX COPY 90 GY SEEK -9999 MARK SPRITE COPY F GP TEST EOF FJMP SPRITE MARK INPUT LINK 800 NOTE SHOOT SWIZ #PADB 1 X TEST X = 1 TJMP SHOOT NOTE COLLISION TEST CI = 2 TJMP GAMEOVER NOTE HORIZONTAL TEST #PADX = -1 TJMP MOVL TEST #PADX = 1 TJMP MOVR NOTE VERTICAL TEST #PADY = -1 TJMP MOVU TEST #PADY = 1 TJMP MOVD JUMP SYNC MARK MOVL SUBI GX 1 GX JUMP SYNC MARK MOVR ADDI GX 1 GX JUMP SYNC MARK MOVU SUBI GY 1 GY JUMP SYNC MARK MOVD ADDI GY 1 GY JUMP SYNC MARK SHOOT COPY GX M COPY GY M JUMP SYNC MARK SYNC LINK -1 WAIT JUMP INPUT MARK GAMEOVER COPY 35 GY REPL G REPL A REPL M REPL E1 REPL O REPL V REPL E2 REPL R COPY -99 GY JUMP RESET MARK G COPY 10 GX COPY 307 GP JUMP END MARK A COPY 20 GX COPY 301 GP JUMP END MARK M COPY 30 GX COPY 313 GP JUMP END MARK E1 COPY 40 GX COPY 305 GP JUMP END MARK O COPY 60 GX COPY 327 GP JUMP END MARK V COPY 70 GX COPY 322 GP JUMP END MARK E2 COPY 80 GX COPY 305 GP JUMP END MARK R COPY 90 GX COPY 318 GP MARK END TEST #PADB = 0 TJMP END HALT MARK RESET TEST #PADB = 0 FJMP NEWGAME JUMP RESET MARK NEWGAME COPY 1 M LINK -1 JUMP INIT
EXA 2 — Захватчики
Захватчики имеют спрайт, похожий на НЛО, и бесконечно появляются в верхней части экрана, ныряя к игроку. Как и игрок EXA, вражеский EXA сначала создает файл, который определяет спрайт, инициализирует регистр вывода коллизий и циклически проходит через файл, записывая значения в регистр GP, чтобы нарисовать спрайт.
DATA 000 010 020 130 140 DATA 150 160 070 080 090 DATA 001 011 121 131 141 DATA 151 161 171 081 091 DATA 002 012 122 132 142 DATA 152 162 172 082 092 DATA 003 013 023 133 143 DATA 153 163 073 083 093 NOTE INIT COPY 2 CO LINK 800 MARK SPRITE COPY F GP TEST EOF FJMP SPRITE
Основной цикл
Как только спрайт нарисован, мы начинаем вторжение. На самом деле есть два цикла, которые контролируют поведение захватчика. Внешний цикл порождает нового захватчика, а внутренний цикл перемещает захватчика и проверяет наличие столкновений. Во внешнем цикле мы используем инструкцию RAND
для генерации случайного значения от 0 до 110 (экран Redshift имеет размер 120x100 пикселей) и записываем это значение в регистр GX, чтобы создать захватчика в случайной позиции x в верхней части экрана. . Во внутреннем цикле мы перемещаем захватчика вниз по экрану, увеличивая значение регистра GY. Затем мы тестируем столкновения с игроком или с пулей. Если происходит столкновение с игроком, мы переходим к метке KILLPLAYER, чтобы обработать этот случай. Если происходит столкновение с пулей, мы просто переходим к метке INVADE, чтобы создать нового захватчика. Наконец, мы проверяем, находится ли захватчик за кадром. Если это так, мы переходим к метке INVADE, чтобы создать нового захватчика, в противном случае мы переходим к метке MOVE, чтобы продолжить внутренний цикл.
MARK INVADE RAND 0 110 GX COPY 0 GY MARK MOVE ADDI GY 1 GY NOTE TEST COLLISION TEST CI = 1 TJMP KILLPLAYER TEST CI = 3 TJMP INVADE NOTE TEST OFFSCREEN TEST GY > 99 TJMP INVADE WAIT JUMP MOVE
Столкновение игроков
В случае столкновения с игроком мы скрываем спрайт захватчика, записывая закадровое значение в регистр GX, а затем слушаем в глобальном регистре M сообщение от игрока EXA, сигнализирующее о перезапуске игры. Мы используем оператор TEST MRD
, чтобы проверить, может ли EXA читать из глобального регистра M без паузы/блокировки. Если в регистре M есть значение, мы отбрасываем это значение, чтобы отправитель мог продолжить выполнение своего кода и перейти к инструкции INVADE, чтобы перезапустить основной цикл.
MARK KILLPLAYER NOOP NOOP NOOP NOOP NOOP COPY -77 GX TEST MRD TJMP RESET JUMP KILLPLAYER MARK RESET VOID M JUMP INVADE
*ПРИМЕЧАНИЕ. Инструкции NOOP
предназначены для работы в условиях гонки. Когда происходит столкновение с игроком и захватчиком, захватчик получает сообщение первым. Инструкции NOOP
приостанавливают захватчика, чтобы у игрока было время наверстать упущенное и получить сообщение о столкновении до того, как захватчик окажется за пределами экрана. Это может быть немного хакерски, но здесь мы EXAPUNKING, хакерство по определению.
EXA 3 — Пули
EXA, управляющий пулями, следует той же схеме инициализации, что и два других EXA. Он рисует квадратный спрайт 2x2 и определяет выходное значение столкновения как 3.
DATA 140 150 141 151 COPY 3 CO MARK SPRITE COPY F GP TEST EOF FJMP SPRITE
Как и у захватчика EXA, у пули EXA также есть два цикла: внешний цикл ожидает сообщения от игрока о выстреле пули, а внутренний цикл перемещает пулю и проверяет на столкновение с захватчиком. Мы также проверяем, пытается ли игрок выстрелить более одной пули за раз, и в этом случае мы просто игнорируем сообщение о выстреле, отбрасывая значение из регистра M и возвращаясь к метке MOVE.
MARK WAIT4SHOOT COPY -99 GX COPY -99 GY COPY M GX COPY M GY MARK MOVE NOTE IGNORE MORE THAN 1 TEST MRD TJMP IGNORE SUBI GY 1 GY NOTE TEST COLLISION TEST CI = 2 TJMP WAIT4SHOOT NOTE TEST OFFSCREEN TEST GY < 1 TJMP WAIT4SHOOT WAIT JUMP MOVE MARK IGNORE VOID M JUMP MOVE
GG
И это, друзья мои, то, как вы правильно EXAPUNK.
Спасибо за чтение! Надеюсь, вам было интересно или полезно. Если вы играете в EXAPUNKS, найдите меня в Steam (bitwitch), и мы сможем сразиться в хакерской битве!