Программирование игр на ассемблере (Часть 1) — Архив WASM.RU

Все статьи

Программирование игр на ассемблере (Часть 1) — Архив WASM.RU

--> Введение

Я уверен, что многие из вас, уже задумывались о создании игр. Многие из вас провели немало часов в интернете, в поисках какой-либо информации о том, как писать игры на чистом ассемблере. Но, к сожалению, такой информации почти нет, а тем более на русском. Я думаю, что этот туториал пополнит ряды русскоязычной документации и надеюсь, что он вам понравится.

--> О чем этот туториал ?

Этот туториал посвящен разработке игры полностью на ассемблере. В нем будет рассмотрен широкий круг вопросов - от структурированного кода до графических аспектов.

--> Для кого это ?

В широком смысле - для тех, кто хотел бы научиться тому, чего возможно не знал прежде... Да и вообще мало кто знает, что полностью виндовское приложение можно написать на чистом ассемблере.

--> Что для этого надо ?

Единственное требование - это способность читать. Однако, если вы желаете писать и транслировать исходный код, то вам понадобится компилятор MASM 6.12+. Вы также можете скачать пакет MASM32, в котором есть все, что вам понадобится.
MASM32v7 можно взять здесь:
http://www.movsd.com/ или
http://wasm.ru/tools/7/masm32v7.zip или
http://spiff.tripnet.se/~iczelion/files/masm32v7.zip.

--> Почему Ассемблер?

Общеизвестно, что любой компилятор генерит код, в котором неизбежно присутствуют баги. Ассемблер - трудный язык как для понимания, так и для написания, что особенно справедливо для DOS'а. С приходом Windows многое меняется.

В чтении труден не только ассемблер, даже в Cи бывают такие дебри, что сам черт ногу сломает. Читабельность исходников зависит от квалификации программиста и умения комментировать код. Вообще-то тут еще надо разобраться, что сложнее: сложить 2 переменные на ассемблере или проследить иерархию какой-нибудь виртуальной функции? Здесь комментарий решает все.
Помните: то, что известно вам, не значит, что это же известно другим.

Далее, проблема в переносимости. Предоставленный язык ассемблера не переносим на другие платформы. Есть, конечно, способ обойти это, который позволяет вам писать код для любой x86 платформы, но его описание не входит в рамки этого туториала. Большинство игрушек пишутся под винды. Это означает, что код привязан к DirectX и к WIN32API, следовательно вы не сможете переносить код, во всяком случае, без некоторой доработки.

Конечно, ассемблер труден в понимании. На этой странице будут показаны его основы. Писать ассемблерный код под Windows, особенно с MASM, очень просто. Это подобно написанию некоторого кода на C. Попытайтесь, и я уверен, что вы не будете разочарованы.

--> Основы Win32 ASM

Если Вы уже знакомы с языком ассемблера под windows, то можете пропустить этот раздел. Он является важным дополнением. Для его обсуждения, предполагается, что вы по крайней мере знакомы с x86 архитектурой.

