WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 4 | 5 ||

«Том Миллер Managed DirectX*9 Программирование графики и игр о м п * * э* > Предисловие Боба Гейнса Менеджера проекта DirectX SDK корпорации Microsoft SAMS [Pi] KICK START Managed DirectX 9 ...»

-- [ Страница 6 ] --

} Этот метод не так сложен, как может показаться. Одним из его пара­ метров является передаваемый сетевой пакет. Аналогично вызову Write (когда мы размещали данные в пакет) для извлечения этих данных вызы­ вается метод Read.

Глава 19. Создание сессии Client/Server ЧТЕНИЕ ДАННЫХ В УСТАНОВЛЕННОМ ПОРЯДКЕ Обратите внимание, что данные должны читаться в том же самом порядке, в котором они были записаны. В противном случае при работе приложения могут возникнуть ошибки.

Метод Read должен иметь в качестве входного параметра тип полу­ ченных нами данных, который мы должны предварительно определить.

Затем мы устанавливаем необходимость отправки ответа либо опреде­ ленному клиенту, либо всем участникам сети.

Для отправки данных мы используем самое простое действие — SendData. Данная процедура не требует обратной связи (за исключением сообщения собственно на самом сервере).

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

Действие CheckPlayers отличается от остальных. Клиентское прило­ жение запрашивает на сервере необходимую только ему информацию.

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

Сценарий последнего действия похож на сценарии Wave и RunAway.

С помощью метода ReadString мы извлекаем информацию о новом име­ ни клиента непосредственно из сетевого пакета. Единственное главное отличие от описанных выше сценариев Wave и Run away заключается в том, что после включения в новый пакет данных идентификаторов сооб­ щения и отправителя, мы также записываем туда новое имя клиента, ини­ циировавшего указанное действие.

Формирование отклика клиента Теперь мы можем написать процедуру, позволяющую отследить при­ нятие данных клиентом. Добавьте в приложение Client фрагмент кода, похожий на тот, который мы использовали для тех же целей на сервере, см. листинг 19.7.

364 Часть VI. Добавление сетевых возможностей Листинг 19.7. Обработчик получения данных клиентом.

private void OnDataReceive(object sender, ReceiveEventArgs e) { NetworkMessages msg = (NetworkMessages)e.Message.ReceiveData.Read (typeof(NetworkMessages));

string newtext = string.Empty;

int playerlD = 0;

switch (msg) { case NetworkMessages.ChangeName:

playerlD = (int)e.Message.ReceiveData.Readftypeof(int));

string newname = e.Message.ReceiveData.ReadString();

newtext = string.Format ("DPlay Userld 0x(0( changed name to {1}", playerlD.ToString(Y), newname);

break;

case NetworkMessages.CheckPlayers:

int count = (int)e.Message.ReceiveData.Readftypeof(int));

newtext = string.Format ("Server reports {0} users on the server currently.", count) ;

break;

case NetworkMessages.RunAway:

playerlD = (int)e.Message.ReceiveData.Readftypeof (int));

newtext = string.Format ("Server reports DPlay Userld 0x{0} has ran away.", playerlD. ToString(Y));

break;

case NetworkMessages.Wave:

playerlD = (int)e.Message.ReceiveData.Readftypeof(int));

newtext = string.Format ("Server reports DPlay Userld 0x{0} has waved.", playerlD. ToString(Y));

break;

) //We received some data, update our UI this.Beginlnvoke(new AddTextCallback(AddText), new object[] { newtext });

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

Глава 19. Создание сессии Client/Server Обработка отключения сервера Последнее, что мы должны сделать для созданного соединения, — пра­ вильно отследить момент потери или отсоединения сервера. Нечто по­ добное мы уже проделывали при работе с Р2Р-сетями. Вначале мы до­ бавляем определение этого события (SessionTerminated):

connection.SessionTerminated += new SessionTerminatedEventHandler(OnSessionTerminate);

И записываем код для этого обработчика:

private void OnSessionTerminate(object sender, SessionTerminatedEventArgs e) { this.Beginlnvoke(new DisconnectCallback(OnDisconnect), null);

} private void OnDisconnect() { EnableSendDataButtons(false);

AddText("Session terminated.") ;

connected = false;

// Dispose of our connection, and set it to null connection.Dispose();

connection = null;

} Данная процедура проверяет факт отсоединения и освобождает объект.

Также выводится сообщение о том, что соединение разорвано.

Краткие выводы В этой главе мы рассмотрели следующие вопросы.

• Создание выделенных серверов.

• Соединение с серверами с помощью клиентского интерфейса.

• Отслеживание подключения и отключения игрока.

• Передача игровых данных.

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

366 Часть VI. Добавление сетевых возможностей Глава 20. Особенности более совершенного использования сетей Эта глава охватывает дополнительные возможности DirectPlay, кото­ рые позволят нам расширить диапазон использования сетей, и включает следующие разделы.

• Модели событий.

Пропускная способность, трафик.

• Очередность отправки данных.

Приложения лобби «lobby-launching».

Использование голосовой связи.

Модели событий и обработчики К настоящему моменту мы изучили работу Р2Р-сетей и основы архи­ тектуры клиент-сервер. Теперь мы можем попытаться расширить диапа­ зон наших знаний относительно использования сетей.

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

ApplicationDescriptionChanged — применяется для классов Peer, Server и Client. Отслеживается событие, когда изменяется описа­ ние объекта (например, меняется имя).

AsyncOperationComplete — применяется для классов Peer, Server и Client. Фиксирует завершение асинхронной операции. Некото­ рые функции DirectPlay (например, Connect или Receive) возвра­ щают параметр, который сообщает приложению о завершении выполнения. С помощью этого параметра соответствующие ме­ тоды можно отменить, используя процедуру CancelAsyncOperation.

Необходимо помнить, что некоторые операции могут заканчивать­ ся автоматически, как, например, метод FindHosts при установке соединения.

• ConnectComplete — применяется для классов Peer и Client. Собы­ тие возникает после неудачной попытки соединения. Значение па­ раметра ResultCode указывает на причину возникшей ситуации.

Например, если хост отклонил ваш запрос о подключении, резуль­ татом будет значение HostRejectedConnection.

