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

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

Содержание


Таблица 14.1. Базовые адреса управляющего регистра UART. Последовательный порт Базовый адрес порта
Регистр 7: Регистр временного заполнения (Scratch-Pad Register)
Регистр 9: Регистр более значимого байта ключа делителя скорости пepeдaчи,(Baud-Rate Divisor Latch Most-Significant Byte-DLM)
Таблица 14.2. Распайка для последовательных портов ПК. Провод Функция Обозначение
Таблица 14.3. Регистр маски прерывания (IMR) PIC'a.
Таблица 14.4. Векторы прерывания последовательного порта.
Подобный материал:
1   ...   25   26   27   28   29   30   31   32   ...   37

Связь

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

В этой главе будут изучены следующие темы:
  • Средства связи видеоигр;
  • Последовательный интерфейс ПК;
  • Функции поддержки последовательного порта ROM BIOS;
  • Соединение через нуль-модем;
  • Создание коммуникационных библиотек;
  • Стратегия коммуникационных видеоигр;
  • Синхронизация вектора состояния;
  • Синхронизация состояния порта ввода/вывода;
  • Синхронизация по времени;
  • Модем;
  • Написание игры Net-Tank (Сетевой танк) для двух игроков в замкнутом пространстве.

В этой главе мы сконцентрируем внимание на проблемах дизайна игр для нескольких игроков, по возможности не вдаваясь в управление коммуникациями как таковыми. К сожалению, у нас нет времени на изучение методов осуществления связи через модем в полном объеме. Однако мы научимся управлять связью через последовательный порт с помощью нуль-модема. С этими знаниями, потратив дополнительно несколько ночей, вы научитесь связываться через модем. У вас будут все элементы, достаточные для того, чтобы получить нечто работающее. Мы хотим научиться писать видеоигры для двух и более игроков сидящих за своими компьютерами и играющими одновременно без потери синхронизации и прочих проблем. Поэтому нас больше будет интересовать тактика коммуникации в архитектуре видеоигр, а не физическая коммуникация сама по себе.

Средства связи видеоигр

К этому моменту вы уже должны быть готовы писать видеоигры. Вы от начала и до конца научились тому, что для этого необходимо знать. Однако мы до сих пор упускали один важный момент: как написать игру, чтобы в нее могли играть сразу несколько человек? Это нам покуда совершенно неизвестно.

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

Поскольку нас первоначально интересует написание видеоигр для потребителей, мы будем концентрировать наши усилия на использовании серийных портов как средства коммуникации (а не сетевые коммуникации типа IPX/SPX или NETBIOS). Я выбираю параллельный порт в отличие от использования нескольких Ethernet плат по следующим причинам:
  • Во-первых, у каждого компьютера есть свой параллельный порт;
  • Во-вторых, у многих людей есть свои модемы, с помощью которых они обладают основным средством для игры вдвоем.

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

Последовательный интерфейс ПК

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

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

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

Единственное, что нам нужно знать, это как открыть последовательный порт, записать в него символы и прочитать ответную информацию. Перейдем ближе к делу и посмотрим, что ПК могут для этого предложить (между прочим, значение некоторых терминов объяснялось чуть ранее).

Универсальный асинхронный приемопередатчик

ПК оборудованы универсальным асинхронным приемопередатчиком (UART) - чипом, который принимает и передает последовательные данные. Существуют Два наиболее популярных UART для ПК:
  • Модель 8250;
  • Модель 16550.

Можете считать, что они полностью совместимы друг с другом и нам не нужно выяснять, какой из них используется. Единственным их важным отличием является только то, что модель 16550 имеет внутренний FIFO (First In, First Out - "первый вошел - первый вышел") буфер, который располагает входящие - данные так, что они не могут потеряться вследствие задержки обработки. Теперь взглянем на каждый из регистров UART и на то, как получить к ним доступ. После того как мы обязались написать полную библиотеку для связи, необходимо уяснить, как открыть последовательный порт, а также как осуществлять чтение и запись. Написав однажды,соответствующие функции, мы можем сконцентрироваться на целях игры.

Установки и статус UART

Установки и статус UART управляются через набор внутренних регистров доступных как порты ввода/вывода, адреса которых начинаются от некоторого базового адреса. Базовый адрес определяется номером последовательного порта через который вы хотите связаться. Рассмотрим таблицу 14.1, в которой указаны базовые адреса управляющих регистров UART.

