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

Вид материалаДокументы

Содержание


Таблица 3.3. Функции драйвера мыши. Bios INT 33h Функция 00h
Функция 01h
Функция 03h
Функция 0Bh
Функция 1Ah
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   37

Если вы внимательно изучали таблицу, то должны, были заметить, что клавиши; имеющие двухсимвольную кодировку, обладают, тем не менее, только одним скан-кодом. Это происходит потому, что каждый скан-код может быть дополнен информацией о статусе клавиш. Кроме того, благодаря таблице 3.2, мы теперь сами можем по скан-коду определять код ASCII.

Статус клавиш

Мы должны иметь возможность определять:
  • Была ли нажата какая-нибудь клавиша;
  • Какая была нажата клавиша;
  • Статус клавиши Shift.

Статус клавиш — это просто битовый вектор (последовательность), со­держащий информацию о клавишах Shift, Alt, Ctrl и других. Эта последова­тельность находится в памяти по адресам 417h и 418h. Мы не будем читать эти ячейки напрямую, а воспользуемся BIOS и Си.

Листинг 3.4 содержит код, позволяющий получить статус клавиш.

Листинг 3.4. Получение статуса клавиш.

#define SHIFT_R 0х0001

#define SHIFT_L 0х0002

#define CTRL 0х0004

#define ALT 0х0008

#define SCROLL_LOCK_ON 0х0010

#define NUM_LOCK_ON 0х0020

#define CAPS_LOCK_ON 0х0040

#define INSERT_MODE 0х0080

#define CTRL_L 0х0100

#define ALT_L 0х0200

#define CTRL_R 0х0400

#define ALT_R 0х0800

#define SCROLL_LOCK_DWN 0х1000

#define NUM_LOCK_DWN 0х2000

#define CAPS_LOCK_DWN 0х4000

#define SYS_REQ_DWN 0х8000

unsigned int Get_Control_Keys(unsigned int mask)

{

// функция возвращает статус интересующей нас управляющей клавиши

return(mask &_bios_keybrd(_KEYBRD_SHIFTSTATUS));

} // конец функции

В листинге 3.4 функция Get_Control_Key() использует вызов BIOS из Си для определения статуса клавиш. В строки #define включены описания масок для определения статусных клавиш, благодаря чему вы можете вызывать функцию Get_Control_Key(), не задумываясь о значении битов состояния. Более того, используя маски и логический оператор AND, за один вызов можно получить сразу несколько состояний.

Получение скан-кодов с клавиатуры

Код, представленный в Листинге 3.5 напрямую считывает скан-код и возвращает его в вызывающую программу. Если ввода нет, то функция возвращает 0.

Листинг 3.5. Получение скан-кодов с клавиатуры.

unsigned char Get_Scan_Code(void)

{

// получить скан-код нажатой клавиши

// используется встроенный ассемблер

//клавиша нажата?

_asm

{

mov ah,01h ;функция Olh - проверка на нажатие клавиш

int 16h ;вызвать прерывание

jz empty ;нет нажатых клавиш — выходим

mov ah,00h ;функция 0 - получить скан-код

int 16h ;вызвать прерывание

mov al,ah ;результат поместить в AL

xor ah,ah ;обнуляем АН

jmp done ;в AX возвращается значение "все в порядке"

empty:

xor ax,ax ;очистить AX

done:

} // конец ассемблерного блока

} // конец функции

Мы опять используем встроенный ассемблер. Можно, было, конечно, использовать функцию BIOS через вызов _int86() в Си, но на встроенном ассемблере это выглядит намного круче.

Получение ASCII-кодов с клавиатуры

Давайте теперь посмотрим, как мы можем получить ASCII-символ, введенный с клавиатуры. Это может быть полезно, когда игрок вводит свое имя и нам нужны ASCII-коды. Мы можем получить скан-коды и транслировать их в ASCII, но к чему такие сложности, если сразу можно прочитать ASCII-коды?

Листинг 3.6 показывает функцию, которую мы будем часто использовать, работая с клавиатурой. Эта программа опрашивает клавиши и определяет их нажатие. Если символ введен, то функция возвращает его ASCII-код, в противном случае возвращается 0.

Листинг 3.6. Получение ASCII-кодов с клавиатуры.

unsigned char Get_Ascii_Key(void)

