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<string, Type> types)
{
//work around the xbox's lack of SortedList / SortedDictionary
var items = new List<KeyValuePair<string, Type>>();
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<string, Type>(name, type));
}
}
items.Sort(SortItems);
foreach (var item in items)
{
types.Add(item.Key, item.Value);
}
}
{
//work around the xbox's lack of SortedList / SortedDictionary
var items = new List<KeyValuePair<string, Type>>();
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<string, Type>(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<string, Type> a, KeyValuePair<string, Type> b)
{
return a.Key.CompareTo(b.Key);
}
private static int SortItems(KeyValuePair<string, Type> a, KeyValuePair<string, Type> 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; }
}
}
{
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
{ … }
//
//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<string, Type> tutorials = new Dictionary<string, Type>();
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;
}
{
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<string, Type> tutorials = new Dictionary<string, Type>();
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();
}
}
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();
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... } } }
Каким образом писать void Main ?
ОтветитьУдалитьКод непонятен-
using (Application tutorial = Activator.CreateInstance(type) as Application)
{
tutorial.Run();
}
Что такое Activator.CreateInstance(type) и можно ли без него обойтись в смысле попроще *?
Activator.CreateInstance(type) создает экземпляр определенного типа. В данном случае это сделано для того, чтобы создать экземпляр класса, который соответствует выбранному в меню "уроку".
УдалитьВ своих приложениях XNA+XEN делать так не обязательно