Первое, что вам нужно понять, это команды (инструкции).

  • MOV
    • Эта инструкция копирует значение из одного места в другое. Вы можете копировать только из регистра в регистр, из памяти в регистр или из регистра в память. Но с помощью этой инструкции, вы не можете копировать напрямую из памяти в память.
      Пример: 
      
      	MOV	EAX, 30
      	MOV	EBX, EAX
      	MOV	my_var1, EAX
      	MOV	DWORD PTR my_var, EAX 
      В первой строке число 30 копируется в регистр EAX. Во второй строке регистр EAX копируется в регистр EBX. В 3-ей строке регистр EAX копируется в память. В 4-ой строке регистр EAX копируется в то место памяти, куда УКАЗЫВАЕТ пойнтер my_var , причем префикс DWORD указывает на то, что пересылаются 4 байта.

  • ADD & SUB
    • Эти две инструкции выполняют сложение и вычитание, соответственно.
      Пример: 
      	ADD	EAX, 30
      	SUB	EBX, EAX 
      Прибавляем 30 к содержимому регистра EAX, а затем вычитаем полученное значение из регистра EBX.

  • MUL & DIV
    • Эти две инструкции выполняют умножение и деление, соответственно.
      Пример: 
      	MOV	EAX, 10
      	MOV	ECX, 30
      	MUL	ECX
      	XOR	EDX, EDX
      	MOV	ECX, 10
      	DIV	ECX 
      Рассмотрим пример: сначала загружаем в EAX=10 и в ECX=30. EAX по умолчанию всегда один из 2-х сомножителей, посему третья команда (в ней вы явно указываете второй сомножитель) перемножает EAX и ECX. Результат от умножения (произведение) будет находится в EAX:EDX. Перед выполнением деления, сначала Вы должны очистить регистр EDX, что и делает команда XOR, выполняя исключающее ИЛИ с самим собой. После деления, целая часть результата будет находится в EAX, а остаток (если есть) в EDX.

Вообще инструкций очень много, но этих пока достаточно для начала. Вероятно мы будем использовать и некоторые другие, но их не сложно понять, если только вы поняли основные. Теперь нам нужно разобраться с правилами вызова функций. Мы будем использовать стандартные правила, такие же как Win32 API. Что это значит, а то, что мы помещаем параметры в стек справа налево и нам также не надо будет заботиться об очистке стека от параметров, все это будет для нас прозрачно. Для вызова функции мы будем использовать псевдо-оператор INVOKE.

Далее, есть одна проблема с вызовом функций Windows. Чтобы использовать invoke, вы должны иметь прототип функции.

Вообще, MASM обеспечивает высокий уровень синтаксиса при написании. В нем есть конструкции, позволяющие писать логику If-Then-Else и циклы For loops аналогично сишным конструкциям.

--> Разработка

А теперь пришло время для проектирования игры. Этим процессом часто пренебрегают и сразу кидаются кодировать то, что пришло в голову. Хотя такой подход иногда и проходит, но чаще всего проваливается.

Как говорится, сначала было слово. Для написания игры нужно представить в голове, как она будет работать. Иногда просто полезно описать СЛОВАМИ то, что вы хотите видеть в своей игре.

Простая часть закончена, теперь нужно продумать все детали. Будут ли в игре элементы соревнования? Нужны ли опции сохранения/загрузки? Сколько уровней? Что должно происходить в конце уровня? Имеется ли вводный экран? И еще много, много вопросов.

После этого нужно будет приниматься за наброски уровней. Какой должен быть экран и интерфейс? Это не обязательно должно быть точно так, но это даст реальную картину, как должна выглядеть финальная версия игры.

--> Код

Всегда имеет смысл насыщать код возможно большим количеством комментариев, чем лично я, например, всегда пренебрегаю.

Далее следует примерная схема организации ассемблерного игрового кода:

;###########################################################################
;###########################################################################
; ABOUT SPACE-TRIS:
;
;   Главная секция - WinMain и т.д.
;
;           - WinMain()
;           - WndProc()
;           - Main_Loop()
;           - Game_Init()
;           - Game_Main()
;           - Game_Shutdown()
;
;###########################################################################
;###########################################################################

;###########################################################################
;###########################################################################
; THE COMPILER OPTIONS
;###########################################################################
;###########################################################################

        .386
        .MODEL flat, stdcall
        OPTION CASEMAP :none   ; case sensitive