{

//если это нормальный ascii код — возвращаем его, иначе 0

if (_bios_keybrd(_KEYBRD_READY))

return(_bios_keybrd(_KEYBRD_READ));

else

return(0);

}// конец функции


Чтобы использовать функцию из Листинга 3.6, вы должны выполнить примерно следующие действия:


if (( c=Get_Ascii_Key()) > 0)

{

обработать_символ

} иначе

{

символов_нет

}

Теперь все вместе: Демонстрационная программа работы с клавиатурой

Как теперь насчет того, чтобы собрать все написанное про клавиатуру в одну кучу? В Листинге 3.7 представлена демонстрационная программа, которая состоит из вызовов уже написанных в этой главе функций. Она показывает скан-коды нажатых клавиш и состояние клавиш Ctrl и Alt. Если вы нажмете Q, программа завершит свою работу.

Листинг 3.7. Демонстрационная программа работы с клавиатурой (KEY.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ///////////////////////////////////////

#include

#include

#include

#include

#include

#include

// ОПРЕДЕЛЕНИЯ ///////////////////////////////////////////////

// битовые маски для управляющих клавиш

#define SHIFT_R 0х0001

#define SHIFT_L 0х0002

#define CTRL 0х0004

#define ALT 0х0008

#define SCROLL_LOCK_ON 0х0010

#define NUM_LOCK_ON 0х0020

#define CAPS_LOCK_ON 0х0040

#define INSERT_MODE 0х0080

#define CTRL_L 0х0100

#define ALT_L 0х0200

#define CTRL_R 0х0400

#define ALT_R 0х0800

#define SCROLL_LOCK_DWN 0х1000

#define NUM_LOCK_DWN 0х2000

#define CAPS_LOCK_DWN 0х4000

#define SYS_REQ_DWN 0х8000

// Значения скан-кодов. Внимание: каждая клавиша продуцирует только

// один скан-код, поэтому определения даны только для символов

// нижнего регистра. Например, одна и та же клавиша соответствует

// символу "1" (нижний регистр) и символу "!" (верхний регистр).

// Однако пользоваться надо все равно определением SCAN_1

#define SCAN_ESC 1

#define SCAN_1 2

#define SCAN_2 3

#define SCAN_3 4

#define SCAN_4 5

#define SCAN_5 6

#define SCAN_6 7

#define SCAN_7 8

#define SCAN_8 9

#define SCAN_9 10

#define SCAN_0 11

#define SCAN_MINUS 12

#define SCAN_EQUALS 13

#define SCAN_BKSP 14

#define SCAN_TAB 15

#define SCAN_Q 16

#define SCAN_W 17

#define SCAN_E 18

#define SCAN_R 19

#define SCAN_T 20

#define SCAN_Y 21

#define SCAN_U 22

#define SCAN_I 23

#define SCAN_0 24

#define SCAN_P 25

#define SCAN_LFT_BRACKET 26

#define SCAN_RGT_BRACKET 27

#define SCAN_ENTER 28

#define SCAN_CTRL 29

#define SCAN_A 30

#define SCAN_S 31

#define SCAN_D 32

#define SCAN_F 33

#define SCAN_G 34

#define SCAN_H 35

#define SCAN_J 36

#define SCAN_K 37

#defane SCAN_L 38

#define SCAN_SEMI 39

#define SCAN_APOS 40

#define SCANJTILDE 41

#define SCAN_LEFT_SHIFT 42

#define SCAN_BACK_SLASH 43

#define SCAN_Z 44

#define SCAN_X 45

#define SCAN_C 46

#define SCAN_V 47

#define SCAN_B 48

#define SCAN_N 49

#define SCAN_M 50

#define SCAN_COMMA 51

#define SCAN_PERIOD 52

#define SCAN_FOWARD_SLASH 53

#define SCAN_RIGHT_SHIFT 54

#define SCAN_PRT_SCRN 55

#define SCAN_ALT 56

#define SCAN_SPACE 57

#define SCAN_CAPS_LOCK 58

#define SCAN_F1 59

#define SCAN_F2 60

#define SCAN_F3 61

#define SCAN_F4 62

#define SCAN_F5 63

#define SCAN_F6 64

#define SCAN_F7 65

#define SCAN_F8 66

#define SCAN_F9 67

#define SCAN_F10 68

#define SCAN_Fll 133

#define SCAN_Fl2 134

#define SCAN_NUM_LOCK 69

#define SCAN_SCROLL_LOCK 70

#define SCAN_HOME 71

#define SCAN_UP 72

#define SCAN_PGUP 73

#define SCAN_NUM_MINUS 74

#define SCAN_LEFT 75

#define SCAN_CENTER 76

#define SCAN_RIGHT 77

#define SCAN_NUM_PLUS 78

#define SCAN_END 79

#define SCAN_DOWN 80

#define SCAN_PGDWN 81

#define SCAN_INS 82

#define SCAN_DEL 83

// ФУНКЦИИ /////////////////////////////////////////////

unsigned char Get_Ascii_Key(void)

{

// Если в буфере клавиатуры есть символ, функция возвращает его

// ASCII-код. Если символа нет, возвращается 0.

if (_bios_keybrd(_KEYBRD_READY)) return(_bios_keybrd(_KEYBRD_READ)) ;

else return(0);

} // конец функции

////////////////////////////////////////////////////////

unsigned int Get_Control_Keys(unsigned int mask)

(

// функция возвращает состояние любой управляющей клавиши

return(mask & _bios_keybrd(_KEYBRD_SHIFTSTATUS));

}//конец функции ////////////////////////////////////////////////////////

unsigned char Get_Scan_Code(void) (

// функция возвращает скан-код нажатой клавиши // используется встроенный ассемблер

_asm{

mov ah,01h ;функция 01h - проверка нажатия клавиши

int 16h ;вызов прерывания

jz empty ;нет символа - выход

mov ah,00h ;функция 0 - получение скан-кода

int 16h ;вызов прерывания

mov al,ah ;перемещаем результат из АН в AL

xor ah,ah ;обнуляем АН

jmp done ;результат возвращается в АХ

empty:

xor ax,ax ;обнуляем AX

done:

} //конец ассемблерного блока

} // конец функции

// ОСНОВНАЯ ПРОГРАММА ///////////////////////////////////////

void main(void)

{

unsigned char key;

int done=0;

unsigned int control;

_clearscreen(_GCLEARSCREEN);

while(!done)

{

_settextposition(2,0);

if ( (key = Get_Scan_Code()) ) printf("%c %d ",key,key);

// проверка на нажатие Ctrl и Alt

if (Get_Control_Keys(CTRL))

printf("\ncontrol key pressed");

if (Get_Cbntrol_Keys(ALT))

printf("\nalt key pressed ");

if (key==16) done=1); // 16 — это скан-код клавиши Q

}// конец цикла while

} // конец функции main

