netlib.narod.ru< Назад | Оглавление | Далее >

Концепции игрового программирования в XNA

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

Если вы еще не сделали этого, загрузите и установите последнюю версию XNA Game Studio и Visual C# Express Edition с сайта http://www.microsoft.com/XNA. Если у вас уже есть Visual Studio 2005, XNA Game Studio будет прекрасно работать и с ней. Примеры из этой книги работают в обоих средах программирования.

Как только все будет на своих местах, запустите Visual C# и выберите в меню File пункт New Project. Вы увидите диалоговое окно, показанное на рис. 1.1.


Рис. 1.1. Создание нового проекта Windows Game (2.0) в Visual C# Express Edition

Рис. 1.1. Создание нового проекта Windows Game (2.0) в Visual C# Express Edition


В этом диалоговом окне щелкните по типу проекта Windows Game (2.0), а затем щелкните OK для создания нового игрового проекта, названного WindowsGame1. Обратите внимание, что версия 2.0 включает дополнительные типы проектов в диалоговом окне New Project, так что всегда проверяйте, что выбрали версию 2.0 в шаблонах проектов.

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

Когда проект будет создан, щелкните по значку Start Debug на панели инструментов или нажмите клавишу F5, чтобы запустить только что созданную игру. Хотя прямо сейчас в ней нет ничего впечатляющего — только синий экран — вы видите, что в этой программе есть все основы, необходимые, чтобы начать кодировать игру.

Закройте окно игры и проанализируйте созданные для вас файлы. Недавно созданные файлы показывает Solution Explorer (рис. 1.2).


Рис. 1.2. Solution Explorer для проекта Windows Game

Рис. 1.2. Solution Explorer для проекта Windows Game


Помимо значка и уменьшенного изображения на рис. 1.2 вы видите два созданных для вас файла с кодом: Program.cs и Game1.cs. Чтобы лучше понять, для чего предназначены эти файлы, вам надо изучить базовую концепцию игрового программирования: игровой цикл.

Общая структура игры

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

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

Приведенный ниже псевдокод показывает упрощенную структуру игры, включая игровой цикл:

Инициализация графики, ввода и звука
Загрузка ресурсов
Запуск игрового цикла. На каждом шаге:
    Сбор пользовательского ввода
    Выполнение необходимых вычислений
    (AI, перемещения, обнаружение столкновений и т.д.)
    Проверка критериев завершения игры - если достигли,
    останавливаем цикл
    Рисование (визуализация) экрана, генерирование звука
    и обратная связь с игровыми контроллерами
Завершение графики, ввода и звука
Освобождение ресурсов

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

До XNA эту структуру игры приходилось кодировать с нуля, так что вам приходилось бороться с множеством деталей, которые не были напрямую связаны с вашей игрой. XNA скрывает от вас большую часть этих сложностей. Когда вы создаете новый проект Windows Game, два создаваемых файла охватывают создание объекта класса Microsoft.Xna.Framework.Game (объект Game1), предоставление кода с важными методами этого класса, которые вам надо переопределить, и вызов метода Run, запускающего игровой цикл.

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

Game1()       - Общая инициализация (Game1.cs)
Initialize()  - Инициализация игры (Game1.cs)
LoadContent() - Загрузка графических ресурсов (Game1.cs)
Run() - Запуск игрового цикла (Program.cs). На каждом шаге:
    Update() - Чтение пользовательского ввода, выполнение вычислений,
               проверка завершения игры (Game1.cs)
    Draw() - Код визуализации (Game1.cs)
UnloadContent() - Освобождение графических ресурсов (Game1.cs)

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

Давайте посмотрим детали для каждого файла проекта.

Открыв файл Program.cs вы увидите всего десять строк кода (не считая инструкций using), показанные в следующем кодовом фрагменте:

static class Program
{
    static void Main(string[] args)
    {
        using (Game1 game = new Game1())
        {
            game.Run();
        }
    }
}

