Единое окно доступа к образовательным ресурсам

Введение в XNA

Голосов: 5

Книга адресована студентам и начинающим разработчикам, которые хотят использовать в своих проектах высокопроизводительную графику. Книга рассчитана на читателей, уже знакомых с основами C# и платформы .NET. Первая глава знакомит читателя с XNA Framework. Вторая глава посвящена визуализации базовых примитивов XNA Framework. В третьей главе рассматриваются более сложные вопросы визуализации: использование полноэкранного режима, плавная анимация примитивов и имитация прозрачности. В четвертой главе весь ранее изученный материал сводится воедино на примере создания полноценного хранителя экрана с дистрибутивом, при необходимости автоматически инсталлирующим на компьютер пользователя XNA Framework. В пятой, заключительной главе затрагивается тема программируемого графического конвейера, но уже на значительно более глубоком уровне; рассматриваются язык HLSL, основы ассемблеро-подобного языка Vertex Shader 1.1 и интегрированная среда разработки FX Composer 2.0, значительно облегчающая разработку и отладку шейдеров. Данная книга входит в состав <a target=_blank href="http://www.microsoft.com/Rus/Msdnaa/Curricula/">"Библиотеки учебных курсов"</a>, формирование которой ведется в рамках программы академического сотрудничества <a target=_blank href="http://www.microsoft.com/rus/msdnaa/">MSDN Academic Alliance (MSDN AA)</a>.

Приведенный ниже текст получен путем автоматического извлечения из оригинального PDF-документа и предназначен для предварительного просмотра.
Изображения (картинки, формулы, графики) отсутствуют.
    параметров передаваемых классам Direct3D Graphics и правильности выполнения
этих методов. Информация об различных подозрительных ситуациях и ошибках
передаѐтся в отладчик (например, в отладчик Visual Studio 2005).
При разработке и отладке приложений рекомендуется всегда использовать отладочную версию Direct3D
Graphics. Так же полезно установить ползунок Debug Output Level, отвечающий за подробность
отладочной информации в крайнее правое положение, чтобы получать информацию о любых
подозрительных ситуациях. Ведь согласно “эффекту бабочки”, даже самый безобидный на первый взгляд
недочѐт может привести к каскаду трудноуловимых ошибок.
В группе Debugging желательно включить следующие флажки: Maximum Validation (максимальная
проверка корректности параметров, передаваемых классам Direct3D Graphics), Enable Shader Debugging
(отладка шейдеров) и Break on Memory Leaks (обнаружение утечек памяти).
       В ни ма н ие.
       Отладочная (Debug) версия DirectX Graphics значительно медленнее обычной (Retail) версии. Поэтому не
       забывайте отключать отладочную версию DirectX по завершению отладки. В противном случае вы рискуете
       столкнуться с аномально низкой производительностью трѐхмерных игр и аналогичных приложений. Для того
       чтобы переключиться в нормальный режим, достаточно просто включить радиокнопку Use Retail Version of
       DirectX – остальные опции вроде Debug Output Level не оказывают никакого влияния на обычную версию
       Direct3D Graphics.

Debug View
По умолчанию Visual Studio 2005 Pro не отображает сообщения от отладочной версии DirectX, а в
бесплатной версии Visual C# 2005 Express подобная функциональность не предусмотрена в принципе.
Поэтому я включил на CD диск с книгой бесплатную программу Марка Руссиновича Debug View,
расположенную в каталоге \Tools\DebugView. Скопируйте еѐ на локальный жесткий диск компьютера и
запустите файл DebugView.exe. На экране появится окно следующего вида (рисунок 1.9). Наибольший
интерес для нас представляет центральная часть окна, в которой отображают отладочные сообщения от
всех приложений, выполняющихся в данный момент на компьютере. Если вы поработаете некоторое время
на компьютере при запущенной утилите Debug View, то наверняка заметите множество отладочных
сообщений от разнообразных приложений.




Рисунок 1.9. Приложение Debug View

Откройте панель управления DirectX, и включите отладочную версию DirectX. Запустите на выполнение
приложение, рисующую шахматную доску (пример Ch01\Ex02), поработайте с ним некоторое время, после
чего завершите. Тем временем в окне Debug Info появится информация следующего вида:

// Библиотека Direct3D загружается в адресное пространство нашего приложения
Direct3D9: :====> ENTER: DLLMAIN(041dd6e0): Process Attach: 0000041c, tid=000016a8
Direct3D9: :====> EXIT: DLLMAIN(041dd6e0): Process Attach: 0000041c


// Direct3D находится в отладочном режиме
Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.


// Расширенные возможности отладки Direct3D недоступны (эта функциональность доступна
// только для DirectX-приложений, написанных на C++)
D3D9 Helper: Enhanced        D3DDebugging      disabled;    Application      was   not   compiled     with
D3D_DEBUG_INFO
// Сообщение с пометкой INFO содержат разнообразную служебную информацию о ходе
// выполнения приложения. В частности следующее сообщение означает, что устройство
// находится в режиме Software Vertex Processing (Программная обработка вершин). Иными
// словами, при создании графического устройства был использован флаг
// CreateOptions.SoftwareVertexProcessing.
Direct3D9: (INFO) :======================= Hal SWVP device selected
Direct3D9: (INFO) :HalDevice Driver Style 9
Direct3D9: :DoneExclusiveMode
Direct3D9: :====> ENTER: DLLMAIN(041dd6e0): Process Detach 0000041c, tid=0000022c


// Освобождение ресурсов Direct3D завершено
Direct3D9: (INFO) :MemFini!


// Завершение работы Direct3D
Direct3D9: :====> EXIT: DLLMAIN(041dd6e0): Process Detach 0000041c


     Обр а т ит е вн има н ие
     В столбце Time указано время поступления отладочного сообщения, что облегчает идентификацию сообщений.
     По умолчанию используется относительное время – за точку отсчѐта берется время поступления первого
     события, т.е. время наступление первого события всегда равно 0.0 секунд.

Как видно, приложение выполняется без каких-либо эксцессов. Теперь закомментируйте строку
device.Dispose() в обработчике события Close() и снова запустите приложение на выполнение. На этот
раз отладочные сообщения будут несколько отличаться:
Direct3D9: :====> ENTER: DLLMAIN(042dd6e0): Process Attach: 00000298, tid=00000910
Direct3D9: :====> EXIT: DLLMAIN(042dd6e0): Process Attach: 00000298
Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.


D3D9 Helper: Enhanced        D3DDebugging      disabled;    Application      was   not   compiled    with
D3D_DEBUG_INFO
Direct3D9: (INFO) :======================= Hal SWVP device selected


Direct3D9: (INFO) :HalDevice Driver Style 9
Direct3D9: :DoneExclusiveMode


// Предупреждение. К устройству, не рассчитанному на работу в многопоточном режиме (в
// конструкторе класса GraphicsDevice указан флаг CreateOptions.SingleThreaded)
// пытается обратиться другой поток.
Direct3D9: (WARN) :Device that was created without D3DCREATE_MULTITHREADED is being
used by a thread other than the creation thread.


// Ошибка! Устройство может быть уничтожено только потоком, создавшим его
Direct3D9: (ERROR) :Final Release for a device can only be called from the thread that
the device was created from.


Direct3D9: (WARN) :Device that was created without D3DCREATE_MULTITHREADED is being
used by a thread other than the creation thread.


Direct3D9: (WARN) :Device that was created without D3DCREATE_MULTITHREADED is being
used by a thread other than the creation thread.


Direct3D9: (WARN) :Device that was created without D3DCREATE_MULTITHREADED is being
used by a thread other than the creation thread.


Direct3D9: (WARN) :Device that was created without D3DCREATE_MULTITHREADED is being
used by a thread other than the creation thread.


Direct3D9: :====> ENTER: DLLMAIN(042dd6e0): Process Detach 00000298, tid=00000520
Direct3D9: (INFO) :MemFini!
Direct3D9: :====> EXIT: DLLMAIN(042dd6e0): Process Detach 00000298
Хотя приложение и работает нормально, по отладочным сообщениям можно легко догадаться, что при
завершении работы приложения сборщик мусора попытался уничтожить устройство, что естественно не
удалось – ведь сборщик мусора вызывает методы Finalize в отдельном потоке, в то время как устройство
Direct3D может удалить лишь тот поток, который его создал. Таким образом, отладочная версия DirectX
помогла нам легко локализовать проблему.
Единственное неудобство доставляют отличия между Direct3D и XNA Framework. К примеру, устройство
Direct3D по умолчанию запускается в однопоточном режиме, а включение поддержки многопоточного
режима осуществляется путем указания флага D3DCREATE_MULTITHREADED. А вот класс GraphicsDevice,
напротив, по умолчанию создаѐт графическое устройство с поддержкой многопоточности, а отключение
данной функциональности осуществляется путем указания флага CreateOptions.SingleThreaded. Кроме
того, отладочная версия Direct3D нечего не знает о .NET Framework – вместо того, чтобы сообщить о
проблемах из-за удаления объекта GraphicsDevice сборщиком мусора она просто жалуется на странное
поведение непонятно откуда взявшегося дополнительного потока. Впрочем, получив некоторый опыт
чтения сообщений отладочной версии Direct3D, вы перестанете обращать внимание на подобные нюансы.


Взаимодействие XNA Framework c DirectX.

Как говорилось выше, XNA Framework по сути является прослойкой между .NET и DirectX. Но насколько
эта тонка прослойка и оказывает ли она существенное влияние на производительность приложения? Чтобы
ответить на этот вопрос мы рассмотрим работу XNA Framework на примере метода
Direct3D.Device.Present, декомпилировав его с использованием .NET Reflector, который находится на
CD с книгой в каталоге Tools\NET Reflector14:

public unsafe void Present()
{
// Проверяет, не был ли вызван метод Dispose для данного экземпляра класса
// GraphicsDevice. Если метод Dispose уже был вызван, генерируется исключение
// ObjectDisposedException
      InternalHelper.CheckDisposed(this, (void*) this.pComPtr);
// Получает указатель на COM–интерфейс IDirect3DDevice9, являющийся низкоуровневым
// аналогом класса GraphicsDevice.
      IDirect3DDevice9* devicePtr1 = this.pComPtr;
// Вызывает COM-метод IDirect3DDevice9::Present, который выполняет переключение
// буферов и вывод изображения на экран. К сожалению, .NET Reflector сгенерировал,
// мягко говоря, не самый красивый код.
      int num1 = **(((int*) devicePtr1))[0x44](devicePtr1, 0, 0, 0, 0);

// Если метод IDirect3DDevice9::Present возвратил отрицательное значение, то есть во
// время выполнения метода произошла ошибка (в COM отрицательные значения
// соответствуют кодам ошибок).
      if (num1 < 0)
      {
// Если код ошибки равен -2005530520 (соответствует потери устройства)
            if (num1 == -2005530520)
            {
// Подготовка к вызову обработчика события DeviceLost (если он определен) с
// последующим вызовом. Тема потери устройства будет рассмотрена в разделе 1.2.4.
                  EventArgs args1 = EventArgs.Empty;

14
  Конечно, C#-код, генерируемый .NET Reflector, далѐк от идеала. Тем не менее, его код несоизмеримо
проще анализировать по сравнению с ассемблерным IL-кодом.


                         EventHandler handler1 = this.<backing_store>DeviceLost;
                         if (handler1 != null)
                         {
                               handler1(this, args1);
                         }
            }
// Генерируется исключение. Класс исключения и текст сообщения об ошибке определяется
// кодом, который вернул Com-метод IDirect3DDevice9::Present
            throw ExceptionHelper.GetExceptionFromResult(num1);
      }
}


Как видно, метод GraphicsDevice.Present содержит вызов COM-метода
IDirect3DDevice9::Present плюс небольшую обвязку для взаимодействия с COM. Иными
словами, на платформе Windows метод GraphicsDevice.Present по сути является
обвязкой над методом . Впрочем, на других платформах всѐ может быть
совершенно иначе.