Мышь

Изобретение мыши, безусловно, было событием в компьютерной истории. Появившись в результате исследований в центре Xerox PARK в Калифорнии, она завоевала сердца миллионов пользователей. Мышь позволила наглядно работать с экраном и легко общаться с интересующими объектами. Обычно мышь подсоединяется к последовательному порту компьютера или DIN коннектору на лаптопе. Существующая bus mice требует специальной платы, поэтому не получила широкого распространения (это понятно — мало кто хочет просто так вскрывать свой компьютер для установки одной платы).

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

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

Мы будем использовать только минимум функций для определения позиции мыши и статуса кнопок. В таблице 3.3 перечислены эти функции.

Замечание

Микки (mickey) — это самое маленькое расстояние, которое отслеживается мышью. Оно примерно равно 1/200 дюйма.

^ Таблица 3.3. Функции драйвера мыши.

Bios INT 33h

Функция 00h - инициализировать драйвер мыши

Вход: AX: 0000h

Выход: AX: FFFFh в случае успеха,

0000h при неудаче

ВХ - количество кнопок мыши

^ Функция 01h - включить курсор мыши

Вход: AX: 0001h

Выход: Ничего

Функция 02h - выключить курсор мыши

Вход: AX: 0002h

Выход: Ничего

^ Функция 03h - возвратить позицию курсора и статус клавиш

Вход: АХ:000Зh

Выход: ВХ - статус кнопок

Бит 0 - левая кнопка: 1 - нажата, 0 - не нажата

Бит 1 - правая кнопка: 1 - нажата, 0 - не нажата

Бит 2 - центральная кнопка: 1 - нажата, 0 - не нажата