^ Таблица 14.1. Базовые адреса управляющего регистра UART.

Последовательный порт Базовый адрес порта

СОМ1 3F8h

COM2 2F8h

COM3 3E8h

COM4 2E8h

Как видите, если мы хотим играть через последовательный порт СОМ1, нам необходимо использовать порт 3F8h в качестве базового адреса ввода/вывода. Каждый порт имеет девять регистров, в которые можно писать или из которых можно считывать информацию в зависимости от их типа. Следовательно, для доступа к регистру 1 порта СОМ1 необходимо использовать адрес ввода/вывода 3F8h+1, то есть 3F9n.

Теперь мы знаем, где расположены регистры. А что каждый из них делает?



Это регистр поддержки передачи, куда помещается следующий символ для передачи. Если это одиночный байт и вы используете схему передачи, имеющую менее 8 бит, то данные игнорируются, и не передаются вообще.



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



Этот регистр используется, чтобы задействовать тот тип прерываний, который может сгенерировать UART. Он доступен как для чтения, так и для записи. После установки серийного порта было бы неудобно постоянно опрашивать его, поэтому для получения входных данных лучше написать процедуру обслуживания прерывания (ISR), которая будет вызываться каждый раз при получении символа. Этот регистр позволяет нам сообщить UART'y, какие именно события Должны вызывать прерывание. Для нас представляет интерес только прерывание RxRDY, которое генерируется при получении символа UART'ом.



Регистр идентификации прерывания используется для определения причины по которой UART выдал прерывание. Это может показаться избыточным однако если вы предварительно установили UART для получения прерывания по двум или более различным событиям, то, поскольку этот регистр определяет тип произошедшего прерывания, он поможет вам выяснить, что именно произошло.



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



Данный регистр оказывает влияние на некоторые выходные данные линий управления модема. Нас больше всего в нем интересует бит GP02. Когда он установлен, появляется возможность прихода прерываний.



Регистр состояния линии используется, чтобы обрисовать состояние коммуникационного порта. В этом регистре нас интересует пятый бит (ТВЕ), который используется для определения возможности продолжения передачи символов (THR).



Регистр состояния модема используется, чтобы показать состояние линий управления модемом. Для наших целей этот регистр едва ли понадобится. Однако вы могли бы найти применение индикатору звонка (RI). Вы можете написать программу, которая будет перехватывать звонок и когда вызывается ваш номер, например, сообщать об этом соответствующей надписью на экране и звуком.

^ Регистр 7: Регистр временного заполнения (Scratch-Pad Register)

Не используется

Регистр 8: Менее значимый ключ делителя скорости передачи (Baud-Rate Divisor Latch Least-Significant Byte - DLL)

Предназначен для хранения младшего байта делителя, используемого при вычислении действительной скорости передачи через порт. Окончательная скорость вычисляется так: берут младший и старший банты и используют их как делитель числа 115200. В результате получится скорость передачи. Этот регистр доступен через регистр 0 при установленном 7-м бите (DLAB) регистра З (LCR).

^ Регистр 9: Регистр более значимого байта ключа делителя скорости пepeдaчи,(Baud-Rate Divisor Latch Most-Significant Byte-DLM)

Этот регистр используется для поддержки старшего байта делителя, использумого для вычисления действительной скорости передачи через последовательный порт. Окончательная скорость передачи вычисляется следующим образом: берут старший и младший байты и используют их как делитель, на который нужно разделить число 115200. Это дает скорость передачи. Данный регистр доступен через регистр 1 при установленном 7-м бите (DLAB) регистра 3 (LCR).

Аппаратное обеспечение UART

Разобравшись с программным обеспечением UART, давайте взглянем на его аппаратную поддержку. Нас интересуют только две вещи: куда воткнуть кабель и как сделать разъем.

ПК могут иметь два типа последовательных портов:
  • 9-штырьковый (разъем типа DB-9);
  • 25-штырьковый (разъем типа DB-25).

В таблице 14.2 приведена их распайка.

^ Таблица 14.2. Распайка для последовательных портов ПК.

Провод Функция Обозначение

9-штырьковый разъем

1 Сигнал наличия несущей CD

2 Прием данных RXD

3 Передача данных TXD

4 Сигнал готовности ввода данных BTR

5 Земля GND

