Многие, думаю, создавали, ну или хотя бы пытались создать какую-нибудь игрушку. И очень часто инди-разработчики выбирают жанр аркады, скролл-шутера, платфомера или что-то в этом роде. Здесь я хочу рассказать о создании простейшего редактора уровней к такой аркаде.
В качестве платформы я выбрал C# + XNA 4.0, и WinForms для пользовательских элементов в редакторе уровней.
Создаем в VisualStudio проект Windows Game и вперед!
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/3f0/b23/e65/3f0b23e65022c9d318812bc2f9371e7e.png)
Для уровня я создал отдельный класс, который используется и в самой игре, и в редакторе. Для удобства я вынес его в отдельный проект и добавил в оба солюшена. Сам объект уровня создается в редакторе, сериализуется в файл, а после из того файла десериализуется в игру.
Как я уже сказал, интерфейс я делал на WindowsForms. Подключаем System.Windows.Forms к нашему проекту. Некоторые имена классов WindowsForms могут конфликтовать с именами в Xna, поэту сделаем так
В MSDN есть отличное описание WindowsForms и всех его виджетов, так что на этом я останавливаться не буду. С взаимодействием WindowsForms и XNA у меня проблем не возникло. Создаем в классе Game1 (стандартное имя класса игры в Xna) процедуру, создающую окно, виджеты и настраивающую их, и вызываем ее в методе Initialize(). У меня получилось как-то так:
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/5ef/7e5/cfa/5ef7e5cfac6cd21dae452204318dc296.png)
Создадим класс уровня и укажем, что он сериализуемый:
Этот класс содержит списки всех объектов(я сделал только один тип объектов, но добавить другие уже не составит труда)
Класс Block:
Обратите внимание, что он тоже должен быть сериализуемый, а текстуру не сериализуем(иначе ошибка), а добавляем ее уже непосредственно при загрузке уровня в саму игру(для этого можно создать дополнительное свойство String Texturename, int TextureType или что вам захочется).
CameraPos — вектор, указывающий на позицию камеры, необходимый, чтобы сделать прокрутку уровня.
В этом же классе методы для переводя координа из экранных в мировые и наооборот:
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/c4b/9a8/cbb/c4b9a8cbbc06eb8218d0a1e76d3e6bda.png)
В классе Game1 создаем описание текущего инструмента:
И в обработчике событий переключалки инструментов в нашем вспомогательном окне меняем переменную currTool на то, что выбирает пользователь.
Если выбран блок, то создаем его и тягаем по окну, при клике мышью добавляем го в список блоков и он фиксируется в уровне.
Инструмент для создания блока имеет два режима работы: свободное расположение и расположение на определенных местах, на расстоянии, кратном размеру блока, чтобы уровень был более правильный.
Проктрутка окна:
Не забываем при делать перевод из экранных координат в мировые и наоборот, когда это необходми(при рисовании, добалении объекта и т.п.).
В обработчике клика по кнопкам Save / Load вызываем методы класса для работы с загрузкой сохранением.
Этот класс располагаем в проекте для работы с уровнем(там же, где и Level, Block и др.)
В игре делаем this.level = OpenLevel(«foo.lvl»), но не забываем блокам добавить текстуру.
Дальше обрабатываем взаимодействие игрока с уровнем(у меня пока это ограничивается этим:
)
Вот так я сделал каркас для редактора уровней. Теперь можно добавить другие объекты, расширять его.
Прошу не ругаться за неаккуратный/некрасивый/кривоватый код.
Выкладываю скриншот, проект, в котором можно кидать эти блоки на сцену и саму игру, в которой можно побегать по уровню выложить не могу, сейчас она сырая, но если интересно, то «на посмотреть» хватит. По умелчанию грузит евел с именем Test.lev из папки с бинариком игры, а редктор сохраняет файл в папке со всоим файлом, поэтому файл уровня нужно будет перенести. Иначе, нужно поправить имя уровня в исходниках(все исходники прилагаются).
Ссылка на skydrive (файл в формате 7z)
На narod.ru(файл в формате zip) — для тех, у кого нет 7zip
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/eb6/6ad/44d/eb66ad44d222de55d1ec5419a5efeb2c.png)
UPD: добавил файлы к статье.
В качестве платформы я выбрал C# + XNA 4.0, и WinForms для пользовательских элементов в редакторе уровней.
Создаем в VisualStudio проект Windows Game и вперед!
![](https://habrastorage.org/getpro/habr/post_images/3f0/b23/e65/3f0b23e65022c9d318812bc2f9371e7e.png)
0. Архитектура
Для уровня я создал отдельный класс, который используется и в самой игре, и в редакторе. Для удобства я вынес его в отдельный проект и добавил в оба солюшена. Сам объект уровня создается в редакторе, сериализуется в файл, а после из того файла десериализуется в игру.
1. Интерфейс
Как я уже сказал, интерфейс я делал на WindowsForms. Подключаем System.Windows.Forms к нашему проекту. Некоторые имена классов WindowsForms могут конфликтовать с именами в Xna, поэту сделаем так
using WinForms = System.Windows.Forms;
В MSDN есть отличное описание WindowsForms и всех его виджетов, так что на этом я останавливаться не буду. С взаимодействием WindowsForms и XNA у меня проблем не возникло. Создаем в классе Game1 (стандартное имя класса игры в Xna) процедуру, создающую окно, виджеты и настраивающую их, и вызываем ее в методе Initialize(). У меня получилось как-то так:
![](https://habrastorage.org/getpro/habr/post_images/5ef/7e5/cfa/5ef7e5cfac6cd21dae452204318dc296.png)
2. Класс уровня
Создадим класс уровня и укажем, что он сериализуемый:
[Serializable]
public class Level
{
//...
}
Этот класс содержит списки всех объектов(я сделал только один тип объектов, но добавить другие уже не составит труда)
public List<Block> Blocks = new List<Block>();
public Vector2 cameraPos;
Класс Block:
[Serializable]
public class Block
{
[NonSerialized]
public Texture2D tex;
public Rectangle rect;
public Block(Texture2D texture, Rectangle rectangle)
{
this.tex = texture;
this.rect = rectangle;
}
}
Обратите внимание, что он тоже должен быть сериализуемый, а текстуру не сериализуем(иначе ошибка), а добавляем ее уже непосредственно при загрузке уровня в саму игру(для этого можно создать дополнительное свойство String Texturename, int TextureType или что вам захочется).
CameraPos — вектор, указывающий на позицию камеры, необходимый, чтобы сделать прокрутку уровня.
В этом же классе методы для переводя координа из экранных в мировые и наооборот:
public Vector2 ScreenToWorld(Vector2 vector)
{
Vector2 rvect;
rvect = cameraPos + vector;
return rvect;
}
public Vector2 WorldToScreen(Vector2 vector)
{
Vector2 rvect;
rvect = vector - cameraPos;
return rvect;
}
![](https://habrastorage.org/getpro/habr/post_images/c4b/9a8/cbb/c4b9a8cbbc06eb8218d0a1e76d3e6bda.png)
3. Непосредственно сам редактор
В классе Game1 создаем описание текущего инструмента:
enum CurrentTool
{
None,
Brick,
Delete
}
CurrentTool currTool = CurrentTool.None;
И в обработчике событий переключалки инструментов в нашем вспомогательном окне меняем переменную currTool на то, что выбирает пользователь.
Если выбран блок, то создаем его и тягаем по окну, при клике мышью добавляем го в список блоков и он фиксируется в уровне.
Инструмент для создания блока имеет два режима работы: свободное расположение и расположение на определенных местах, на расстоянии, кратном размеру блока, чтобы уровень был более правильный.
Проктрутка окна:
if (Keyboard.GetState().IsKeyDown(Keys.Left) && lastKeyboardState.IsKeyUp(Keys.Left))
{
level.cameraPos += new Vector2(-10, 0);
}
//в остальные стороны аналогично
Не забываем при делать перевод из экранных координат в мировые и наоборот, когда это необходми(при рисовании, добалении объекта и т.п.).
4. Сохранение/загрузка уровня.
В обработчике клика по кнопкам Save / Load вызываем методы класса для работы с загрузкой сохранением.
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
namespace LevelLib
{
public static class SaveOpenLevel
{
public static Level OpenLevel(string filename)
{
Level level;
if (filename == "")
{
return null;
}
if (!File.Exists(filename))
{
return null;
}
BinaryFormatter bf = new BinaryFormatter();
Stream fs = new FileStream(filename, FileMode.Open);
level = (Level)bf.Deserialize(fs);
fs.Close();
return level;
}
public static int SaveLevel(string filename, Level level)
{
if (filename == "")
{
return 1;
}
BinaryFormatter bf = new BinaryFormatter();
Stream fs = new FileStream(filename, FileMode.Create);
bf.Serialize(fs, level);
fs.Close();
return 0;
}
}
}
* This source code was highlighted with Source Code Highlighter.
Этот класс располагаем в проекте для работы с уровнем(там же, где и Level, Block и др.)
В игре делаем this.level = OpenLevel(«foo.lvl»), но не забываем блокам добавить текстуру.
Дальше обрабатываем взаимодействие игрока с уровнем(у меня пока это ограничивается этим:
foreach(Block block in level.blocks)
{
if(block.Intersets(player.Rectangle)
return true;
}
)
5. Послесловие
Вот так я сделал каркас для редактора уровней. Теперь можно добавить другие объекты, расширять его.
Прошу не ругаться за неаккуратный/некрасивый/кривоватый код.
Выкладываю скриншот, проект, в котором можно кидать эти блоки на сцену и саму игру, в которой можно побегать по уровню выложить не могу, сейчас она сырая, но если интересно, то «на посмотреть» хватит. По умелчанию грузит евел с именем Test.lev из папки с бинариком игры, а редктор сохраняет файл в папке со всоим файлом, поэтому файл уровня нужно будет перенести. Иначе, нужно поправить имя уровня в исходниках(все исходники прилагаются).
Ссылка на skydrive (файл в формате 7z)
На narod.ru(файл в формате zip) — для тех, у кого нет 7zip
![](https://habrastorage.org/getpro/habr/post_images/eb6/6ad/44d/eb66ad44d222de55d1ec5419a5efeb2c.png)
UPD: добавил файлы к статье.