СХ - Х-координата курсора

DX - Y-координата курсора

^ Функция 0Bh - возвратить относительную позицию мыши

Вход: AX: 000Bh

Выход: СХ - относительное горизонтальное движение в mickey

DX - относительное вертикальное движение в mickey

^ Функция 1Ah - установить чувствительность

Вход: AX: 00lAh

Выход: ВХ - чувствительность по оси Х (0-100)

СХ - чувствительность по оси Y (0-100)

DX - значение скорости, при которой чувствительность возрастает в 2 раза (0-100)

Как видите, функции драйвера вызываются через прерывание 33h. Мы записываем параметр в регистр АХ .и получаем результат в регистрах АХ, ВХ, СХ и DX. Я написал простую функцию для работы с мышью, она называется Squeeze_Mouse(). Эта функция может выполнять много действий — все зависит от передаваемых параметров. Прототип функции:


int Squeeze_Mouse(int command, int *x, int *y, int *buttons) ;


Кроме этого, я сделал несколько описаний, чтобы упростить работу с ней:

#define MOUSE_INT 0х33 // номер прерывания

#define MOUSE_RESET 0х00 // сброс мыши

#define MOUSE_SHOW 0х01 // показать мышь

#define MOUSE_HIDE 0х02 // погасить мышь

#define MOUSE BUTT_POS 0х03 // возвратить координаты

//и количество кнопок

#define MOUSE_SET_SENSITIVITY 0x1A // установить

//чувствительность

//в пределах 0-100

#define MOUSE_MOTION_REL 0x0B // установить

// относительную

// чувствительность

Таким образом, если мы хотим получить координаты мыши, то должны написать следующее:


Squeeze Mouse(MOUSE_BUTT_POS, &mouse_x, &mouse_y, &mouse_buttons);


где mouse_x, mouse_y и mouse_buttons - локальные переменные для сохранения результатов.


Теперь обратим внимание на два способа, используемые для передачи координат мыши:
  • Драйвер мыши может возвращать абсолютные координаты. В этом случае значения Х и Y являются координатами мыши на экране. К примеру, если мышь находится в левом верхнем углу экрана, функция возвращает (0,0);
  • Драйвер мыши может возвращать относительные координаты. При этом возвращается разница координат от предыдущей посылки. Например, если мышь подвинулась на 20 микки по оси Х и на 10 по оси Y, то эти значения и будут возвращены. Для чтения в относительном режиме используйте константу MOUSE_MOTION_REL.

Еще несколько слов о мыши. Вы можете менять ее чувствительность к передвижению, используя константу MOUSE_SET_SENSITIVITY. Для этого подберите для переменных Х и Y значение от 1 до 100 и вызовите Squeeze_Mouse. Чувствительность мыши определяется как отношение пиксельного перемещения курсора мыши к одному микки.

Листинг 3.8 содержит демонстрационную программу, которая показывает использование мыши. Эта программа позволяет рисовать на экране, нажимая на левую кнопку мыши и менять цвет, используя правую кнопку.

Листинг 3.8. Работа с мышью (MOUSE.C).

// ВКЛЮЧАЕМЫЕ ФАЙЛЫ ///////////////////////////////////////

#include

#include

#include

#include

#include

#include

// ОПРЕДЕЛЕНИЯ ///////////////////////////////////////////

// вызовы функций мыши

#define MOUSE_INT 0х33 // Номер прерывания мыши

#define MOUSE_RESET 0х00 // Инициализация драйвера

#define MOUSE_SHOW 0х01 // Показать курсор мыши

#define MOUSE_HIDE 0х02 // Спрятать курсор мыши

#define MOUSE_BUTT_POS 0х03 // Получение полного статуса

#define MOUSE_SET_SENSITIVITY Ox1A // Установка чувствительности

#define MOUSE_MOTION_REL ОхОВ // Получить значение счетчика

// микки

#define MOUSE_LEFT_BUTTON 0х01 // левая кнопка

#define MOUSE_RIGHT_BUTTON 0х02 // правая кнопка

#define MOUSE_CENTER_BUTTON 0х04 // средняя кнопка

// ФУНКЦИИ ////////////////////////////////////////////////

int Squeeze_Mouse(int command, int *x, int *y,int *buttons)