;###########################################################################
;###########################################################################
; THE INCLUDES SECTION
;###########################################################################
;###########################################################################

        ;==================================================
        ; хидеры для Windows structs,
        ; unions, constants
        ;==================================================
        INCLUDE Includes\Windows.inc

        ;================================================
        ; хидеры для Window calls
        ;================================================
        INCLUDE \masm32\include\comctl32.inc
        INCLUDE \masm32\include\comdlg32.inc
        INCLUDE \masm32\include\shell32.inc
        INCLUDE \masm32\include\user32.inc
        INCLUDE \masm32\include\kernel32.inc
        INCLUDE \masm32\include\gdi32.inc

        ;====================================
        ; хидеры Direct Draw 
        ;====================================
        INCLUDE Includes\DDraw.inc

        ;===============================================
        ; либы 
        ;================================================
        INCLUDELIB \masm32\lib\comctl32.lib
        INCLUDELIB \masm32\lib\comdlg32.lib
        INCLUDELIB \masm32\lib\shell32.lib
        INCLUDELIB \masm32\lib\gdi32.lib
        INCLUDELIB \masm32\lib\user32.lib
        INCLUDELIB \masm32\lib\kernel32.lib

        ;=================================================
        ; хидеры с прототипами 
        ;=================================================
        INCLUDE Protos.inc

;###########################################################################
;###########################################################################
; LOCAL MACROS
;###########################################################################
;###########################################################################

        szText MACRO Name, Text:VARARG
                LOCAL lbl
                JMP lbl
                Name DB Text,0
                lbl:
        ENDM

        m2m MACRO M1, M2
                PUSH             M2
                POP               M1
        ENDM

        return MACRO arg
                MOV      EAX, arg
                RET
        ENDM

        RGB MACRO red, green, blue
                XOR      EAX,EAX
                MOV      AH,blue
                SHL      EAX,8
                MOV      AH,green
                MOV      AL,red
        ENDM

        hWrite MACRO handle, buffer, size
                MOV      EDI, handle
                ADD      EDI, Dest_index
                MOV      ECX, 0
                MOV      CX, size
                ADD      Dest_index, ECX
                MOV      ESI, buffer
                movsb
        ENDM

        hRead MACRO handle, buffer, size
                MOV      EDI, handle
                ADD      EDI, Spot
                MOV      ECX, 0
                MOV      CX, size
                ADD      Spot, ECX
                MOV      ESI, buffer
                movsb
        ENDM

;#################################################################################
;#################################################################################
;  глобальные переменные 
;#################################################################################
;#################################################################################

;#################################################################################
;#################################################################################
; External variables
;#################################################################################
;#################################################################################

;#################################################################################
;#################################################################################
; BEGIN INITIALIZED DATA
;#################################################################################
;#################################################################################

    .DATA

;#################################################################################
;#################################################################################
; BEGIN CONSTANTS
;#################################################################################
;#################################################################################

;#################################################################################
;#################################################################################
; BEGIN EQUATES
;#################################################################################
;#################################################################################

        ;=================
        ;Utility Equates
        ;=================
FALSE           EQU  0
TRUE            EQU  1


;#################################################################################
;#################################################################################
; BEGIN THE CODE SECTION
;#################################################################################
;#################################################################################

  .CODE

start:

;########################################################################
; WinMain Function
;########################################################################

;########################################################################
; End of WinMain Procedure
;########################################################################

;########################################################################
; Main Window Callback Procedure -- WndProc
;########################################################################

;########################################################################
; End of Main Windows Callback Procedure
;########################################################################

;========================================================================
;========================================================================
; THE GAME PROCEDURES
;========================================================================
;========================================================================

;########################################################################
; Game_Init Procedure
;########################################################################

;########################################################################
; END Game_Init
;########################################################################

;########################################################################
; Game_Main Procedure
;########################################################################

;########################################################################
; END Game_Main
;########################################################################

;########################################################################
; Game_Shutdown Procedure
;########################################################################

;########################################################################
; END Game_Shutdown
;########################################################################

;######################################
; THIS IS THE END OF THE PROGRAM CODE #
;######################################
END start
. . .

Вот мы и подошли к концу первой статьи. А теперь хорошая новость - весь скучный материал в основном позади. Есть еще и плохая новость - вы не видели код, который будет в следующей статье. :)

2002-2013 (c) wasm.ru