Этот фрагмент кода включает класс Program, в котором находится точка входа XNA-приложений — функция Main. В функции только две строки: одна для создания объекта game из класса Game1 и другая для вызова метода Run этого объекта, который, как вы уже знаете, запускает игровой цикл.

Обратите внимание на создание объекта в инструкции using, что обеспечивает автоматическое освобождение ресурсов по завершении инструкции. Другой заслуживающий внимания момент — аргумент args функции Main, получающий параметры командной строки, использованные при вызове игры. Если вы желаете использовать аргументы командной строки в вашей игре — например, для ввода специальных кодов, помогающих в процессе тестирования, — вам надо будет иметь дело с этим аргументом.

Класс Game1 реализован в файле Game1.cs. Быстрый взгляд на класс Game1 в этом файле показывает, что он наследуется от класса Microsoft.Xna.Framework.Game, предоставляемого XNA базового класса, который инкапсулирует создание окна, инициализацию графики, ввода и звука, и базовую логику игры, о которой мы уже говорили.

Давайте откроем файл и исследуем его детали в следующих разделах.

Инициализация игры

Класс Game1 начинается с определения и создания объекта, который будет ссылаться на диспетчер графических устройств, в игровом мире часто упоминаемого как устройство (device), и объекта SpriteBatch, используемого для рисования текста и двухмерных изображений. Конструктор класса Game1 также конфигурирует корневой каталог для диспетчера содержимого, который является точкой входа для конвейера содержимого XNA, чтобы XNA Framework был проинформирован, где искать игровое содержимое (графику, звуки, трехмерные модели, шрифты и т.д.). Следующий фрагмент кода представляет инициализацию устройства и диспетчера содержимого:

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

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

Диспетчер графических устройств

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

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

Диспетчер конвейера содержимого

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

В не-XNA играх вы должны были беспокоиться о том, как загружать игровое содержимое, такое как звуки, графика и трехмерные модели. Где находится содержимое? Как ваша программа должна читать это содержимое? Есть ли у вас правильные библиотеки, чтобы прочитать содержимое в том формате, в котором оно было создано посредством одной из используемых вами коммерческих утилит?

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


Рис. 1.3. Конвейер содержимого XNA

Рис. 1.3. Конвейер содержимого XNA


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

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

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

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

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

Методы инициализации игры в классе XNA Game

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

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

Метод Initialize вызывается один раз, когда вы выполняете метод Run (описанный в начале раздела), прямо перед запуском игрового цикла. Это подходящее место для включения любых не относящихся к графике процедур инициализации, таких как подготовка звукового содержимого.

Этот метод также включает вызов его базового метода, который перебирает элементы коллекции GameComponents и вызывает метод Initialize каждого из них. Это значит, что создавая более сложные игры, вы можете создавать игровые компоненты, которые класс Game также будет вызывать. Сейчас не надо волноваться об этих деталях: мы вернемся к ним, когда будем создавать наши игры.

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

Завершение игры

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

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

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

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

Игровой цикл

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

Класс Microsoft.Xna.Framework.Game предоставляет два переопределяемых метода, которые вызывает внутренний игровой цикл: Update, куда вы должны поместить игровые вычисления, и Draw, где вы рисуете игровые объекты. Давайте пристальнее взглянем на эти методы, представленные в следующем фрагменте кода, чтобы осветить некоторые существенные детали:

protected override void Update(GameTime gameTime)
{
    // Позволяет выйти из игры
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
             ButtonState.Pressed)
        this.Exit();

    base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    base.Draw(gameTime);
}

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

Другая, заслуживающая упоминания деталь метода Update в показанном выше коде, — включение предопределенного кода для завершения игры при нажатии кнопки Back на игровом пульте Xbox 360:

if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
          ButtonState.Pressed)
    this.Exit();

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

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

Метод Draw включает строку для очистки графического устройства, заполняющую игровое окно единым цветом CornflowerBlue:

graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

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


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz