Программирование игр для Windows. Советы профессионала

       

Пример обработчика прерывания № 2 - Ловим нажатия клавиш!


Как мы узнали в третьей главе, «Основы работы с устройствами ввода», мы можем использовать BIOS для чтения нажатия клавиши путем проверки значения скан-кода. Это отличный способ, и он вполне применим для большинства случаев, но что делать, если вы хотите одновременно нажать две клавиши? (Примером такой ситуации может служить момент, когда вы одновременно нажимаете клавиши «стрелка вверх» или «стрелка вниз» и «стрелка вправо» чтобы изменить направление движения на диагональное). Единственный способ обработать подобную ситуацию в программе требует более тонкой работы с клавиатурой. BIOS по сравнению со стандартными функциями Си просто дает нам еще один уровень управления, но если мы хотим добиться необходимой для профессионально написанных компьютерных игр функциональности, нам следует глубже, глубже и еще глубже разобраться с клавиатурой.

На самом деле клавиатура персонального компьютера представляет собой отдельный микрокомпьютер, который называется 8048. При нажатии или отпускании клавиши клавиатура посылает последовательность сигналов персональному компьютеру на обработку. BIOS сообщает нам о том, какая клавиша нажата, но умалчивает об отпущенных клавишах. Такой половинчатой информации не достаточно для того, чтобы организовать обработку одновременного нажатия нескольких клавиш. Нам нужно знать и когда была нажата, и когда была отпущена та или иная клавиша, чтобы написать алгоритм, способный отслеживать состояния некоторого набора клавиш. При нажатии клавиши мы выставляем соответствующий ей флаг в еостояние «нажато», а при отпускании — сбрасываем значение этого флага. Применяя такой подход, мы регистрируем нажатия различных клавиш и храним эту информацию в нашей структуре данных до тех пор, пока клавиша не будет отпущена.

При нажатии клавиши клавиатурой посылается код нажатия, а при отпускании — соответствующий код отпускания. Эти коды похожи на уже известный вам скан-код. Различие состоит в том, что при отпускании клавиши, каким бы ни было значение кода нажатия, к нему всегда прибавляется 128.




Чтобы наша программа смогла взять контроль за клавиатурой на себя, мы выгрузим обработчик клавиатуры операционной системы DOS и установим наш собственный драйвер. Он будет получать коды клавиш независимо от того, что произошло — нажатие или отпускание, а затем сохранять эту информацию в глобальной переменной, к которой имеет доступ наша Си-программа. При таком подходе функция нашей программы сможет использовать текущее значение этой переменной для выяснения того, какие клавиши в данный момент нажаты, а какие отпущены. Разобравшись что к чему, функция сведет эту информацию в таблицу. Клавиатурное прерывание имеет номер 0х09. Все, что нам требуется сделать, это написать и установить соответствующую процедуру обработки данного прерывания.
Прежде чем мы этим займемся, вспомним адреса клавиатурных портов ввода/вывода и их функции. Собственно клавиатурный порт ввода/вывода находится по адресу 60h, а регистр, управляющий клавиатурой — по адресу 61lh Эти порты находятся на микросхеме PIA (Peripheral Interface Adapter). Кроме всего прочего, нам еще потребуется выполнить определенные процедуры перед вызовом нашего обработчика прерывания и после его завершения. Общий порядок всех действий таков:
1.
Войти в процедуру обслуживания прерывания. Это происходит при каяждом нажатии клавиши.
2.       Прочитать из порта ввода/вывода 60h код клавиши и поместить его в глобальную переменную для последующей обработки программой или обновления содержимого таблицы, в которой хранится информация о нажатых клавишах.
3.       Прочитать содержимое управляющего регистра из порта ввода/вывода 61h и выполнить над ним логическую операцию OR с числом 82h.
4.       Записать полученный результат в порт регистра управления 61h.
5.       Выполнить над содержимым управляющего регистра логическую операцию AND с числом 7Fh. Это сбрасывает состояние клавиатуры, давая ей понять что нажатие на клавишу обработано и мы готовы к считыванию информации о нажатии других клавиш.




