четверг, 30 июня 2011 г.

XNA Game Studio и движок Xen. Первый проект


Xen – это популярный графический движок для XNA Game Studio. Движок уже достаточно взрослый и обладает рядом полезных функций, так имеет смысл рассмотреть некоторые из них.
Скачать сам движок и обучающие материалы можно по следующей ссылке:
В архиве находится решение Tutorial, которое содержит обучающие примеры и файл build.bat, который собирает движок и устанавливает нужные надстройки для Visual Studio.
В обучающем решении содержатся 29 уроков, мы рассмотрим некоторые из них.
Запускаем пример и видим окошко с возможностью выбора урока:

Для начала посмотрим на исходный код основного файла Program.cs. В нем содержится несколько интересных подходов.
Сначала просмотрим исходный код примеров и заметим, что классы с примерами не являются производными класса Game, как мы привыкли делать в XNA Game Studio.
Все классы примеров наследуют абстрактный класс Application, который определен в пространстве имен Xen.
Вернемся к классу Program. Его задача в том, чтобы найти все классы с уроками в проекте и отправить их в форму с выбором урока.
За эту функциональность отвечает метод FindTutorials
public static void FindTutorials(Dictionary<stringType> types)
{
         
//work around the xbox's lack of SortedList / SortedDictionary
          var items = new List<KeyValuePair<stringType>>();

          foreach (Type type in typeof(Program).Assembly.GetExportedTypes())
          {
#if XBOX360
                    //hack! :-)
                    if (type == typeof(Tutorials.XenMenu.XenMenuApp))
                               continue;
#endif

                    if (typeof(Xen.Application).IsAssignableFrom(type))
                    {
                               string name = type.FullName;
                               foreach (object attribute in type.GetCustomAttributes(typeof(DisplayNameAttribute), true))
                                         name = ((DisplayNameAttribute)attribute).Name;

                               items.Add(new KeyValuePair<stringType>(name, type));
                    }
          }

          items.Sort(SortItems);
          foreach (var item in items)
          {
                    types.Add(item.Key, item.Value);
          }
}
//helper code for the tutorial selection
private static int SortItems(KeyValuePair<stringType> a, KeyValuePair<stringType> b)
{
          return a.Key.CompareTo(b.Key);
}

Он ищет в классы, порожденные от Application, в текущей сборке и создает из них список.
Название урока в форме будет получаться при помощи атрибута DisplayNameAttribute:
public class DisplayNameAttribute : Attribute
{
          private string name;

          public string Name
          {
                    get { return name; }
                    set { name = value; }
          }
}
Например, вот как определен класс для первого примера:
//Starting simple...
//
//This is a basic implementation of the Application class.
//All it will do is draw to the screen, clearing it to blue.

[DisplayName(Name = "Tutorial 01: Application Class")]      
//ignore this attribute, it simply gives the tutorial a name
public class Tutorial : Application
{ … }

Метод Main просто создает форму, дожидается пока пользователь выберет какой-нибудь пример и запускает этот пример. Для этого у класса Application есть метод Run, также как и класса Game.
static void Main(string[] args)

{

#if XBOX360

          int selection = 0;

          while (true)

          {

                    Type tutorial = null;

                    using (var menu = new XenMenu.XenMenuApp(selection))

                    {

                               menu.Run();

 

                               if (menu.SelectedTutorial != null)

                               {

                                         selection = menu.SelectedIndex + 1;

                                         tutorial = menu.SelectedTutorial;

                               }

                    }

 

                    GC.Collect();

 

                    if (tutorial != null)

                    {

                               using (Application app = Activator.CreateInstance(tutorial) as Application)

                                         app.Run();

                    }

                    else

                               break;

          }

#else

          while (true)

          {

                    bool runInWinForms;

                    bool runInXnaGame;

 

                    Type type = TutorialChooser.ChooseTutorial(out runInWinForms, out runInXnaGame);

                    if (type == null)

                               break;

 

                    using (Application tutorial = Activator.CreateInstance(type) as Application)

                    {

                               tutorial.Run();

                    }

          }

#endif

}
Ну а сам метод TutorialChooser.ChooseTutorial определен следующим образом:
public partial class TutorialChooser : Form
{
          bool canceled = true;

          public string SelectedTutorial
          {
                    get { return (tutorialList.SelectedItem ?? "").ToString(); }
          }

          public bool Canceled
          {
                    get { return canceled; }
          }

          public TutorialChooser(IEnumerable<string> tutorialNames, string startName)
          {
                    Cursor.Show();

                    InitializeComponent();

                    int selectIndex = 0;
                    int i = 0;

                    foreach (string str in tutorialNames)
                    {
                               i++;
                               if (startName == str)
                                         selectIndex = i;

                               tutorialList.Items.Add(str);
                    }

                    if (tutorialList.Items.Count > 0)
                               tutorialList.SelectedIndex = Math.Min(tutorialList.Items.Count-1,selectIndex);
          }

          private void OK(object sender, EventArgs e)
          {
                    Cursor.Hide();

                    canceled = false;
                    Close();
          }

          private void Cancel(object sender, EventArgs e)
          {
                    Close();
          }

          public static Type ChooseTutorial(out bool useWinForms, out bool useXnaGame)
          {
                    useWinForms = false;
                    useXnaGame = false;

                    Dictionary<stringType> tutorials = new Dictionary<stringType>();
                    Program.FindTutorials(tutorials);

                    TutorialChooser chooser = new TutorialChooser(tutorials.Keys, chosenTutorial);
                    chooser.ShowDialog();

                    if (chooser.Canceled)
                               return null;

                    Type type;
                    tutorials.TryGetValue(chooser.SelectedTutorial, out type);

                    chosenTutorial = chooser.SelectedTutorial;
                    useWinForms = chooser.winformsHost.Checked;
                    useXnaGame = chooser.xnaGameHost.Checked;
                    return type;
          }
 

          static string chosenTutorial;
}

В нем мы получаем список всех уроков из метода Program.FindTutorials.
Для того чтобы убедиться в том, что это действительно так, давайте создадим собственный класс, который будет реализовывать Application и иметь атрибут DisplayNameAttribute:
[DisplayName(Name = "Мой пример добавления нового урока")]
public class MyTestTutorial : Application
{
    protected override void Frame(FrameState state)
    {
        throw new NotImplementedException();
    }

    protected override void Initialise()
    {
        throw new NotImplementedException();
    }

    protected override void Update(UpdateState state)
    {
        throw new NotImplementedException();
    }
}

Как и следовало ожидать, все работает правильно.

Теперь перейдем в папку Tutorial 1 и откроем файл Game class.cs.
В этом файле содержится простейший пример создания приложения. Тут мы научимся тому, как создать проект, практически идентичный пустому каркасу приложения в XNA Framework.
В классе Tutorial есть только одна переменная типа DrawTargetScreen. Она нужна для того, что рисовать что-либо на экране. Дальше мы увидим, что модель работы приложения Xen отличается от стандартной модели для XNA Framework. В частности для того, чтобы нарисовать что-нибудь на экране, нужно просто добавить этот элемент в переменную DrawTargetScreen. Это может показаться несколько более сложным, однако это является стандартной практикой для графических движков.
Первый метод, который есть в классе Tutorial – это метод Initialise. (Тут же мы замечаем следующее различие с классом Game. Там этом метод имел название Initialize :) )


//This method gets called just before the window is shown, and the device is created

protected override void Initialise()

{

          //all draw targets need a default camera.

          //create a 3D camera

          var camera = new Camera3D();

 

          //create the draw target.

          this.drawToScreen = new DrawTargetScreen(camera);

 

          //Set the screen clear colour to blue

          //(Draw targets have a built in ClearBuffer object)

          this.drawToScreen.ClearBuffer.ClearColour = Color.CornflowerBlue;

}
 
В Initialise создается переменная drawToScreen, стоит заметить, что для создания поверхности рисования (будем так ее называть) нужно сначала создать камеру. В Xen имеется несколько вариантов камер, а также можно создать свою камеру, реализовав интерфейс ICamera.
Тут же устанавливается цвет для очистки экрана, как обычно это CornflowerBlue.
Следующий метод – Update
//this is the default Update method.

//Update() is called 60 times per second, which is the same rate that player input

//is updated.

//Note: Player input and Updating is explained in more detail in Tutorial 13

protected override void Update(UpdateState state)

{

          //quit when the back button is pressed (this maps to escape on the PC)

          if (state.PlayerInput[PlayerIndex.One].InputState.Buttons.Back.OnPressed)

                    this.Shutdown();

}

Update вызывается 60 раз в секунду, как и Update в классе Game. Однако мы видим, что этот Update не получает в качестве параметра объект GameTime.
Дело в том, что тип UpdateState уже включает в себя свойства для получения игрового времени (свойства DeltaTimeSeconds и TotalTimeSeconds, которые, кстати, имеют тип float, а не double, соответственно, их не нужно постоянно приводить), более того UpdateState включает в себя свойства для получения состояния контроллеров, таких как мышь, клавиатура и т.д.
Пока отметим то, что в следующей строке нигде явно не указано, какой именно контроллер используется, а просто проверяется, нажал ли первый игрок на некоторую кнопку Back, которая для Windows совпадает с кнопкой Esc.
if (state.PlayerInput[PlayerIndex.One].InputState.Buttons.Back.OnPressed)
                this.Shutdown();
Метода Draw в Xen мы не увидим, однако тут есть аналогичный метод Frame:
//This is the main application frame drawing method. All high level drawing code should go in here.

//

//The 'FrameState' is a state or context object. It provides access to current information,

//such as the time and shader globals.

//

// Do not store a reference to a 'state' object, doing so (and using it elsewhere) can have

// unexpected side effects. All 'state' objects are designed to be used only within

// the method they were passed in to.

//

protected override void Frame(FrameState state)

{

          //perform the draw to the screen.

          drawToScreen.Draw(state);

 

          //at this point the screen has been drawn...

}

Тут  мы тоже не встречаем объекта GameTime. Опять же тип FrameState содержит свойства, необходимые для получения игрового времени, а также такие полезные показатели как количество кадров в секунду, номер текущего кадра, статистику по прошлому кадру и т.д.
Если запустить первый урок на выполнение, то мы увидим пустой экран:

Весь исходный код первого урока выглядит так:
using System;

using System.Collections.Generic;

using System.Text;

 

 

 

using Xen;

using Xen.Camera;

using Xen.Graphics;

 

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Graphics;

 

 

/*

 * Welcome to xen!

 * 

 * 

 * This tutorial shows how to implement the Application class.

 * (It's quite similar to the XNA Game class)

 * 

 * 

 * 

 * This sample demonstrates:

 * 

 * implementing the Application class

 * creating a draw target that draws to the screen

 * setting the clear colour of the draw target

 * 

 */

namespace Tutorials.Tutorial_01

{

          //Starting simple...

          //

          //This is a basic implementation of the Application class.

          //All it will do is draw to the screen, clearing it to blue.

 

          [DisplayName(Name = "Tutorial 01: Application Class")]       //ignore this attribute, it simply gives the tutorial a name

          public class Tutorial : Application

          {

                    //A DrawTarget is a class that performs all the logic needed to complete a draw operation to

                    //a surface (such as the screen or a render texture).

                    //

                    //Drawing in xen is very explicit, the call to Draw() will perform the entire draw operation.

                    //

                    //A DrawTargetScreen is a draw target that draws items directly to the screen.

                    //

                    //In this tutorial all that will happen is the DrawTarget will clear itself to blue

                    //(Most applications will only have one DrawTargetScreen)

                    private DrawTargetScreen drawToScreen;

 

 

                    //This method gets called just before the window is shown, and the device is created

                    protected override void Initialise()

                    {

                               //all draw targets need a default camera.

                               //create a 3D camera

                               var camera = new Camera3D();

 

                               //create the draw target.

                               this.drawToScreen = new DrawTargetScreen(camera);

 

                               //Set the screen clear colour to blue

                               //(Draw targets have a built in ClearBuffer object)

                               this.drawToScreen.ClearBuffer.ClearColour = Color.CornflowerBlue;

                    }

 

                    //this is the default Update method.

                    //Update() is called 60 times per second, which is the same rate that player input

                    //is updated.

                    //Note: Player input and Updating is explained in more detail in Tutorial 13

                    protected override void Update(UpdateState state)

                    {

                               //quit when the back button is pressed (this maps to escape on the PC)

                               if (state.PlayerInput[PlayerIndex.One].InputState.Buttons.Back.OnPressed)

                                         this.Shutdown();

                    }

 

                    //This is the main application frame drawing method. All high level drawing code should go in here.

                    //

                    //The 'FrameState' is a state or context object. It provides access to current information,

                    //such as the time and shader globals.

                    //

                    // Do not store a reference to a 'state' object, doing so (and using it elsewhere) can have

                    // unexpected side effects. All 'state' objects are designed to be used only within

                    // the method they were passed in to.

                    //

                    protected override void Frame(FrameState state)

                    {

                               //perform the draw to the screen.

                               drawToScreen.Draw(state);

 

                               //at this point the screen has been drawn...

                    }

          }

}

2 комментария:

  1. Каким образом писать void Main ?
    Код непонятен-
    using (Application tutorial = Activator.CreateInstance(type) as Application)
    {
    tutorial.Run();

    }

    Что такое Activator.CreateInstance(type) и можно ли без него обойтись в смысле попроще *?

    ОтветитьУдалить
    Ответы
    1. Activator.CreateInstance(type) создает экземпляр определенного типа. В данном случае это сделано для того, чтобы создать экземпляр класса, который соответствует выбранному в меню "уроку".

      В своих приложениях XNA+XEN делать так не обязательно

      Удалить