6 Сигнал готовности набора данных DSR

7 Запрос на пересылку RTS

8 Сигнал очистки для пересылки CTS

9 Индикатор звонка RI

25-Штырьковый разъем

2 Передача данных TXD

3 Прием данных RXD

4 Запрос на пересылку RTS

5 Сигнал очистки для пересылки CTS

6 Сигнал готовности набора данных DSR

7 Земля GND

8 Сигнал наличия несущей CD

20 Сигнал готовности ввода данных DTR

22 Индикатор звонка RI

Операции с ROM BIOS

Прежде чем мы начнем писать собственную коммуникационную программу, давайте посмотрим, чем нам может помочь ROM BIOS? Она поддерживает коммуникации через последовательный порт, хотя и весьма ограниченно. Эти функции доступны через прерывание 14h. Существуют функции для открытия последовательного порта, его конфигурирования, чтения и записи символа. Однако существует одна маленькая проблема: эти функции не работают (трудно в это поверить, но они действительно не работают). На самом деле они работают, правда, при особых условиях, которые нам вряд ли удастся создать. К сожалению, отсутствует крайне необходимая нам поддержка ввода/вывода, управляемого прерыванием. В видеоиграх мы должны иметь систему управления событиями, основанную на прерываниях. Из-за этого, а также из-за того, что функции ROM BIOS работают очень медленно, мы можем вообще не рассматривать их как реальное средство передачи битов по проводам.

Теперь, когда я вам сказал об этом, давайте посмотрим, каким Образом все-таки можно соединить два компьютера и создать маленькую сетевую игру.

Соединение через нуль-модем

Как я уже сказал в самом начале, у нас не хватит времени вникать во все тонкости использования модема. Существует слишком много тем, которых мы слегка коснулись, и все они имеет отношение к дизайну видеоигр. Что я намерен сделать вместо этого, так это создать коммуникационную систему, использующую соединение типа нуль-модем, в котором модем, как таковой, отсутствует. Нуль-модем — простое соединение, которое связывает два компьютера через последовательные порты. Рисунок 14,1 демонстрирует такое соединение.



Чтобы изготовить такое соединение, мы должны взять нуль-модемный кабель с надлежащими разъемами. Это может оказаться не слишком просто, но если вы будете осторожны, то заработает с первой попытки. Мы будем использовать только три типа разъема:
  • Линию передачи данных;
  • Линию приема данных;
  • Землю.

На рисунке 14.2 показано, как сделать нуль-модемный кабель для разных типов разъемов.

Если вы не хотите изготавливать нуль-модемный кабель, вы можете заплатить за него в любом из компьютерных магазинов. (Пожалуйста, не платите больше 15$. Я не могу спокойно смотреть, как люди платят за кусок провода и пластик даже 1.50$). Сейчас официальный нуль-модемный кабель использует более трех шин, которые я перечислил. Он задействует все шины, имитируя тем самым модемное соединение. Пока у нас есть TXD, RXD и земля, мы тоже при деле. (Дополнительные линии используются для аппаратного обеспечения «рукопожатия», но мы не будем ими пользоваться.)

Отлично, теперь у нас есть нуль-модемный кабель, и мы знаем, какие Регистры что делают в UART'e. Я думаю, что теперь самое время начать писать коммуникационное программное обеспечение.



Построение коммуникационной библиотеки

Нам не надо иметь слишком много функций в нашей библиотеке. Фактически, нам достаточно шести функций:
  • Для инициализации последовательного порта;
  • Для установки процедуры обработки прерывания;
  • Для чтения символа из последовательного порта;
  • Для записи символа в последовательный порт;
  • Для определения состояния порта;
  • Для закрытия последовательного порта.

Инициализация последовательного порта

Вот шаги, которым мы должны следовать:
  • Сначала нужно установить номера передаваемых битов, количество стоп битов и тип четности. Это делается с помощью регистра управления линии (LCR);
  • Затем устанавливается скорость передачи загрузкой старшего и младшего байта делителя;
  • Далее нужно инициализировать UART для управления прерываниями;
  • Мы должны сообщить программируемому контроллеру прерываний ПК (PIC), какие прерывания по последовательному порту он должен допускать;
  • Наконец, необходимо активизировать прерывание, установив бит 3 или 4 в регистре маски прерываний (соблюдая осторожность, чтобы не изменить другие его биты).