1.2.2. Конфигурирование проектов в Visual Studio 2005
В этом разделе мы рассмотрим тонкости настройки свойств проекта в среде Visual Studio 2005 с учѐтом
специфики приложений, использующих XNA Framework. Если вы имеете большой опыт работы с Visual
Studio, можете пропустить этот раздел.
Как вы знаете, самой крупной единицей Visual Studio 2005 является решение (Solution), описание которого
хранятся в текстовом файле формата XML с расширением .sln. Каждое решение состоит из одного или
нескольких проектов: набора файлов исходного кода и ресурсов, которые будут откомпилированы в одну
сборку (исполняемый файл .exe или динамическую библиотеку .dll). Файлы с описанием проектов,
использующих язык C#, имеют расширение .csproj. Файлы исходного кода C#-программы имеют
расширение .cs, файлов ресурсов – .resx и т.д. В принципе для получения представления о структуре
решения достаточно открыть любой проект и взглянуть на вкладку Solution Explorer (рисунок 1.4).
      П р им еч а н ие
      Возможность хранить несколько проектов внутри одного решения очень полезна при разработке сложных
      приложений. К примеру, вы можете держать в одном решении 4 проекта: библиотека моделирования
      искусственного интеллекта, редактор уровней, собственно игра и еѐ инсталлятор. Эта возможность будет
      довольно активно использоваться в ряде примеров книги.

Проект может иметь несколько конфигураций, позволяющих быстро переключаться между различными
настройками проекта. При создании нового проекта Visual Studio 2005 добавляет в него две конфигурации:
Debug и Release.
 Конфигурация Debug предназначена для отладки приложения. При использовании этой конфигурации
  приложение компилируется без использования оптимизаций, а в .exe файл добавляются отладочные
  символы. Благодаря этому отладчик может найти однозначное соответствие между полученным
  двоичным ком и исходным текстом программы, что позволяет осуществлять пошаговое выполнение
  программы, просмотр промежуточных значений переменных и т.п. Кроме того, программист, используя
  директивы условной компиляции, может добавлять в отладочную версию код ряд разнообразных
  дополнительных расширенных проверок вводимых данных.
 Конфигурация Release применяется для построения финальной версии приложения. В этом случае
  компилятор применяет различные оптимизации: удаление лишних переменных, перестановка
  инструкций, разворачивание циклов и т.п. Это значительно повышает производительность приложения
  (иногда в десятки раз), однако значительно усложняет отладку, которая возможна лишь на уровне
  машинного кода.
Переключение между этими конфигурациями легко осуществляется с использованием выпадающего списка
на панели инструментов Visual Studio (рисунок 1.10). Вообще, конфигурации Debug и Release отличаются
между собой лишь настройками различных свойств. Теоретически, проигравшись со свойствами
конфигурации Debug, вы можете легко превратить еѐ в функциональный аналог конфигурации Release и
наоборот. Кроме того, вы можете добавить в решение некоторые специфические конфигурации вроде
“Release Shareware Version”, “Release Full Version” и т.д.


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

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

Конфигурация Release в проектах C# по умолчанию настроена довольно оптимально и вам вряд ли
придѐтся еѐ менять. К сожалению, этого нельзя сказать о конфигурации Debug. Сейчас мы с вами это
исправим. Активируйте конфигурацию Debug при помощи выпадающего списка на панели задач. Щелкните
два раза на узле Properties во вкладке Solution, чтобы открыть вкладку свойств проекта 15. (рисунок 1.11).




Рисунок 1.11. Свойства проекта

Для начала щѐлкните на закладку Debug и включите флажок Enable unmanaged code debugging. Из
названия нетрудно догадаться, что этот флажок включает отладку неуправляемого кода. Зачем это надо?
Большинство классов сборки XNA Framework являются тонкими надстройками над COM-компонентами
“обычного” DirectX16. В результате при выключенной отладке неуправляемого кода Visual Studio 2005 не
может получать информацию от COM-компонентов отладочной версии DirectX и, соответственно, выводить
еѐ в окно Output (см. раздел 1.1).




15
     Эта вкладка, по сути, является визуальным редактором файла проекта (*.cjproj и т.п.)
16
     Данное утверждение может быть не верным для платформ, отличных от Windows.


