Pull to refresh

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

Reading time5 min
Views8.3K
Многие, думаю, создавали, ну или хотя бы пытались создать какую-нибудь игрушку. И очень часто инди-разработчики выбирают жанр аркады, скролл-шутера, платфомера или что-то в этом роде. Здесь я хочу рассказать о создании простейшего редактора уровней к такой аркаде.
В качестве платформы я выбрал C# + XNA 4.0, и WinForms для пользовательских элементов в редакторе уровней.
Создаем в VisualStudio проект Windows Game и вперед!



0. Архитектура


Для уровня я создал отдельный класс, который используется и в самой игре, и в редакторе. Для удобства я вынес его в отдельный проект и добавил в оба солюшена. Сам объект уровня создается в редакторе, сериализуется в файл, а после из того файла десериализуется в игру.

1. Интерфейс


Как я уже сказал, интерфейс я делал на WindowsForms. Подключаем System.Windows.Forms к нашему проекту. Некоторые имена классов WindowsForms могут конфликтовать с именами в Xna, поэту сделаем так
using WinForms = System.Windows.Forms;

В MSDN есть отличное описание WindowsForms и всех его виджетов, так что на этом я останавливаться не буду. С взаимодействием WindowsForms и XNA у меня проблем не возникло. Создаем в классе Game1 (стандартное имя класса игры в Xna) процедуру, создающую окно, виджеты и настраивающую их, и вызываем ее в методе Initialize(). У меня получилось как-то так:


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;
}




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



UPD: добавил файлы к статье.

Tags:
Hubs:
Total votes 53: ↑38 and ↓15+23
Comments5

Articles