FindHostQuer — применяется для классов Peer и Server. Событие фиксируется, когда новое клиентское приложение вызывает ме­ тод FindHosts и начинает выполнять поиск хоста. Если вы не из­ мените значение параметра RejectMessage (по умолчанию «false») перед тем, как ответить на запрос, метод FindHosts успешно обна Глава 20. Особенности более совершенного использования сетей ружит ваш хост. Установка аргумента RejectMessage в значение «true» сделает ваш сеанс недоступным для клиента.

• FindHostResponse — применяется для классов Peer и Client. От­ слеживается событие обнаружения хоста. Возвращаемый параметр будет содержать информацию (адрес и описание хоста), доста­ точную для того, чтобы соединиться с найденным сервером. Если не происходит немедленного соединения с сервером, поиск будет продолжаться, и вполне возможно, что один и тот же хост будет найден несколько раз.

GroupCreated — применяется для классов Peer и Server. Возника­ ет в результате успешного выполнения процедуры создания групп игроков CreateGroup.

• GroupDestroyed — применяется для классов Peer и Server. Отсле­ живается событие расформирования группы (например, в резуль­ тате использования метода DestroyGroup или при разрыве соеди­ нения). Если при создании группы использовался флажок AutoDestruct, группа будет автоматически расформирована, если один из участников завершил сеанс. Причину расформирования группы можно найти в параметре Reason.

• Grouplnformation — применяется для классов Peer и Server. Когда изменяется информация о группе (например, при помощи метода SetGroupInformation), это событие регистрируется всеми ее участ­ никами. В качестве параметра возвращается только идентификатор группы, поэтому не забудьте запросить остальную информацию.

• HostMigrated — обработчик применим только к классу Peer. Если в течении Р2Р-сеанса главный компьютер вышел из соединения, статус хоста передается следующему компьютеру. Событие про­ исходит, только если при создании хоста был включен соответ­ ствующий флажок.

• IndicateConnect — применяется для классов Peer и Server. Собы­ тие, аналогичное событию FindHostQuery (соединение также мо­ жет быть запрещено).

• IndicateConnectAborted — применяется для классов Peer и Server.

Как правило, за событием IndicateConnect должно следовать со­ бытие PlayerCreated. Однако, если по каким-либо причинам со­ единение было прервано до этого момента (например, случайный разрыв соединения), произойдет указанное событие.

• Peerlnformation — обработчик применим к классу Peer. Отслежи­ вает изменение данных объекта peer. При необходимости следует восстановить остальную информацию.

• PlayerAddedToGroup — применяется для классов Peer и Server.

Происходит, когда новый игрок (или группа игроков) добавляется к существующей группе. Использует идентификаторы группы и игрока.

368 Часть VI. Добавление сетевых возможностей • PlayerCreated — применяется для классов Peer и Server. Данное событие следует за событием IndicateConnect. В момент выпол­ нения обработчика в качестве аргумента можно устанавливать контекстную переменную для игрока (любая специфическая ин­ формация об игроке). Каждый раз, получая данные от игрока, можно получать и контекстную переменную этого игрока.

• PlayerDestroyed — применяется для классов Peer и Server. Собы­ тие фиксируется, когда игрок покидает сеанс связи. Когда же за­ вершается сам сеанс связи, мы получаем подобное сообщение от каждого клиента.

PlayerRemovedFromGroup — применяется для классов Peer и Server.

Происходит при удалении игрока из группы (например, с помощью вызова RemovePlayerFromGroup) или выходе игрока из сеанса.

Receive — применяется для классов Peer, Server и Client. Инфор­ мирует о получении новых данных (сетевого пакета, идентифи­ катора игрока, контекстной переменной игрока, отправляющего информацию и т. д.). Обратите внимание на использование флаж­ ка NoLoopback, который мы уже описывали раньше.

• SendComplete — применяется для классов Peer, Server и Client.

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

SessionTerminated — применяется для классов Peer и Client. Со­ бытие отслеживается каждый раз при завершении сеанса. Полу­ чить информацию о причине завершения соединения можно с помощью параметра ResultCode. Может содержать определяемую пользователем информацию (например, для организации другого подключения) Clientlnformation — применяется для классов Server и Client. От­ слеживается момент изменения информации о клиенте. При не­ обходимости информацию следует восстановить после регистра­ ции события.

• Serverlnformation — применяется для классов Server и Client. Отсле­ живает изменение информации о сервере. При необходимости тре­ буется восстановление информации после регистрации события.

Определение пропускной способности и статистики сети На сегодняшний день вопросы пропускной способности соединений являются весьма актуальными. Подключение к Internet с помощью моде Глава 20. Особенности более совершенного использования сетей ма имеет достаточно низкую пропускную способность. Сеть на базе Ethernet более предпочтительна и является наиболее распространенной на данный момент, но и здесь также имеются ограничения. Задача разра­ ботчика сетевых приложений состоит в том, чтобы самым рациональ­ ным образом распределять трафик, добиваясь максимальной пропуск­ ной способности соединения.

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

DirectPlay API достаточно универсален и позволяет оптимальным об­ разом распределять пересылаемые клиентам данные. Для получения ин­ формации о состоянии имеющихся подключений необходимо, указав иден­ тификатор запрашиваемого игрока, вызвать метод GetConnectionlnformation.

Клиент возвратит информацию о соединении с сервером. Ниже приведен пример такой информации:

Connection information:

PacketsDropped: BytesDropped: PacketsRetried: BytesRetried: PacketsSentNonGuaranteed: BytesSentNonGuaranteed: PacketsSentGuaranteed: BytesSentGuaranteed: PeakThroughputBps: ThroughputBps: RoundTripLatencyMs: MessagesReceived: • PacketsReceivedNonGuaranteed: BytesReceivedNonGuaranteed: PacketsReceivedGuaranteed: BytesReceivedGuaranteed: MessagesTimedOutLowPriority: MessagesTransmittedLowPriority: MessagesTimedOutNormalPriority: MessagesTransmittedNormalPriority: MessagesTimedOutHighPriority: MessagesTransmittedHighPriority: В этом блоке содержится практически вся информация о соединении:

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

13 Зак. 370 Часть VI. Добавление сетевых возможностей Здесь же можно найти информацию о максимальной (PeakThroughputBps) и средней (ThroughputBps) пропускной способности канала, измеряемой в байтах за секунду. Данная информация позволяет проверить, насколько оп­ тимально загружается канал.

Под средним временем ожидания (RoundTripLatencyMs) можно по­ нимать время, затраченное на отправку пакета и на пересылку его обрат­ но. Данный параметр коррелирует с пропускной способностью, но при этом позволяет отследить временные затраты для данного конкретного канала.

В завершение мы получаем информацию о количестве сообщений, полученных в различные моменты времени и имеющих различные при­ оритетные уровни (нормальный, низкий и высокий).

Необходимо отметить, что DirectPlay не будет посылать сообщения удаленному компьютеру быстрее, чем тот может их обработать. Если удаленный компьютер не отвечает долгое время, отправитель создаст очередность отправляемых данных. Перед отправкой данные могут быть сгруппированы в пакеты, которые, в свою очередь, могут быть объеди­ нены. Вы можете определить количество данных в очереди, вызывая метод GetSendQueuelnformation (выполняется аналогично методу GetConnectionlnformation для сервера или Peer-объекта). При этом зап­ рос возвращает два целых числа (для каждого из объектов), первое — число сообщений, находящихся в очереди, и второе — суммарное чис­ ло байтов для этих сообщений.

Также возможно управлять приоритетом отсылаемых данных, исполь­ зуя соответствующие флажки GetSendQueuelnformationFlags. Это позво­ ляет отследить момент переполнения очереди и ограничить количество пересылаемых данных.

Наибольшее внимание при настройке соединений следует уделить устранению «ненужных» данных, которые передаются по каналу (это касается пересылки без необходимости строк, булевых переменных и пр.).

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

private bool IsRunning = true;

private bool IsMale = false;

private bool IsWarrior = true;

private bool IsGhost = false;

Если отправлять данные в таком виде, передаваемый пакет будет иметь размер, равный 16-ти байтам, что является весьма расточительным в плане трафика. Наша задача снизить это значение, по крайней мере, до одного байта:

Глава 20. Особенности более совершенного использования сетей private const byte IsRunning = Oxl;

private const byte IsMale = 0x2;

private const byte IsWarrior = 0x4;

private const byte IsGhost = 0x8;

private byte SendData = IsRunning | IsWarrior;

Теперь однобайтовая переменная SendData содержит информацию, соответствующую четырем булевым переменным. Таким способом мы можем «замаскировать» до 8-ми логических переменных (экономя при этом 31 байт). Если необходимо переслать более чем 8 переменных, можно использовать короткий формат, который может поддерживать до 16-ти булевых переменных (экономя 62 байта) или длинный формат, который может поддерживать до 64-х булевых переменных (экономя 252 байта).

Для уменьшения трафика при возможности необходимо использовать наименьший формат.

Запуск приложений, использующих концепцию «Lobby» Для того, кто когда-либо запускал игры типа MSN Games (сайт http:// zone.msn.com), концепция «лобби» достаточно понятна. По существу, лобби — это подход, когда группа игроков собирается перед запуском игры, и игра запускается одновременно для всех игроков, при этом все игроки автоматически соединяются друг с другом.

Вспоминая сборки для DirectPlay, необходимо заметить, что это — пер­ вая используемая нами сборка, имеющая подпространство имен. Внутри этой сборки имеются два дополнительных пространства имен: Lobby и Voice. Свойства второго списка мы рассмотрим позже, а пока сконцент­ рируемся на пространстве имен Lobby.

Пространство Lobby включает два основных класса, которые будут управлять взаимодействиями в лобби-приложениях: Application и Client.

Класс Client используется для запуска и поддержки лобби-приложений на удаленных машинах, а класс Application непосредственно управляет лобби-приложением. Каждый из упомянутых классов имеет модели со­ бытий, подобно тем моделям, которые используются в классах Peer, Server и Client.

Вначале нам необходимо определить программы, которые могут за­ пускаться в режиме Lobby, а уже потом попробовать запустить их. Как обычно, создаем новое окно приложения, устанавливаем параметр Dock в значение «Fill», проверяем добавление ссылок на DirectPlay и директи­ ву using для пространства имен Lobby:

using Microsoft.DirectX.DirectPlay.Lobby;

Часть VI. Добавление сетевых возможностей Отдельно объявляем переменную класса Client, поскольку именно этот класс отвечает за объекты и запуск лобби-приложения:

private Client connection = null;

Так же мы должны освободить объект после выхода из соединения, добавив две строки к соответствующей перегрузке Dispose:

if (connection != null) connection.Dispose ();

Следует отметить, что имя класса Client встречается и в пространстве имен DirectPlay, и в пространстве имен Lobby. Таким образом, мы долж­ ны однозначно определить оба этих пространства и включить их в ди- • рективу using. Это же касается и другого класса (Application), который уже включен в пространство имен System.Windows.Forms.

Теперь давайте заполнять окно списка «list box» для наших лобби приложений. Добавьте следующий код к вашему конструктору форм:

// Fill the list box connection = new Client();

foreach(ApplicationInformation ai in connection.GetLocalPrograms()) { listBoxl.Items.Add(ai.ApplicationName);

} Как видите, это относительно простая операция. После создания лоб­ би-объекта Client происходит перебор всех имеющихся локальных про­ грамм, и название каждой из них записывается в наше окно. Запуск этих приложений не сложен;

добавим процедуру обработки двойного нажа­ тия кнопки мыши в окне списка. Для этого используем код, приведенный в листинге 20.1.

Листинг 20.1. Запуск приложения.

private void HstBoxl_DoubleClick(object sender, System.EventArgs e) } if (listBoxl.Selectedltem == null) return;