Попробуйте включить эту опцию в примере Ch01\Ex02 (визуализация шахматной доски) и понаблюдать в
окне Output за сообщениями отладочной версии DirectX (рисунок 1.12). Отметьте ощутимо возросшее время
загрузки приложения.




Рисунок 1.12. Окно Output.

В целом, окно Output в сочетании с опцией Enable unmanaged code Debugging является неплохой
интегрированной альтернативой утилите Debug View (см. раздел 1.1.1), хотя и не лишенной ряда
недостатков – очень низкой производительности и отсутствия поддержки в Visual C# 2005 Express.
       В ни ма н ие
       Даже если вы выбрали конфигурацию Release, Visual Studio при нажатии клавиши F5 (Start with Debugging) всѐ
       равно запускает .NET-приложение под управлением отладчика, что ощутимо снижает быстродействия. Для
       полного отключения отладки необходимо запустить приложение на выполнение комбинацией клавиш Ctrl + F5
       (Start without debugging).

Проверка переполнения
Вторая полезная опция, Check for arithmetic overflow/underflow, находится в диалоговом окне
Advanced Build Setting, открываемом при помощи кнопки Advanced Build Setting в закладке Build
(рисунок 1.13). Этот флажок включает проверку целочисленного переполнения для всей программы: если в
ходе вычислений результат вдруг выйдет за пределы допустимого диапазона, то программа автоматически
сгенерирует исключение System.OverflowException. Эта поможет сэкономить вам много времени и
нервов, при поиске трудноуловимых ошибок вроде:
// Объявляем 16-битную переменную со знаком и присваиваем ей 32767
short a = 32767;
// Увеличиваем еѐ на 5. Из за переполнения переменная a станет равна -32764, а не 32752 !!!
a = (short) (a + 5);
// В итоге оператор WriteLine выведет на экран -32764
Console.WriteLine(a);




Рисунок 1.13. Диалоговое окно Advanced Build Setting

Так как проверка переполнения несколько снижает производительность программы, еѐ рекомендуется
выключать в конфигурации Release. Если же у вас имеется потенциально опасный код, в котором может
произойти переполнение, поместите его во внутрь блока checked. Например:


checked
{
   a = (short) (a + 5);
}
Остальные особенности конфигурирования проектов C# мы рассмотрим в следующих разделах по мере
необходимости. Более подробную информацию по этой теме можно найти в [К.7], [К.8] и [К.9].



1.2.3. Изменение размеров окна
Запустите на выполнение нашу программу, рисующую шахматную доску (Ex02) и попробуйте поизменять
размеры окна. Думаю, вы быстро заметите, что с программой что-то не так. При уменьшении размеров окна
шахматная доска не масштабируется, в результате чего изображение начинает выглядеть как-то странновато
(рисунок 1.13). А при увеличении окна изображение шахматной доски искажается непонятным образом
(рисунок 1.14).
Как гласит народная мудрость, за двумя зайцами погонишься – ни одного не поймаешь. Поэтому для начала
мы сосредоточимся на первой проблеме – неизменном размере шахматной доски при уменьшении формы.
Эта проблема вызвана тем, что по умолчанию Windows Forms не гарантирует вызов события Paint при
изменении размеров формы. В принципе, для борьбы с этим недоразумением мы могли бы добавить в
обработчик события Resize17 вызов метода Invalidate, генерирующего событие Paint.




Рисунок 1.13. Некорректная реакция программы на уменьшение размера окна.




Рисунок 1.14. Некорректная реакция программы на увеличение размера окна

Однако существует гораздо более элегантное решение: если установить у формы стиль ResizeRedraw, то
при изменении размера формы будет автоматически генерироваться событие Paint. Для этого добавьте в
обработчик Load строку:
SetStyle(ControlStyles.ResizeRedraw, true);
Попробуйте ещѐ раз запустить программу на выполнение. И что мы видим? Хотя теперь приложение и
реагирует на изменение размера окна, появились новая, гораздо более неприятная проблема – при
изменении размеров окна форма непрерывно мерцает. Чтобы понять причину этого явления попробуйте
изменить цвет формы на зелѐный (свойство BackColor) и снова запустите на выполнение. Мерцания
шахматной доски обретут зеленоватый оттенок.



17
     Событие Resize генерируется при изменении размеров формы