Теперь обсудим эти шаги в деталях. Для инициализации порта мы должны, во-первых, установить количество бит, количество стоп-битов и тип четности. Это делается программированием регистра 3 управления линии (LCR). После этого нужно установить скорость передачи с помощью старшего и младшего байтов делителя скорости передачи.

Работа с делителем немного запутана. Вы знаете, что регистры 0 и 1 выполняют дополнительные функции загрузки двухбайтного делителя, деление на который числа 115200 дает результат, используемый UART как окончательная скорость передачи. Однако, как мы знаем, регистры 0 и 1 являются соответственно регистром поддержки передачи (THR) и регистром прерывания (IER). Когда бит 7 регистра управления линией установлен в 1, они получают номера 8 и 9, но продолжают адресоваться как 0 и 1. Ясно?

В качестве примера, давайте установим скорость передачи в 9600 бод. Мы могли бы найти число, которое при делении на него значения 115200 давало бы 9600. Это число 12. Далее мы должны запихнуть его в младший и старший байты. В этом случае младший байт будет равен 12, а старший - 0. Далее нужно установить бит 7 (DLAB) регистра управления линией в 1 и записать младший байт в регистр 0, а старший - в регистр 1, через которые переданные байты и попадут в регистры 8 и 9. После этого необходимо очистить бит 7 (DLAB) регистра управления линии. Это было бы не так уж и плохо, а?

Затем мы инициализируем UART для приема прерываний. Немного поговорим об этом. Когда UART принимает данные, символ будет оставаться в буфере приема только до тех пор, пока не прибудет следующий, после чего вновь пришедший символ заместит старый, независимо от того, был ли он считан. Мы не можем этого допустить, иначе потеряем информацию. Существует два решения этой проблемы:
  • Во-первых, мы могли бы в цикле опрашивать приемный буфер регистра, чтобы не потерять никаких данных. Но это было бы бесполезной тратой времени;
  • Во-вторых (и это гораздо лучше), можно написать для этой цели процедуру обработки прерывания (или ISR).

Если вы помните, в двенадцатой главе, "Мнимое время, прерывания и мультизадачность", говорилось, что в Си несложно установить новое прерывание, используя ключевое слово _interrupt. Мы напишем процедуру, которая будет активизироваться всякий раз с приходом прерывания. Но как нам сообщить UART, что прерывание обнаружено? Если вы пристально посмотрите на описание регистров, то поймете, что вам необходимо установить бит здесь бит там, и UART будет делать свою работу. Чтобы назначить прерывания, мы должны установить следующие биты в UART:

• Бит 0 (RxRDY) регистра прерывания (IER) должен быть установлен в 1;

• Бит 3 (GP02) регистра управления модемом (MCR) должен быть установлен в 1.

После этого мы уже готовы принимать прерывания, правильно? Ошибаетесь! Нужно сделать еще одну вещь. Мы должны сообщить программируемому контроллеру прерываний (PIC), какие именно прерывания по последовательному порту он должен задействовать. Чтобы выполнить это, необходимо изменить установки в регистре маски прерывания (IMP) PIC'a, который доступен через порт 21h. Таблица 14.3 показывает обозначение битов IMR.

^ Таблица 14.3. Регистр маски прерывания (IMR) PIC'a.

Бит 0: IRQ0 - используется для таймера

Бит 1: IRQ1 - используется для клавиатуры

Бит 2: IRQ2 – зарезервирован

Бит 3: IRQ3 - COM2 или COM4

Бит 4: IRQ4 - СОМ1 или COM3

Бит 5: IRQ5 - жесткий диск

Бит 6: IRQ6 - гибкий диск

Бит 7: IRQ7 - принтер

Таким образом, последняя вещь, которую нам необходимо сделать для обработки прерываний и запуска - активировать нужное прерывание по биту 3 или 4. Но будьте осторожны! Регистр инвертирован, так что 0 означает включен, а 1 — выключен.

Осторожно

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

Установка прерывания

Однажды пройдя через все эти фокусы, чтобы установить простое прерывание, можем окончательно инсталлировать наш собственный вектор ISR, зависящий от СОМ-порта. Запомните, что порты 3 и 4 используют те же самые прерывания, что и порты 1 и 2 соответственно.

^ Таблица 14.4. Векторы прерывания последовательного порта.