{

// Мы будем использовать _int86 вместо встроенного ассемблера

// Почему? И сам не знаю

union _REGS inregs, outregs;

switch(command) {

case MOUSE_RESET:

{

inregs.x.ax = 0х00; // подфункция 0 – инициализация

_int86(MOUSE_INT, &inregs, &outregs};

*buttons = outregs.x.bx; // возвратить количество кнопок

return(outregs.x.ax); // возвратить общий результат

} break;

case MOUSE_SHOW:

{

// эта функция инкрементирует счетчик драйвера. Когда значение

// счетчика становится больше или равно 0, курсор появляется

// на экране

inregs.х.ах = 0х01; // подфункция 1 - показать курсор

_int86(MOUSE_INT, &inregs, &outregs);

return (1);

} break;

case MOUSE_HIDE:

{

// эта функция декрементирует счетчик драйвера; когда его

// значение становится меньше 0, курсор исчезает с экрана

inregs.х.ах = 0х02; // подфункция 2 - спрятать курсор

_int86(MOUSE_INT, &inregs, &outregs);

return(1);

} break;

case MOUSE_BUTT_POS:

{

// эта функция позволяет получить полный статус состояния мыши,

// включая абсолютную позицию курсора в координатах (х,у) и

// состояние кнопок

inregs.х.ах = 0х03; // подфункция 3 - получить статус мыши

_int86(MOUSE_INT, &inregs, &outregs);

// извлечь информацию и вернуть ее через указатели

*х = outregs.х.сх;

*у = outregs.x.dx;

*buttons = outregs.x.bx;

return(1);

} break;

case MOUSE_MOTION_REL:

{

// эта функция позволяет получить относительное изменение

// координат мыши с момента последнего вызова

inregs.х.ах = 0х03; // подфункция 1 – получить

// относительную позицию

_int86(MOUSE_INT, &inregs, &outregs);

// результат при помощи указателей помещается в переменные х и у

*х = outregs.x.cx;

*у = outregs.x.dx;

return(1);

} break;

case MOUSE_SET_SENSITIVITY:

{

// эта функция устанавливает чувствительность мыши. Перед

// вызовом необходимо установить переменные х и у в значения

// из диапазона 1-100. Переменная "buttons" используется для

// установки значения порога удвоения скорости

// (из диапазона 1-100)

inregs.x.bx = *х;

inregs.x.cx = *у;

inregs.x.dx = *buttons;

inregs.x.ax = 0x1A; // подфункция 26 - установка

// чувствительности

_int86(MOUSE INT, &inregs, &outregs);

return(1);

} break;

default:break;

} // конец оператора switch

} // конец функции

// ОСНОВНАЯ ПРОГРАММА /////////////////////////////////////////////////////////////////////////////

void main(void)

{

int x,y,buttons,num_buttons;

int color=l;

_setvideomode(_VRES16COLOR}; // 640х480, 16 цветов

// инициализация драйвера мыши

Squeeze_Mouse(MOUSE_RESET,NULL,NULL,&num_buttons);

//показать курсор

Squeeze_Mouse(MOUSE_SHOW,NULL,NULL,NULL);

while(!kbhit())

{

_settextposition(2,0);

Squeeze_Mouse(MOUSE_BUTT_POS,&x,&у,&buttons);

printf("mouse x=%d y=%d buttons=%d ",х,у,buttons) ;

// рисование

if (buttons==1)

{

_setcolor(color) ;

_setpixel(x-1,y-2);

_setpixel(х,y-2) ;

_setpixel(x-1,y-1);

_setpixel(х,y-1);

} // конец обработки нажатия левой кнопки

// выбор цвета

if (buttons==2)

{

if (++color>15) color=0;

// ждем отпускания правой кнопки

while(buttons==2)

{

Squeeze_Mouse(MOUSE_BUTT_POS,&x,&y,&buttons) ;

} // конец ожидания

} // конец графической работы

} // конец цикла while

// назад в текстовый режим

_setvideomode(_DEFAULTMODE);

} // конец функции main


ИТОГ

В этой главе мы изучили разные устройства ввода, которые могут использоваться в видеоиграх: клавиатура, джойстик, мышь. Более того, мы даже написали функции для работы с этими устройствами. Еще мы выяснили, что не стоит использовать BIOS для работы с устройствами ввода, за исключением клавиа­туры.