Создание игр для мобильных телефонов - Майкл Моррисон 33 стр.


Вы можете подумать, что датаграммные сокеты – не идеальное средство для программирования сетевых игр, и в некоторых случаях это так. Однако не всем играм требуется "активное" соединение, обеспечиваемое потоковыми сокетами. В случае специфических мобильных игр чаще всего целесообразно использовать именно датаграммные сокеты в виду ограниченности пропускной способности сети.

...

В копилку Игрока

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

Сетевое программирование и J2ME

Сетевое программирование в мидлетах выполняется с помощью MIDP API, которое носит название Generic Connection Framework или GCF. Цель GCF – обеспечить необходимый уровень абстракции для сетевых сервисов, которые помогают различным устройствам поддерживать специальные протоколы.

Хотя GCF структурирован иначе, он является подмножеством J2SE API. GCF описывает один фундаментальный класс, который называется Connector. Он используется для установления всех сетевых соединений мидлетом. Особые типы соединений моделируются интерфейсами, доступ к которым можно получить через класс Connector. Класс Connector и интерфейсы соединений находятся в пакете javax.microedition.io. Ниже приведено описание некоторых их интерфейсов:

► ContentConnection – потоковое соединение, которое обеспечивает доступ к данным из Web;

► DatagramConnetction – датаграммное соединение, используемое для реализации пакетно ориентированных соединений;

► StreamConnection – двунаправленное соединение с другими устройствами.

С точки зрения программирования мобильных игр, чаще всего вы будете использовать интерфейсы для реализации соединений в игре. Вне зависимости от типа соединения вы будете использовать класс Connector для установления нужных подключений. Все методы класса Connector являются статическими, в том числе и самый важный метод – open(). Наиболее часто используемый вариант этого метода выглядит так:

static Connection open(String name) throws IOException

Параметр, передаваемый в этот метод, – это строка соединения, которая определяет тип создаваемого подключения. Строка соединения описывается так:

Схема:Цель[;Параметры]

Параметр "Схема" – это название сетевого протокола, например, http или datagram. Параметр "Цель" – обычно адрес в сети, но может изменяться в соответствии с особыми типами протоколов. Последний параметр – это список параметров подключения. Ниже приведены строки соединений для различных типов подключений:

HTTP – "http://www.stalefishlabs.com/"

Socket – "socket://www.stalefishlabs:1800"

Datagram – "datagram://:9000"

File – "file:/stats.txt"

Помните, несмотря на то что приведенные примеры – это возможные строки соединения, только одна из них поддерживается реализацией MIDP – первая строка. Согласно спецификации MIDP поддерживается лишь HTTP-соединение. Если вы уверены, что другая реализация MIDP поддерживает какое-либо еще соединение, то вы можете использовать его. В противном случае вы должны создавать только HTTP-соединения, что, надо сказать, не очень хорошо для создания мобильных сетевых игр.

Метод open() возвращает объект типа Connection, который является базовым интерфейсом для всех интерфейсов соединений. Чтобы использовать определенный тип интерфейса соединения, необходимо преобразовать тип Connection к нужному. Следующий код иллюстрирует использование интерфейса DatagramConnection для создания датаграммного соединения:

DatagramConnection dc = (DatagramConnection)Connector.open("datagram://:5555");

...

Совет Разработчику

Число 5555 в примере, – это сетевой порт, используемый датаграммным подключением. Номер порта может быть любым, но больше 1024. Очень важно, чтобы клиент и сервер мидлета соединялись через один и тот же порт.

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

Создание пакетов датаграммы

Чтобы использовать датаграммы для коммуникации через телефонную сеть, необходимо разделить данные на отдельные части – пакеты. Когда мобильные игры передают информацию через датаграммное соединение, они на самом деле отправляют и принимают пакеты. Датаграммные пакеты разработаны так, что они хранят массив байтов, поэтому любые данные в вашем пакете должны быть преобразованы в массив байтов. Когда вы создаете объект типа Datagram, необходимо определить число байтов, помещаемых в пакет. Ниже приведен пример создания объекта Datagram, способного хранить 64 байта информации:

Datagram dg = dc.newDatagram(64);

В этом коде объект Datagram создается вызовом метода newDatagram объекта соединения. Параметр метода newDatagram() – это размер датаграммы в байтах. Такой метод хорошо подходит для приема информации в играх. Другой подход к созданию датаграммы – это создать и заполнить датаграмму в одной строке. Этот метод хорошо подходит для отправления информации, когда у вас есть данные для отправки. Многие игры для коммуникации используют сообщения, при этом каждая строка должна быть преобразована в байтовый массив перед тем, как будет сохранена в датаграмме:

String message = "GameOver" byte[] bytes = message.getBytes();

В этом коде строка "GameOver" преобразуется в массив байтов, который сохраняется в переменной bytes. Для создания датаграммы используется другой вариант метода newDatagram():

Datagram dg = dc.newDatagram(bytes, bytes.length);