foreachfApplicationlnformation ai in connection.GetLocalPrograms()) { if (ai.ApplicationName == (string)listBoxl.Selectedltem) { Connectlnformation ci = new Connectlnformation();

Глава 20. Особенности более совершенного использования сетей ci.GuidApplication = ai.GuidApplication;

ci.Flags = ConnectFlags.LaunchNew;

connection.ConnectApplication(ci, System.Threading.Timeout.Infinite, null);

break;

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

ПРИСОЕДИНЕНИЕ К С СУЩЕСТВУЮЩЕЙ СЕССИИ ИЛИ СЕАНСУ Мы также можем присоединиться к уже выполняемому приложению, имеющему статус лобби. Для этого необходимо установить флажок ConnectFlags.LaunchNotFound в структуре ConnectionSettings (если действующее приложение не будет найдено, запустится новый эк­ земпляр).

Следует отметить, что при компиляции появится сообщение о нео­ пределенности класса Application, поскольку класс Application встреча­ ется и в System.Windows.Forms, и в Microsoft.DirectX.DirectPlay.Lobby.

По этой причине перепишем основную процедуру следующим образом:

static void Main() { using (Forml frm = new Forml()) { frm.Show();

System.Windows.Forms.Application.Run(frm);

} } После успешного запуска приложения метод ConnectApplication воз­ вращает обработчик соединения, с помощью которого мы можем отпра­ вить данные в приложение, используя метод Send, или вызывать метод ReleaseApplication, который завершит работу приложения (но это приве­ дет не к закрытию приложения, а к отсоединению лобби-клиента от дан­ ного приложения).

374 Часть VI. Добавление сетевых возможностей Создание лобби-приложения Класс Application в пространстве имен Lobby определяет структуру информации, доступную после запуска лобби-сеанса. Регистрация лоб­ би-приложения довольно проста. Необходимо создать и заполнить струк­ туру ProgramDescription, обязательно указав идентификатор GUID для этого приложения, имя, путь и параметры файла. Затем мы можем выз­ вать либо метод RegisterProgram, либо метод UnregisterProgram в зависи­ мости от выполняемой операции.

До сих пор мы подробно не рассматривали конструктор объекта Application для лобби-соединения. Обсудим в качестве примера вариант, содержащий наибольшее число параметров:

public Application ( System.Int32 connectionHandle, Microsoft.DirectX.DirectPlay.Lobby.InitializeFlags flags, Microsoft.DirectX.DirectPlay.Lobby.ConnectEventHandler connectEventHandler ) Параметр обработчика подключения является выходным параметром.

Если ваше приложение было запущено в качестве лобби-клиента, этот обработчик будет возвращен процедурой ConnectApplicaton, в против­ ном случае он будет не определен. Использование флажков InitializeFlags позволяет отключить проверку этого параметра. Последний параметр ConnectEventHandler —позволяет отслеживать событие подключения еще до создания объекта.

После запуска лобби-клиента необходимо вызвать метод RegisterLobby для подключения к соответствующему сеансу, используя в качестве па­ раметра структуру ConnectionSettings, полученную с помощью обработ­ чика подключения.

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

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

Подготовив предварительно наш новый проект, мы должны добавить ссылку на DirectSound, а также на соответствующие переменные и ди­ рективы пространства имен Voice (учитывая, что данное пространство используется и в классе Server, и в классе Client):

Глава 20. Особенности более совершенного использования сетей using Voice = Microsoft.DirectX.DirectPlay.Voice;

using Microsoft.DirectX.DirectSound;

Объявляем переменные Voice для сервера и клиента. Для Р2Р-сети главный компьютер (хост) играет роль сервера, но поскольку любой из объектов Р2Р-сети может принять полномочия хоста, объект peer должен содержать обе части Voice — и для сервера, и для клиента:

private Voice.Client voiceClient = null;

private Voice.Server voiceServer = null;

Как и раньше, вначале мы должны создать серверную часть. Необхо­ димо найти и переписать секцию кода, где мы создавали хост для Р2Р сети. Затем в методе инициализации InitializeDirectPlay следует добавить следующий код после вызова EnableSendDataButton:

// Create our voice server first voiceServer = new Voice.Server(connection);

// Create a session description Voice.SessionDescription session = new Voice.SessionDescriptionf);

session.SessionType = Voice.SessionType.Peer;

session.BufferQuality = Voice.BufferQuality.Default;

session.GuidCompressionType = Voice.CompressionGuid.Default;

session.BufferAggressiveness = Voice.BufferAggressiveness.Default;

// Finally start the session voiceServer.StartSession(session);

Итак, теперь мы должны связать создаваемый объект Voice с объек­ том DirectPlay, который будет выполнять функцию передачи голоса. Это позволит передавать голос наряду с другими данными сети. Поскольку мы используем Р2Р-сеть, сообщения будут передаваться другим игрокам напрямую, однако существуют и другие сценарии голосового общения.

• Смешанный — в этом режиме все голосовые сообщения отсыла­ ются серверу. Затем сервер объединяет их и переправляет игрокам.

Такой подход перегружает трафик, но является очень удобным.

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

Эхо возвращает звуковое сообщение назад.

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

376 Часть VI. Добавление сетевых возможностей foreach(Voice.Compressionlnformation ci in voiceServer.CompressionTypes) ( Console.WriteLine(ci.Description);

} Данная информация позволит выбрать наиболее подходящий кодек.

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

Для оставшихся параметров мы устанавливаем значения по умолча­ нию.

И как обычно, мы добавляем процедуру выхода из соединения и осво­ бождения объекта:

if (voiceServer != null) { voiceServer.StopSession();

voiceServer.Dispose();

voiceServer = null;

} Теперь мы имеем половину программы, позволяющей осуществить голосовое соединение. Добавьте к вашему приложению код, приведен­ ный в листинге 20.2.

Листинг 20.2. Присоединение к сеансу голосовой связи.

private void ConnectVoice() { // Now create a client to connect voiceClient = new Voice.Client(connection) ;

// Fill in description object for device configuration Voice.SoundDeviceConfig soundConfig = new Voice.SoundDeviceConfig(), soundConfig.Flags = Voice.SoundConfigFlags.AutoSelect;

soundConfig.GuidPlaybackDevice = DSoundHelper.DefaultPlaybackDevice;

soundConfig.GuidCaptureDevice = DSoundHelper.DefaultCaptureDevice;

soundConfig.Window = this;

// Fill in description object for client configuration Voice.ClientConfig clientConfig = new Voice.ClientConfig();

clientConfig.Flags = Voice.ClientConfigFlags.AutoVoiceActivated | Voice.ClientConfigFlags.AutoRecordVolume;

clientConfig.RecordVolume = (int) Voice.RecordVolume.Last;

clientConfig.PlaybackVolume = (int) Voice.PlaybackVolume.Default;

clientConfig.Threshold = Voice.Threshold.Unused;

clientConfig.BufferQuality = Voice.BufferQuality.Default;

ClientConfig.BufferAggressiveness = Voice.BufferAggressiveness.Default;

Глава 20. Особенности более совершенного использования сетей // Connect to the voice session voiceClient.Connect(soundConfig, clientConfig, Voice.VoiceFlags.Sync);

voiceClient.TransmitTargets = new int[] ( (int)Voice.PlayerId.AllPlayers 1;

} Сначала мы создаем объект voice client и указываем объект DirectPlay, который будет использоваться для передачи голосовых данных. Затем, прежде чем присоединиться к сеансу, мы должны установить конфигу­ рацию для обеих звуковых карт. Для этого используется структура SoundDeviceConfig, сообщающая DirectPlay об устройствах, которые могут использоваться для голосовой связи. В нашем случае мы автома­ тически выбираем микрофон и заданные по умолчанию устройства зах­ вата и воспроизведения звука (в главе DirectSound мы использовали со­ вместный доступ к этим устройствам, и здесь мы оставим это без изме­ нения).

Далее с помощью структуры ClientConfig мы определяем параметры клиента. Флажки используются таким образом, чтобы максимальное ка­ чество звука устанавливалось автоматически и передача начиналась сра­ зу, как только будет получен звук от микрофона. При использовании флаж­ ка AutoVoiceActivated необходимо установить пороговое значение в по­ ложение «unused». Если же осуществляется «ручной» запуск передачи звука, пороговое значение должно быть установлено на минимальный уровень.

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

Присоединившись к сеансу, мы определяем объекты для передачи голо­ совых сообщений.

И наконец, нам осталось сделать два добавления. Во-первых, необхо­ димо добавить процедуру Dispose, чтобы освободить объект и выйти из соединения (опять используя синхронный флажок):

if (voiceClient != null) { voiceClient.Disconnect(Voice.VoiceFlags.Sync);

voiceClient.Dispose();

voiceClient = null;

} Во-вторых, мы должны предусмотреть вызов метода ConnectVoice и для сервера, и для клиента: в конце метода StartSession на сервере, чтобы позволить главному компьютеру создание клиента voice client, и в проце­ дуре OnConnectComplete после успешной установки соединения:

Часть VI. Добавление сетевых возможностей if (e.Message.ResultCode == ResultCode.Success) { this.Beginlnvoke(new AddTextCallback(AddText), new object[] { "Connect Success."});

connected = true;

this.Beginlnvoke(new EnableCallback(EnableSendDataButton), new object[] { true } );

ConnectVoice();

} ПРОВЕРКА УСТАНОВКИ ЗВУКА Если вы не использовали мастер установки, вызов Connect может выдать ошибку RunSetupException. В этом случае можно «захватить» это исключение и произвести установку звука, используя следую­ щий код:

Voice.Test t = new Voice.Test();

t.CheckAudioSetup();

После установки можно попробовать подключиться еще раз. Дан­ ная установка выполняется один раз.

Краткие выводы В этой главе мы рассмотрели различные особенности DirectPlay, вклю­ чая следующие разделы.

• Модели событий и обработчики.

• Пропускная способность, трафик.

• Очередность отправки данных.

• Лобби-приложения.

• Использование голосовой связи в приложении.

В нашей заключительной главе мы обобщим накопленную к этому моменту информацию.

Глава 21. Методы достижения максимального быстродействия Глава 21. Методы достижения максимального быстродействия В данной главе будут охвачены следующие темы.

• Операции преобразования типов: boxing и unboxing.

• Модель события и ее недостатки.

• Повышение эффективности.

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

Пример Billboard (Доска Объявлений), который поставляется с DirectX SDK, имеет различные версии: неуправляемую версию, написанную на C++, а также две управляемые версии, одну, написанная на С#, другую, разработанную в среде VB.NET Поскольку каждый пример из DirectX SDK учитывает частоту смены кадров, можно легко определить, какое из приложений выполняется быстрее.

Сравнивая пример Billboard, написанный на C++, с образцом, написан­ ном на С#, нужно обратить внимание на то, что скорость выполнения С# приложения составляет примерно 60% от скорости образца на C++. При­ нимая во внимание использование управляемого и неуправляемого кода в различных версиях следует выяснить, с чем связано это «замедление».

Термин «boxing» для.NET Runtime подразумевает преобразование типов в некий объект. Соответственно, процесс обратного преобразова­ ния называется «unboxing». Для выполнения этих операций среде.NET Runtime необходимо выделить часть динамической памяти, достаточную для размещения данных, и затем скопировать данные из стека (где хра­ нится значение) в созданную область динамической памяти.

Теперь, используя управляемую версию примера Billboard, можно обратить внимание на то, что каждая отображаемая ветвь имеет соответ­ ствующую структуру, содержащую необходимую для рисования инфор­ мацию. Поскольку структура смешана, но должна отображаться в соот­ ветствующем порядке, выполним следующую операцию:

trees.Sort (new TreeSortClass());

Данный класс имеет встроенную процедуру сравнения IComparer, ко­ торая принимает в качестве входных параметров два объекта. Прежде чем она будет передана в метод сравнения, структура используемых объек­ тов должна быть преобразована с помощью метода boxing, а после вы­ полнения процедуры сравнения — преобразована обратно (unboxing).

380 Часть VI. Добавление сетевых возможностей Метод Sort будет вызываться приблизительно 4300 раз за кадр. При каждом вызове будут выполняться две операции преобразования boxing и, соответственно, две обратных операции. Сама структура определена следующим образом:

public struct Tree { public CustomVertex.PositionColoredTextured vO, vl, v2, v3;

public Vector3 position;

public int treeTexturelndex;

public int offsetlndex;

};

Можно рассчитать объем памяти, который потребуется для размеще­ ния данных в процессе выполнения операции сортировки (исходя из раз­ мера структуры равного 116-ти байтам). Для этого необходимо умножить величину объекта (116 байт) на число объектов (2) и на количество вызо­ вов для одного кадра (4300), получается огромное значение — 997, байтов на каждый кадр. И это касается только операции размещения.

После того как данные скопированы, и операция сравнения проведе­ на, они должны пройти процедуру unboxing. Это подразумевает те же действия по выделению памяти и копированию данных, только на этот раз для стека. В итоге, данные для каждого кадра (1,995,200 байт) рас­ пределяются между стеком и динамической памятью с поочередным ко­ пированием, причем размер распределенных областей небольшой ( байт), а их количество составляет немалое значение. Теперь, по крайней мере, понятна причина замедления выполнения программ на C++. Ис­ пользование среды.NET Runtime дает огромное преимущество в быст­ родействии и гибкости, что должно послужить причиной для перехода к программированию в этой среде.

Побочные эффекты моделей событий Допустим, имеется знакомый сценарий, характерный для Управляе­ мого Direct3D. Мы уже достаточно знакомы с работой обработчиков со­ бытий в приложениях Управляемого DirectX, и можем предположить, что многие операции, которые выполняют обработчики, расходуют немало ресурсов, если использовать их не должным образом.

Во-первых, это касается распределения памяти. Например, каждый раз при создании вершинного буфера, помимо обычного резервирования памяти, выделяется дополнительная память для делегатов, необходимых для обработки событий. Обработчики событий непосредственно связа­ ны с этими объектами во время работы приложения. Рассмотрим типич­ ный пример:

Глава 21. Методы достижения максимального быстродействия public void WasteVideoMemory() { VertexBuffer waste = new VertexBuffer(device, 5000, 0, VertexFormats.None, Pool.Default);

} Этот код выглядит «мертвым». Конечно, вершинный буфер создается, но он никогда не используется, тем не менее, программа «сборщика му­ сора» все равно обработает данный буфер.

После создания вершинного буфера отслеживаются события DeviceLost, DeviceReset и Disposing. Обработчики этих событий связывают устрой­ ство и вершинный буфер, и, если буфер нигде не используется, «сборщик мусора» не заметит его.

Если бы подобный метод вызывался многократно, мы бы постепенно исчерпали весь запас свободной видеопамяти (поскольку объект был со­ здан в заданном по умолчанию пуле памяти).

Другой побочный эффект подобного поведения — время перезагруз­ ки «shutdown time».

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

device.SetRenderTarget(0, mySwapChain.GetBackBuffer(0, BackBufferType.Mono));

// Render some stuff Этот код выполняет примерно те же самые операции, которые мы опи­ сывали в предыдущем примере. Создается новый объект (в данном слу­ чае поверхность), который никогда не используется. Таким образом, в результате многократного выполнения этой строки создаются тысячи «потерянных» поверхностей. При закрытии приложения устройство пы­ тается освободить все эти поверхности (функция dispose) и естественно «зависает».

Есть два пути для решения этой проблемы. Первый, самый простой, — включение директивы using для объектов, которые будут использоваться в ближайшее время. Соответствующий код может иметь вид:

using(Surface backBuffer = mySwapChain.GetBackBuffer(0, BackBufferType.Mono)) { device.SetRenderTarget(0, backBuffer);

// Render some stuff.

I 382 Часть VI. Добавление сетевых возможностей ИСПОЛЬЗОВАНИЕ ДИРЕКТИВЫ USING Эта директива автоматически размещает создаваемый объект. Ком­ пилятор С# разобьет директиву на код, подобный этому:

Surface backBuffer;

try { backBuffer = mySwapChain.GetBackBuffer(0, BackBufferType.Mono);

device.SetRenderTarget(0, backBuffer);

// Render some stuff } finally { if (backBuffer != null) backBuffer. Dispose();

} Использование директивы using дает существенный выигрыш в быст­ родействии, особенно для случаев многократного распределения неболь­ ших объемов данных в памяти (см. выше). Однако, это не решает про­ блему выделения дополнительной памяти для процедур обработки со­ бытий. Как правило, события и обработчики событий, содержащиеся в Управляемом DirectX, позволяют вам, как разработчику, не заботиться о размерах и времени существования объекта. Тем не менее, в зависимос­ ти от требований и задач приложения, иногда весьма полезно управлять свойствами объектов самостоятельно.

В версии SDK Update Управляемого DirectX 9 мы имеем возможность отключать обработчик событий, если мы знаем заранее, что он нам не понадобится:

Device.IsUsingEventHandlers = false;

device = new Deviсе (...);

Значение «true» установлено по умолчанию, и мы можем изменить его в любое время. Устанавив «false» еще до создания устройства, мы полностью отключаем обработку событий в приложении.

ОТКЛЮЧЕНИЕ ОБРАБОТКИ СОБЫТИЙ Использование этой особенности выключит автоматическое отсле­ живание событий. Использование внешнего управления обработ­ чиками потребует некоторого опыта и сноровки, поэтому при напи­ сании программ тщательно проверяйте ваши действия.

Глава 21. Методы достижения максимального быстродействия Итак, теперь в большинстве случаев мы можем использовать обра­ ботчики в нашем приложении, при необходимости отключая некоторые из них. Рассмотрим следующий код:

// Device is currently using event handlers Device.IsUsingEventHandlers = false;

using(Surface backBuffer = mySwapChain.GetBackBuffer(0, BackBufferType.Mono)) { device.SetRenderTarget(0, backBuffer);

// Render some stuff.

} // Allow device events to be hooked again Device.IsUsingEventHandlers = true;

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

ИСПОЛЬЗОВАНИЕ УСТРОЙСТВА «THREADING DEVICE» Так как существует возможность отключения обработки событий, мы можем использовать в устройстве мультипоточный режим, вклю­ чив соответствующий флажок в используемой для создания устрой­ ства структуре параметров. Если мы собираемся управлять обра­ боткой событий «вручную», это можно делать в общем потоке:

presentParams.ForceNoMultiThreadedFlag = true Эффективность методов Знание эффективности каждого из используемых методов в отдельно­ сти может существенно помочь при написании быстрого и эффективно­ го приложения.

В Управляемом DirectX имеются случаи, когда, казалось бы, «безо­ бидный» код может вызвать значительные проблемы. Например, любой метод, принимающий в качестве параметра строку, должен разместить в памяти неуправляемый строковый тип и скопировать туда данный пара­ метр.

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

Часть VI. Добавление сетевых возможностей myEffeet.Technique = "TransformWorldSpace";

//Render some stuff myEffeet.Technique = "TransformAndAddGlow";

// Render some other stuff Каждый раз, когда мы переключаем методику, происходит перерасп­ ределение памяти и копирование названия методики. Решить данную проблему можно с помощью кэширования возвращаемых указателей, например:

// On device creations, cache technique handles EffectHandle handlel = myEffect.GetTechnique("TransformWorldSpace");

EffectHandle handle2 = myEffeet.GetTechnique("TransformAndAddGlow");

// Later on, for every render myEffeet.Technique = handlel;

//Render some stuff myEffeet.Technique = handle2;

//Render some stuff Краткие выводы В этой главе мы рассмотрели следующие вопросы.

• Операции преобразования типов boxing и unboxing.

• Использование модели события и ее недостатки.

• Эффективность используемых методов.

ЧАСТЬ VII ПРИЛОЖЕНИЯ 386 Часть VII. Приложения Приложение А. Использование сборок диагностики Предположим, что вы только что завершили написание игры и гото­ вите ее к выходу в свет. Несмотря на положительные тестовые результа­ ты вашей испытательной группы, у вас все равно не было возможности проверить это приложение на всех системах без исключения.

Допустим, что приблизительно 5 % всех пользователей, купивших игру, не смогут запустить ее по каким-либо причинам. У вас есть предположе­ ния, что это связано с типом используемой видеокарты. Попробуем про­ верить это, используя пространство имен Diagnostics.

В этом приложении мы рассмотрим следующие вопросы.

Перечисление всех диагностических опций и вывод сообщений о них.

Проверка отдельных пунктов.

Перечисление всех опций в системе Пространство имен Diagnostics включает в себя те же опции, что и инструмент DxDiag. Используя его, можно узнать практически все о ва­ шей системе Данные диагностики размещаются иерархически. Можно использо­ вать рекурсивную функцию для перечисления всех возможных опций и объектов, находящихся в указанном корневом контейнере. Данная функ­ ция приведена в листинге АЛ и включена в образец DirectX SDK DxDiagOutput.

Листинг А.1. Выводимые данные диагностики.

static void OutputDiagData(string parent, Container root) { try { foreach (PropertyData pd in root.Properties) { // Just display the data Console.WriteLine("{0}.{l} = {2}", parent, pd.Name, pd.Data);

} } catch try { Приложение А. Использование сборок диагностики foreach (ContainerData cd in root.Containers) { // Recurse all the internal nodes if (parent == null) OutputDiagData(cd.Name, cd.Container);

else OutputDiagData(parent + V + cd.Name, cd.Container);

} } catch { } // We are done with this container, we can dispose it.

root.Dispose() ;

} Здесь выполняется поиск и отображение всех имеющихся свойств и опций, а также соответствующих им значений. В зависимости от свой­ ства значения будут либо строчными, либо булевыми, либо целочислен­ ными. При необходимости дополнительную информацию можно полу­ чить, используя метод pd.Data.GetType().

После того как мы получили список свойств данного контейнера, не­ обходимо проверить, содержит ли он дочерние контейнеры. Если да, про­ сматриваем каждый из них.

Для начала необходимо создать хотя бы первый контейнер и вызвать указанный метод. Объект контейнера содержит только один конструк­ тор, имеющий в качестве параметра одну логическую переменную. Это значение используется, чтобы определить, должна ли диагностика вклю­ чать в себя информацию об аппаратных средствах WHQL (Windows Hardware Quality Labs).

Получение этой информации может занять продолжительное время, лоэтому если в данной операции нет необходимости, ее лучше пропус­ тить. Образец DirectX SDK использует точку входа в программу, приве­ денную в листинге А.2.

Листинг А.2. Запуск приложения.

static void Main(string[] args) { try { // Just start our recursive loop with our root container. Don't worry // about checking Whql OutputDiagData(null, new Container(false));

} 388 Часть VII. Приложения catch { // Something bad happened } } Таким образом, сформировав опции и свойства диагностики в вашем приложении, можно обнаружить искомый сбой в работе программы.

Проверка отдельных пунктов Как известно, полное перечисление всех опций и свойств (как в DxDiag, так и в DirectX SDK) может занимать немало времени. Допустим, мы хотели бы получить информацию относительно небольшого числа пунк-.

тов.

Ниже приведен пример кода, позволяющего получить определенную информацию о версии DirectX, которая использовалась в вашей системе:

Container parent = new Container(false);

Container child = parent.GetContainer("DxDiag_SystemInfo").Container;

int dxVersionMajor = (int)child.GetProperty("dwDirectXVersionMajor").Data;

int dxVersionMinor = (int)child.GetPropertyCdwDirectXVersionMinor").Data;

string dxVersionLetter = (string)child.GetProperty("szDirectXVersionLetter").Data;

Console. WriteLinePDX Version:(01.(1)(21",dxVersionMaj or,dxVersionMinor,dxVersionLetter);

Мы создаем корневой (без информации WHQL) контейнер. Затем, после образования дочернего контейнера DxDiag Systemlnfo, получаем три различных свойства версии DirectX: главный и вспомогательный номера версии, а также символ, связанный с этой версией (известна, на­ пример, версия DX8.1B).

Префиксы в именах свойств могут определять заданный для объекта по умолчанию тип данных. Эти имена соответствуют Венгерскому при­ мечанию для разработчиков программ на С. Пункты с префиксом «sz» означают строки, пункты с префиксом «b» — логические значения. Все другие префиксы относятся к целочисленным значениям.

Использование пространства имен Diagnostics может оказать суще­ ственную помощь при наладке и проверке вашего приложения. А воз­ можность обратной связи с пользователями позволяет отследить работу приложения на самых разнообразных системах и видеокартах.

Как говорится, — «Лучше планировать проблему до, чем ждать и со­ жалеть после».

Приложение В. Воспроизведение музыки и видео Приложение В. Воспроизведение музыки и видео Как мы уже писали в начале книги, в окончательной версии Управляе­ мого DirectX SDK API мы исключили компоненты DirectMusic и DirectShow.

Вместо этого мы включили пространство имен AudioVideoPlayback, по­ зволяющее запускать видеоролики и музыкальные файлы (например, фай­ лы mp3s или wma). В этом приложении мы обсудим следующие вопросы.

• Простое воспроизведение звукового файла.

• Простое проигрывание видео файла.

Проигрывание видео файла в отдельном окне.

Использование видео файла в качестве текстуры в 3D приложении.

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

При создании пространства имен AudioVideoPlayback нашей главной задачей ставилась простота в использовании. В итоге, мы можем запус­ тить воспроизведение музыкального файла с помощью одной короткой строки, например, код для запуска звукового файла «piano.mp3» выгля­ дит следующим образом:

Audio someAudio = new Audio("piano.mp3", true);

Имеются два основных класса, которые включены в пространство имен AudioVideoPlayback: класс Video и класс Audio. Эти названия довольно очевидны. Конструктор для каждого класса имеет два варианта. Первый параметр для каждого из вариантов представляет собой имя файла, кото­ рый мы хотим запустить. Другой параметр содержит булево значение, которое определяет автоматическое воспроизведение этого файла, в на­ шем случае «true» (по умолчанию «false», то есть файл не будет проиг­ рываться без соответствующей команды). Таким образом, мы создали звуковой объект, который будет запускаться автоматически.

Конструкторы — не единственный способ создавать (или загружать) эти файлы. Наиболее близким является статический метод FromFile, ко­ торый принимает те же самые параметры и возвращает новый экземпляр класса. Имеется также статический метод FromUri, который ведет себя подобно методу FromFile, за исключением того, что он загружает данные с web-узла (или любого корректного URL). При этом воспроизведение при использовании URL может начаться еще до того, как файл загрузит­ ся полностью.

390 Часть VII. Приложения Существуют также методы Open и OpenUrl с теми же входными пара­ метрами. Они заменяют данные в уже созданном объекте на данные, по­ лученные из нового файла или web-узла.

Воспроизведение видео файла в отдельном окне В предыдущем примере для показа простоты работы мы использова­ ли звуковой файл. Допустим, мы хотим сделать то же самое с видео фай­ лом (например, вместо звукового файла запустить видео ролик «butterfly.mpg»).

Для этого достаточно использовать следующий код:

Video someVideo = new Video("butterfly.mpg", true);

Обратите внимание, что в этом случае воспроизведение будет осуще­ ствляться в новом окне.

Немного изменив код, мы можем воспроизводить ролик в уже имею­ щемся окне. Для этого необходимо отключить автоматическое воспроиз­ ведение (убрав значение «true» в первой строке) и добавить еще две до­ полнительные строки:

Video someVideo = new Video("butterfly.mpg");

someVideo.Owner = this;

someVideo.Play() ;

После отмены режима автоматического воспроизведения мы устанав­ ливаем «владельца» файла, который может быть любым объектом Windows-форм. Этим «владельцем» может быть непосредственно наша основная или ее дочерняя форма (например, picture box). После всех этих действий мы, наконец, запускаем видео. Обратите внимание, что теперь ролик прокручивается внутри окна, которое мы можем определить в свой­ ствах «владельца».

Мы можем управлять звуком воспроизводимого видео файла (если он содержит звуковые данные) с помощью свойства Audio, отвечающего за соответствующий нашему видео звуковой объект, с которым мы можем оперировать, как с обычным звуковым объектом.

Использование видео файла в качестве текстуры В этом разделе мы покажем, как можно использовать видео файл в качестве ЗО-текстуры в нашем ЗО-приложении. Многие разработчики Приложение В. Воспроизведение музыки и видео используют в своих играх различные ролики из кинофильмов в качестве фона или деталей игры, и пространство имен AudioVideoPlayback доста­ точно хорошо справляется с этой задачей.

В качестве наиболее простого случая возьмем фрагмент кинофильма и запустим его в полноэкранном режиме. Для начала просто загрузите видео файл в объект Video, установите опцию Fullscreen в значение «true» и запустите ролик. Пока все достаточно просто.

Рассмотрим более сложный сценарий с использованием объектов текстурирования. Начнем по порядку. У нас имеется событие TextureReadyToRender, которое мы хотели бы отслеживать. Данное со­ бытие будет зафиксировано каждый раз, когда в видео файле будет появляться новый кадр, который мы хотим отобразить в качестве тек­ стуры.

Параметр TextureRenderEventArgs процедуры обработчика события содержит вновь созданную текстуру, которая готова для отображения.

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

Теперь все, что необходимо для запуска игры, — это вызвать для ви­ део объекта метод RenderToTexture.

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

Вначале мы должны выяснить, где создана эта видео текстура: в сис­ темном пуле памяти или в заданном по умолчанию пуле. Это можно сде­ лать, проверив описание первого уровня текстуры.

Если текстура была создана в заданном по умолчанию пуле памяти (наиболее общий случай), для создания текстуры в устройстве DirectSD необходимо использовать метод GetRenderTargetData и поверхность, со­ зданную нами в системном пуле памяти. Поскольку мы не можем ото­ бражать текстуры, созданные в системном пуле памяти, необходимо со­ здать используемую для рендеринга текстуру в заданном по умолчанию пуле памяти, например:

SurfaceDescription ds = е.Texture.GetLeyelDescription(O);

if (ds.Pool == Pool.Default) { systemSurface = device.CreateOffscreenPlainSurfacefds.Width, ds.Height, ds.Format, Pool.SystemMemory);

} 392 Часть VII. Приложения texture = new Texture(device, ds.Width, ds.Height, 1, Usage.Dynamic, ds.Format, Pool.Default);

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

using(Surface videoSurface = e.Texture.GetSurfaceLevel(O)) { using(Surface textureSurface = texture.GetSurfaceLevel(O)) { device.GetRenderTargetData(videoSurface, systemSurface);

device.UpdateSurface(systemSurface, textureSurface);

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

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

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

Описанные классы эффективны и удобны, но при этом они не могут рассматриваться как полноценная альтернатива DirectShow API.

На данный момент мы не имеем каких-либо запланированных обнов­ лений для этого пространства имен Управляемого DirectX.

Pages:     | 1 |   ...   | 4 | 5 ||



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.