И так всѐ дело в том, что перед вызовом обработчика события Paint класс Form вызывает виртуальный
метод OnPaintBackground, который по умолчанию очищает экран цветом BackColor. Эта
функциональность позволяет разработчику, использующему GDI+, не заботится об очистке экрана, однако в
нашем случае такая “самовольная” очистка формы приводит лишь к мерцанию. Следовательно, нам
необходимо каким-нибудь образом запретить форме закрашивать экран перед вызовом обработчика
события Paint. Первое, что приходит в голову – перегрузить метод OnPaintBackground
protected override void OnPaintBackground(PaintEventArgs pevent)
{
 // Ничего не делаем
}
Впрочем, если покопаться в документации, можно найти и более изящное решение проблемы. Если
установить у формы стиль ControlStyles.Opaque, то форма не будет автоматически закрашивать фон, что
собственно нам и нужно:
SetStyle(ControlStyles.Opaque, true);
После добавления этой строки в обработчик события Load мерцания наконец-то исчезнут.
И так, первую проблему мы решили, но осталась вторая, гораздо более неприятная – некорректное
масштабирование шахматной доски при изменении размера окна. На первый взгляд проблема возникает
буквально на пустом месте – мы задаѐм параметры двойной буферизации и вспомогательных буферов,
создаѐм новое устройство, после чего перерисовываем экран в обработчике события Paint. Вроде бы ничего
противозаконного мы не делаем… Стоп! При создании графического устройства мы задаѐм размер
вспомогательного буфера, используемого при двойной буферизации, равный размеру клиентской области
окна, что вполне логично. Когда мы изменяем размер окна, его клиентская область так же изменяется. А вот
размер вспомогательного буфера остаѐтся неизменным – мы ведь задаѐм его размер только один раз в
обработчике события Load. Получается что, при изменении размеров окна происходит рассинхронизация
между размером клиентской области окна и вспомогательного буфера, в котором собственно рисуется
изображение, после чего приложение естественно начинает работать некорректно.
Следовательно, нам необходимо добавить в программу коррекцию размера вспомогательного буфера при
изменении размера окна. В XNA Framework эта операция выполняется с использованием метода Reset
класса GraphicsDevice:
public void Reset(params PresentationParameters[] presentationParameters);
где
     presentationParameters       –   набор структур PresentationParameters, описывающих новое
      представление     данных    на    экране. Каждому монитору соответствует своя структура
      PresentationParameters.
И так, нам придѐтся вынести локальную переменную PresentationParameters presentParams за
пределы метода Load (то есть сделать еѐ полем класса MainForm) и добавить в обработчик события Resize
изменение высоты и ширины вспомогательного буфера структуры presentParams с последующим вызовом
метода Device.Reset (листинг 1.5).

    Листинг 1.5.

private void MainForm_Resize(object sender, EventArgs e)
{
// Задаѐм новые размеры буфера глубины
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height;
// Применяем новые параметры к устройству
    device.Reset(presentParams);
}


         П р им еч а н ие
         Сброс устройства является очень медленной операция. Никогда не вставляйте еѐ без причины в обработчик
         события Paint, так как это приведѐт к заметному падению производительности.

После такой модернизации наша программа наконец-то заработала без ошибок. Хотя так ли это? Как гласит
известная пословица, в каждой программе есть как минимум одна ошибка. Наша программа не исключение.
Попробуйте из интереса уменьшить еѐ размер до минимума. Как только высота клиентской области окна
станет меньше одного пикселя, в окне Output появятся сообщения от отладочной версии DirectX, а
программа аварийно завершится с исключением:


Direct3D9: (ERROR) :Failed to create driver surface
Direct3D9: (ERROR) :Reset failed and Reset/TestCooperativeLevel/Release are the only legal
APIs to be called subsequently
A first chance exception of type 'Microsoft.DirectX.Direct3D.DriverInternalErrorException'
occurred in Microsoft.DirectX.Direct3D.dll
Поведение Direct3D вполне логично, ведь попытка вывести изображение на форму с клиентской областью
размером менее одного пикселя выглядит, мягко говоря, довольно странной. Однако пользователя такое
оправдание вряд ли обрадует, поэтому неплохо бы обезопаситься от подобных казусов, ограничив
минимальный размер клиентской области одним пикселем. Это легко можно сделать при помощи свойства
MinimumSize, которое задаѐт минимальные размеры окна. Правда задание этого свойства во вкладке
Properties не самая лучшая идея – область, отводимая формой под клиентскую область, зависит от
множества факторов: установленной операционной системы, пользовательских настроек и т.п. Гораздо
надѐжнее вычислять его прямо в обработчике события Load посредством метода формы
SizeFromClientSize, который возвращает размер окна при заданном размере клиентской области:
// Вычисляем размер окна при клиентской области 1x1 пиксель. Полученное значение присваиваем
// свойству MinimumSize
MinimumSize = SizeFromClientSize(new Size(1, 1));
Ещѐ одной ошибкой стало меньше. Думаю, вы уже убедились, что написать приложение без единой ошибки
для такой сложной операционной системы, как Windows,не так уж и просто. Всегда существует вероятность
пропустить какой-нибудь нюанс. Например, мы до сих пор не пробовали минимизировать окно при помощи
соответствующей стандартной кнопки в правом верхнем углу окна. Попробуйте нажать эту кнопку, и в
обработчике события Resize тут же произойдѐт исключение – приложение попытается установить размер
заднего буфера равным 0 на 0 пикселей. Такое поведение программы обусловлено тем, что при
минимизации окна программы Windows уменьшает его размер до нуля пикселей. Следовательно, нам
необходимо вставить в обработчик события Resize проверку состояния окна – если окно минимизировано,
то программа не должна пытаться изменять размер заднего буфера:

 Листинг 1.6.

private void MainForm_Resize(object sender, EventArgs e)
{
// Если окно не минимизировано, то изменяем размер заднего буфера и сбрасываем устройство
    if (WindowState != FormWindowState.Minimized)
    {
        presentParams.BackBufferWidth = ClientSize.Width;
        presentParams.BackBufferHeight = ClientSize.Height;
        device.Reset(presentParams);
    }
}
Вот теперь наша программа похоже уже не содержит явных ошибок 18. Полный текст полученного
приложения приведѐн в листинге 1.7 (Ex03).

 Листинг 1.7.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;


using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


using Xna = Microsoft.Xna.Framework;
using XnaGraphics = Microsoft.Xna.Framework.Graphics;

18
     Некоторые ошибки всѐ же остались, и мы в этом убедимся в следующем разделе (1.2.4).


namespace GSP.XNA.Book.Ch01.Ex03
{
    public partial class MainForm : Form
    {
        GraphicsDevice device=null;
        PresentationParameters presentParams;


        public MainForm()
        {
            InitializeComponent();
        }


        private void MainForm_Load(object sender, EventArgs e)
        {
            SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
            MinimumSize = SizeFromClientSize(new Size(1, 1));


            presentParams = new PresentationParameters();
            presentParams.IsFullScreen = false;
            presentParams.BackBufferCount = 1;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.BackBufferWidth = ClientSize.Width;
            presentParams.BackBufferHeight = ClientSize.Height;


            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,
 this.Handle, CreateOptions.SoftwareVertexProcessing | CreateOptions.SingleThreaded,
 presentParams);
        }


        private void MainForm_Paint(object sender, PaintEventArgs e)
        {
            device.Clear(ClearOptions.Target, XnaGraphics.Color.WhiteSmoke, 0.0f, 0);


            Xna.Rectangle[] rects = new Xna.Rectangle[32];
            int k = 0;
            for (int j = 0; j < 8; j++)
                for (int i = j % 2; i < 8; i += 2)
                {
                    rects[k] = new Xna.Rectangle(i * ClientSize.Width / 8,
 j * ClientSize.Height / 8, ClientSize.Width / 8, ClientSize.Height / 8);
                    k++;
                }


            device.Clear(ClearOptions.Target, XnaGraphics.Color.Brown, 0.0f, 0, rects);


            device.Present();
        }


        private void MainForm_Resize(object sender, EventArgs e)
        {



    
Яндекс цитирования Яндекс.Метрика