В этом коде массив байтов игровых данных передается первым параметром в метод newDatagram(), а его длина – вторым параметром. В ряде случаев (пакет пересылается от сервера к клиенту) необходимо использовать совершенно другой вариант метода newDatagram():

Datagram dg = dc.newDatagram(bytes, bytes.length, address);

В этом методе есть третий параметр, который содержит адрес цели – получателя датаграммного пакета. Адрес необходим только в случае, если сервер пересылает данные клиенту, при этом адрес можно получить из датаграммы клиента, для чего используется функция getAddress().

...

В копилку Игрока

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

Отправка пакетов датаграммы

Интерфейс DatagramConnection предоставляет единственный метод для отправки пакетов датаграммы. Я говорю о методе send(), который очень просто использовать. На самом деле все, что необходимо для отправки пакета, – это лишь одна строка кода:

dc.send(dg);

Посмотрим, как используется этот код в программе. Для этого рассмотрим следующий листинг, в котором сначала создается объект Datagram, а затем пересылается пакет через датаграммное соединение:

// Преобразовать строку в массив байтов

byte[] bytes = message.getBytes();

// Отправить сообщение

Datagram dg = null;

dg = dc.newDatagram(bytes, bytes.length);

dc.send(dg);

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

Получение пакетов датаграммы

Получение пакета датаграммы похоже на отправление пакета, оно выполняется методом интерфейса DatagramConnection. Этот метод называется receive(), в качестве параметра он принимает объект Datagram, точно так же, как send(). Ниже приведен пример использования метода receive() для получения датаграммы:

dc.receive(dg);

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

// Попытка получения пакета

Datagram dg = dc.newDatagram(64);

dc.recieve(dg);

// Убедиться, что датаграмма содержит информацию

if (dg.getLength() > 0) {

String data = new String(dg.getData(), 0, dg.getLength());

}

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

Создание примера Lighthouse

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

Одним из способов коммуникации маяков с экипажами корабля была азбука Морзе – очень удобный способ передачи слов и букв последовательностями "точек" и "тире". Точка – это короткий визуальный или звуковой сигнал, например, вспышка света или металлический звук. Тире по длительности приблизительно равно трем точкам, что соответствует более длительному звуку или вспышке света. Последовательность точек и тире задает отдельные слова. Ниже приведены коды азбуки Морзе.

A – . – J – . – – – S – . . . 1 – . – – – -

B – – . . . K – – . – T – – 2 – . . – – -

C – . – . L – . – . – U – . . – 3 – . . . – -

D – – . . M – – – V – . . . – 4 – . . . . -

E – . N – – . W – . – – 5 – . . . . .

F – . O – – . X – – . . – 6 – – . . . .

G – – – . P – . – – . Y – . – . – 7 – – . . .

H – . . . . Q – – – . – Z – – – . . 8 – – – – . .

I – . . R – . – . 0 – – – – – – 9 – – – – – .

Используя эти коды, вы можете составлять слова и предложения. После каждой буквы следует небольшая пауза, а в конце предложения – более продолжительная. Ниже приведен код слова "hello" в азбуке Морзе:

. . . . . . – . . . – . . – – -

...

В копилку Игрока

Популярное слово азбуки Морзе – это SOS, что, как большинство ошибочно полагает, означает "Спасите Наши Души" (от англ. Save Our Souls). На самом деле это вовсе не аббревиатура, а простое сочетание букв, но оно служит важным сигналом. Это слово кодируется не по правилам азбуки Морзе – без пауз: . . . – – – . . .

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

...

Совет Разработчику

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

Мидлет Lighthouse – это высокотехничная симуляция устаревшей формы коммуникации. С точки зрения программирования этот пример очень важен, поскольку он демонстрирует, как установить соединение "клиент – сервер" между устройствами, а затем выполнять обмен сообщениями.

Разработка клиента и сервера

Мидлет Lighthouse использует все преимущества отношения "клиент – сервер" между двумя мобильными телефонами. Соединение между мидлетами – датаграммное, это означает, что обмен информацией будет производиться датаграммными пакетами. Поскольку при разработке мидлета Lighthouse используется концепция "клиент – сервер", необходимо знать, какой из телефонов инициирует соединение. Ниже перечислено, что происходит между телефоном-клиентом и телефоном-сервером в мидлете Lighthouse:

1. сервер начинает датаграммное соединение и ждет ответа клиента;

2. телефон-клиент открывает датаграммное соединение с телефоном-сервером;

3. когда соединение установлено, клиент и сервер обмениваются сообщениями;

4. клиент и сервер завершают соединение.

Интерес в разрабатываемом мидлете представляет то, что один из телефонов должен функционировать и как клиент, и как сервер в зависимости от контекста. Чтобы реализовать эту двойную функциональность, пользователь при запуске мидлета может определить, какую роль будет играть его телефон – клиента или сервера. После чего один телефон будет работать либо в режиме клиента, либо в режиме сервера. Зная, что для мидлета Lighthouse есть два режима функционирования, целесообразно разделить сетевой код мидлета на код клиента и код сервера.

...

В копилку Игрока

С точки зрения программирования сетевых игр пример Lighthouse является не настоящим примером приложения "клиент – сервер", а коммуникатором между двумя устройствами. В настоящих сетевых играх, основанных на концепции "клиент – сервер", есть отдельное серверное приложение, запущенное на сетевом сервере. Мидлеты, подключающиеся к серверу, – клиенты, а сервер управляет игрой. Мидлет Lighthouse является приложением "клиент – сервер" только лишь с той точки зрения, что одно устройство (сервер) ожидает подключения другого устройства (клиента).

Написание программного кода

Мидлет Lighthouse может работать в двух режимах – режиме клиента и режиме сервера. Режим определяется пользователем через интерфейс при запуске мидлета (вы это увидите чуть позже). Перед тем как вы перейдете к этому, важно разобрать код работы с сетью, который выполняет отправление и прием пакетов через беспроводное соединение.

Клиент и сервер мидлета Lighthouse

Код "клиент – сервер" в мидлете Lighthouse намного легче понять, если начать рассмотрение кода сервера. Все функции сервера содержатся в классе LHServer, который отвечает за ожидание датаграммного подключения клиента. Класс LHServer реализует интерфейс Runnable, что означает, что он запускается в отдельном потоке:

public class LHServer implements Runnable {

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

private LHCanvas canvas;

private DatagramConnection dc;

private String address;

private Boolean connected;

Холст хранится внутри класса LHServer в переменной canvas. Датаграммное соединение хранится в переменной dc – объекте класса DatagramConnection. Переменная address хранит адрес клиента, чтобы пакеты датаграммы могли быть направлены непосредственно получателю. И наконец, переменная connected отслеживает текущее состояние соединения с клиентом. Конструктор класса LHServer принимает единственный параметр – объект класса LHCanvas, конструктор выполняет ряд инициализаций:

public LHServer(LHCanvas c) {

canvas = c;

connected = false;

}

Метод start() также очень прост, он запускает поток:

public void start() {

Thread t = new Thread(this);

t.start();

}

Метод run() – это метод, в котором реализуются основные функции сервера (листинг 14.1). Листинг 14.1. Метод run() класса LHServer отвечает на сообщения, принятые от клиента

public void run() {

try {

// соединиться с клиентским устройством

canvas.setStatus("Waiting for peer client..."); //Первое статусное сообщение сервера говорит о том, что он ожидает клиента

dc = null;

while (dc == null)

dc = (DatagramConnection)Connector.open("datagram://:5555"); //Порты клиента и сервера должны быть одинаковыми

while (true) {

// попробовать принять пакет датаграммы

Datagram dg = dc.newDatagram(32); //Размер датаграммы (32 байта) должен быть достаточно большим, чтобы вместить наибольшее возможное сообщение, однако в игре Lighthouse сообщения не очень велики

dc.receive(dg);

address = dg.getAddress();

// проверить, что датаграмма содержит данные

if (dg.getLength() > 0) {

String data = new String(dg.getData(), 0, dg.getLength());

if (data.equals("Client")) { //В ответ на соединение клиента, изменяется значение переменной и отправляется ответ

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

canvas.setStatus("Connected to peer client.");

connected = true;

// попробовать ответить на принятое сообщение

sendMessage("Server");

}

else {

// отправить данные

canvas.receiveMessage(data); //Сообщение должно содержать знаки азбуки Морзе, поэтому необходимо его передать холсту

}

}

}

}

catch (IOException ioe) {

System.err.println("The network port is already taken.");

}

catch (Exception e) {

}

}

Метод run() начинается с вызова метода setStatus() класса LHCanvas, который выводит в строку статуса холста "Waiting for peer client…" – режим ожидания клиента. Пользователь будет знать, что сервер ожидает подключения клиента. После того как статус выведен на холст, вызывается метод run(), создающий датаграммное соединение. Номер использования порта (5555) – произвольный, однако важно, что клиент и сервер используют один порт для соединения. Также важно указать, что создаваемое соединение – датаграммное.

После того как датаграммное соединение установлено, метод run() запускает бесконечный цикл, в котором выполняются попытки принятия пакетов от клиента. Сначала создается объект класса Datagram, а затем он используется как хранилище и приемник датаграмм. Адрес датаграммы сохраняется на тот случай, если серверу потребуется отправить ответ.

Если датаграмма содержит данные, то байты датаграммы преобразуются в строку. Затем проверяется, равна ли эта строка "Client", специальному сообщению, обозначающему соединение клиента с сервером. Если соединение прошло успешно, то статус изменяется и клиенту отправляется сообщение "Server", таким образом клиент уведомляется о том, что соединение установлено.

Датаграммный пакет содержит строку "Client" только в том случае, если соединение установлено впервые. Далее будут отправляться и приниматься пакеты, содержащие только слова "Dot" (точка) или "Dash" (тире), в зависимости от того, какое сообщение отправляется клиентом. Сообщение передается в класс LHCanvas, где оно обрабатывается методом receiveMessage(). Подробнее об этом вы узнаете чуть позже, когда познакомитесь с кодом холста мидлета Lighthouse.

Назад Дальше