6.       Сбросить состояние контроллера прерываний 8259. (Без этого можно и обойтись, однако лучше подстраховаться). Для этого следует записать в порт 20h число 20h. Забавное совпадение, не правда ли?
7.       Выйти из обработчика прерывания.
Листинг 12.7 содержит текст программы обработчика клавиатурного прерывания, позволяющего отслеживать состояние клавиш со стрелками. При работе этой программы вы можете использовать курсорные клавиши или их комбинации для перемещения маленькой точки в любом направлении по экрану.
Листинг 12.7. Киберточка (CYBER.C)._____________________
// ВКЛЮЧАЕМЫЕ ФАЙЛЫ///////////////////////////////////////
#include
<dos.h>
#include <bios.h>
#include <stdio.h>
#include <math.h>
#include <conio.h>
#include <graph.h>
// ОПРЕДЕЛЕНИЯ //////////////////////////////////////////
#define KEYBOARD_INT    0х09
#define KEY_BUFFER      0х60
#define KEY_CONTROL     0х61
#define INT_CONTROL     0х20
// коды нажатия и отпускания для клавиш со стрелками
#define MAKE_RIGHT      77
#define MAKE_LEFT       75
#define MAKE_UP         72                                        
#define MAKE_DOWN       80
#define BREAK__RIGHT     205
#define BREAK_LEFT     203
#define BREAK_UP        200
#define BREAK_DOWN      208
// индексы в таблице состояния клавиш со стрелками
#define INDEX_UP        0
#define INDEX_DOWN      1
#define INDEX_RIGHT     2
#define INDEX_LEFT      3
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ////////////////////////////////
void (_interrupt _far *01d_Isr)(void);
// хранит старый обработчик прерывания
unsigned char far *video_buffer = (char far *)0xA0000000L;
// указатель на видеобуфер
int raw_key;    // необработанные данные от клавиатуры
int key_table[4] = {0,0,0,0};
// таблица состояний клавиш со стрелками
// ФУНКЦИИ //////////////////////////////////////////////
void Plot_Pixel_Fast(int x,int y,unsigned char color)
{
// эта функция рисует точку заданного цвета несколько быстрее,


// чем обычно за счет применения операции сдвига вместо операции
// умножения
// используем известный факт, что 320*у = 256*у + 64*у = у<<8 + у<<6
video_buffer[((y<<8) + (у<<6) ) + х] = color;
} // конец Plot_Pixel_Fast
/////////////////////////////////////////////
void Fun_Back(void)
{
int index;
// несомненно  запоминающийся рисунок фона
_setcolor(1) ;
_rectangle(_GFILLINTERIOR, 0,0,320,200);
_setcolor{15) ;
for (index=0; index<10; index++)
{
_moveto(16+index*32,0);
_lineto(16+index*32,199);
} // конец
цикла
for (index=0; index<10; index++)
{
_moveto(0,10+index*20) ;
_lineto(319,10+index*20);
} // конец цикла
} // конец Fun Back
///////////////////////////////////////
void _interrupt _far New_Key_Int(void)
{
// я в настроении немного попрограммировать на ассемблере!
_asm{
sti                ; разрешаем прерывания
in al,KEY BUFFER   ; получаем нажатую клавишу
xor ah,ah          ; обнуляем старшие 8 бит регистра АХ
mov raw_key, ax  ; сохраняем код клавиши
in al,KEY_CONTROL ; читаем управляющий регистр
or al,82h       ; устанавливаем необходимые биты для сброса FF
out KEY_CONTROL,al ; посылаем новые данные в управляющий регистр
and al,7fh
out KEY_CONTROL,al ; завершаем
сброс
mov al,20h
out INT CONTROL,al; завершаем
прерывание
} // конец ассемблерной вставки
// теперь вернемся К Си, чтобы изменить данные
// в таблице состояния клавиш со стрелками
// обработка нажатой клавиши и изменение таблицы
switch(raw_key)
{
case MAKE_UP:    // нажатие стрелки вверх
{
key_table[INDEX_UP]    = 1;
} break;
case MAKE_DOWN:  // нажатие стрелки вниз
{
key_table[INDEX_DOWN]  = 1;
) break;
case MAKE_RIGHT: // нажатие' стрелки вправо
{
key_table[INDEX_RIGHT] = 1;
} break;
case MAKE_LEFT:  // нажатие стрелки влево
{
key__table[INDEX_LEFT]  = 1;
} break;
case BREAK_UP:    // отпускание стрелки вверх
{
key_table[INDEX_UP]    = 0; 
} break;
case BREAK DOWN:  // отпускание стрелки вниз
{
key_table[INDEX_DOWN]  = 0;


} break;
case BREAK_RIGHT; // отпускание стрелки вправо
{
key_table[INDEX_RIGHT] = 0;
} break;
case BREAK_LEFT:  // отпускание стрелки влево
{
key_table[INDEX_LEFT]  = 0;
} break;
default: break;
} // конец оператора switch
} // конец New_Key_Int
//  ОСНОВНАЯ ПРОГРАММА ////////////////////////////////
void main(void)
{
int dопе=0, x=160, y=100;// флаг выхода и координаты точки
//установка видеорежима 320x200x256
_setvideomode(_MRES256COLOR) ;
Fun_Back(); // оригинальная картинка, не так ли?
printf("\nPress ESC to Exit.");
// установка нового обработчика прерывания
Old_Isr = _dos_getvect(KEYBOARD_INT) ;
_dos_setvect(KEYBOARD_INT, New_Key_Int);
// основной цикл
while(!done)
{
_settextposition(24,2) ;
printf("raw key=%d   ",raw_key);
// смотрим в таблицу и перемещаем маленькую точку
if (key_table[INDEX_RIGHT]) х++;
if (key_table[INDEX_LEFT]) х--;
if (key_table[INDEX_UP]) y--;
if (key_table[INDEX_DOWN]) y++;
// рисуем
киберточку
Plot_Pixel_Fast(x,y,10);
// Наша клавиша завершения. Значение кода нажатия ESC равно 1
if (raw_key==1) done=l;
} // конец while
// восстановление старого обработчика прерывания
_dos_setvect(KEYBOARD_INT, Old_Isr) ;
_setvideomode (_DEFAULTMODE) ;
} // конец
функции main
Уфф... Вот и все, ребята!

Содержание раздела