К основному контенту

Сериализация и уровень

Уровень – это подгружаемые данные, никто не хранит их всех в памяти как и в основном исходном коде. Будем учиться писать уровень который будет читаться с файла.


Только не нужно пропускать статью со словами «Что нам стоить прочитать с файла…», Читать будем с помощью сериализации. Почему? Потому, что нужно сразу привыкать к хорошему или правильному. На консоле будет сложно прочитать данные старыми методами. Можно, но сложно поэтому вспоминаем, а для кого-то учим XML, структуры в ней, и методы сбора информации.

Среда: Microsoft Visual Studio C# 2008 Express Edition, XNA 3.1
 
Проект, как это будет реализовано?


Имеем категорию levels, файл level_001.xml
В файле нацарапано:

  
    
      0
      600
    
    0
    Land
  
  
    
      100
      100
    
    1
    Box
  
  
    
      400
      100
    
    0
    Box
  
  
    
      600
      100
    
    0
    Circle
  
  
    
      700
      100
    
    0
    Circle
  

Каждый элемент уровня имеет несколько параметров:

Position – Координаты объекта
Rotation – Вращение объекта
TypeName – Тип объекта

В программе должен быть описан каждый тип объекта. Я опишу три простейших:
class Land:LevelElement Элемент уровня: земля
class Box:LevelElement Элемент уровня: ящик
class Circle:LevelElement Элемент уровня: круг

Теперь общее представление программной структуры:



Самый главный класс – Игровой менеджер (GameManager), который содержит множество игровых элементов (GameElement). Игровой элемент может быть уровень, игрок, фон, даже элемент уровня в общем все зависит от вашей фантазии. Мы, для краткости, рассмотрим только одну вариацию – уровень (Level). Уровень в свою очередь может содержать множество элементов уровня (LevelElement). Вот они наши вариации Ящик(Box), Земля(Land), Круг(Circle). Создается игровой элемент не просто так, а добавляется в коллекцию учитывая промежуточные значения класса LevelReadElement, куда заноситься данные из файла.

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


Теперь ближе к файлам, комментарии в коде.
Game1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using Serialization.GameManagers;
using Serialization.Levels;

namespace Serialization
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        GameManager gameManager;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            gameManager = new GameManager(this, graphics);
            gameManager.Add(new Level());
            Components.Add(gameManager);
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        {

        }

        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)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            base.Draw(gameTime);
        }
    }
}
GameManager.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

namespace Serialization.GameManagers
{
    /// 
    /// Игровой менеджер
    /// 
    class GameManager: DrawableGameComponent
    {
        /// 
        /// Список игровых элементов
        /// 
        List elements;
        /// 
        /// Инициализирован ли менеджер
        /// 
        public bool IsInitialized;
        /// 
        /// Чудо рисователь
        /// 
        public SpriteBatch spriteBatch;
        /// 
        /// Контент загрузчик
        /// 
        public ContentManager contentManager;
        /// 
        /// Графическое устройство
        /// 
        public GraphicsDeviceManager graphicDeviceManager;
        /// 
        /// Физический симулятор
        /// 
        public PhysicsSimulator physicsSimulator;
        /// 
        /// Конструктор
        /// 
        /// 
Игра/// 
Менеджер Графического устройстваpublic GameManager(Game game, GraphicsDeviceManager graphicDeviceManager) : base(game)
        {
            elements = new List();
            IsInitialized = false;
            this.graphicDeviceManager = graphicDeviceManager;
            physicsSimulator = new PhysicsSimulator(new Vector2(0, 1000));
        }
        /// 
        /// Инициализация
        /// 
        public override void Initialize()
        {
            base.Initialize();
            this.IsInitialized = true;
        }
        /// 
        /// Загрузка данных
        /// 
        protected override void LoadContent()
        {
            base.LoadContent();
            contentManager = Game.Content;
            spriteBatch = new SpriteBatch(GraphicsDevice);
            foreach (GameElement element in elements)
                element.LoadContent();
        }
        /// 
        /// Выгрузка данных
        /// 
        protected override void UnloadContent()
        {
            foreach (GameElement element in elements)
                element.UnLoadContent();
            base.UnloadContent();
        }
        /// 
        /// Обновление данных
        /// 
        /// 
Игровое времяpublic override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            foreach (GameElement element in elements)
                element.Update(gameTime);
            physicsSimulator.Update(gameTime.ElapsedGameTime.Milliseconds * 0.001f);
        }
        /// 
        /// Отображение данных
        /// 
        /// 
Игровое времяpublic override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
            GraphicsDevice.Clear(Color.Black);
            foreach (GameElement element in elements)
                element.Draw(gameTime);
        }
        /// 
        /// Добавление Игрового элемента
        /// 
        /// 
Игровой элементpublic void Add(GameElement element)
        {
            element.gameManager = this;
            if (IsInitialized) element.LoadContent();
            elements.Add(element);
        }
        /// 
        /// Удаление Игрового элемента
        /// 
        /// 
Игровой элементpublic void Remove(GameElement element)
        {
            element.UnLoadContent();
            elements.Remove(element);
        }
    }
}
GameElement.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

namespace Serialization.GameManagers
{
    /// 
    /// Элемент игры (базовый класс)
    /// 
    class GameElement
    {
        /// 
        /// Принадлежность игровому Менеджеру
        /// 
        public GameManager gameManager;
        /// 
        /// Конструктор
        /// 
        public GameElement()
        { }
        /// 
        /// Загрузка данных
        /// 
        public virtual void LoadContent()
        { }
        /// 
        /// Выгрузка данных
        /// 
        public virtual void UnLoadContent()
        { }
        /// 
        /// Обновление данных
        /// 
        /// 
Игровое времяpublic virtual void Update(GameTime gameTime)
        { }
        /// 
        /// Отображение данных
        /// 
        /// 
Игровое времяpublic virtual void Draw(GameTime gameTime)
        { }
    }
}
Level.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

using Serialization.GameManagers;
using Serialization.Levels.LevelElements;

namespace Serialization.Levels
{
    /// 
    /// Уровень
    /// 
    class Level:GameElement
    {
        /// 
        /// Список елементов в уровне
        /// 
        public List elements;
        /// 
        /// Конструктор
        /// 
        public Level()
        {
            elements = new List();
        }
        /// 
        /// Загрузка ресурсов элементов в уровне
        /// 
        public override void LoadContent()
        {
            //foreach (LevelElement element in elements)
                //element.LoadContent();
                /*Box box = new Box();
                LevelReadElement r = new LevelReadElement();
                r.Position = new Vector2(100, 100);
                r.Rotation = 1;
                r.TypeName = "Box";
                box.level = this;
                box.LoadContent(r);
                Add(box);
                Add(box);*/
                ReadLevel("level_001.xml");
        }
        /// 
        /// Выгрузка элементов уровня
        /// 
        public override void UnLoadContent()
        {
            foreach (LevelElement element in elements)
                element.UnLoadContent();
        }
        /// 
        /// Обновление элементов уровня
        /// 
        /// 
Игровое времяpublic override void Update(GameTime gameTime)
        {
            foreach (LevelElement element in elements)
                element.Update(gameTime);
        }
        /// 
        /// Отображение Элементов уровня
        /// 
        /// 
Игровое времяpublic override void Draw(GameTime gameTime)
        {
            foreach (LevelElement element in elements)
                element.Draw(gameTime);
        }
        /// 
        /// Добавление элемента к уровню
        /// 
        /// 
Элемент уровняpublic void Add(LevelElement element)
        {
            element.level = this;
            elements.Add(element);
        }
        /// 
        /// Удаление элемента из уровня
        /// 
        /// 
Элемент уровняpublic void Remove(LevelElement element)
        {
            element.UnLoadContent();
            elements.Remove(element);
        }
        /// 
        /// Загрузка элементов из файла
        /// 
        /// 
Название файла (путь к файлу)public void ReadLevel(string path)
        {
            List list = new List();
            string filename = System.IO.Path.Combine("levels", path);
            FileStream stream = File.Open(filename, FileMode.Open);
            XmlSerializer serializer = new XmlSerializer(typeof(List));
            list = (List)serializer.Deserialize(stream);
            stream.Close();
            foreach (LevelReadElement element in list)
            {
                if (element.TypeName == "Box")
                {
                    Box levEl = new Box();
                    levEl.level = this;
                    levEl.LoadContent(element);
                    Add(levEl);
                }
                else
                if (element.TypeName == "Circle")
                {
                    Circle levEl = new Circle();
                    levEl.level = this;
                    levEl.LoadContent(element);
                    Add(levEl);
                }
                else
                if (element.TypeName == "Land")
                {
                    Land levEl = new Land();
                    levEl.level = this;
                    levEl.LoadContent(element);
                    Add(levEl);
                }
            }
        }
        /// 
        /// Запись элементов в файл
        /// 
        /// 
Название файла (путь к файлу)public void SaveLevel(string path)
        {
            List list = new List();
            LevelReadElement el;
            foreach (LevelElement element in elements)
            {
                el = new LevelReadElement();
                el.TypeName = element.TypeName;
                el.Position = element.body[0].Position;
                el.Rotation = element.body[0].Rotation;
                list.Add(el);
            }
            string filename = System.IO.Path.Combine("levels", path);
            FileStream stream = File.Open(filename, FileMode.OpenOrCreate);
            XmlSerializer serializer = new XmlSerializer(typeof(List));
            serializer.Serialize(stream, list);
            stream.Close();
        }
    }
}
LevelElement.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;


namespace Serialization.Levels
{
    /// 
    /// Один элемент уровня
    /// 
    class LevelElement
    {
        /// 
        /// Ссылка на принадлежность уровню
        /// 
        public Level level;
        /// 
        /// Список тел элемента
        /// 
        public List body;
        /// 
        /// Список геометрий
        /// 
        public List geom;
        /// 
        /// Текстовое название элемента
        /// 
        public string TypeName;
        /// 
        /// Констуктор
        /// 
        public LevelElement()
        {
            TypeName = "none";
            body = new List();
            geom = new List();
        }
        /// 
        /// Загрузка данных
        /// 
        public virtual void LoadContent(LevelReadElement element)
        { }
        /// 
        /// Выгрузка данных
        /// 
        public virtual void UnLoadContent()
        { }
        /// 
        /// Обновление данных
        /// 
        /// 
Игровое времяpublic virtual void Update(GameTime gameTime)
        { }
        /// 
        /// Отображение данных
        /// 
        /// 
Игровое времяpublic virtual void Draw(GameTime gameTime)
        { }
    }
}
LevelReadElement.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
namespace Serialization.Levels
{
    /// 
    /// Читаемый элемент уровня из файла
    /// 
    [Serializable]
    public class LevelReadElement
    {
        /// 
        /// Координатная позиция
        /// 
        public Vector2 Position;
        /// 
        /// Вращение
        /// 
        public float Rotation;
        /// 
        /// Текстовое название елемента
        /// 
        public string TypeName;
    }
}
Box.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

namespace Serialization.Levels.LevelElements
{
    /// 
    /// Элемент уровня: ящик
    /// 
    class Box:LevelElement
    {
        /// 
        /// Тестура ящика
        /// 
        public Texture2D texture;
        /// 
        /// Масса тела
        /// 
        public float Mass=10;
        /// 
        /// Конструктор
        /// 
        public Box()
            : base()
        { }
        /// 
        /// Загрузка данных
        /// 
        /// 
Начальные параметрыpublic override void LoadContent(LevelReadElement element)
        {
            texture = level.gameManager.contentManager.Load("box");
            body.Add(BodyFactory.Instance.CreateRectangleBody(level.gameManager.physicsSimulator, texture.Width, texture.Height, Mass));
            geom.Add(GeomFactory.Instance.CreateRectangleGeom(level.gameManager.physicsSimulator, body[0], texture.Width, texture.Height));
            body[0].Position = element.Position;
            body[0].Rotation = element.Rotation;
            TypeName = element.TypeName;
        }
        /// 
        /// Отображение данных
        /// 
        /// 
Игровое времяpublic override void Draw(GameTime gameTime)
        {
            level.gameManager.spriteBatch.Begin();
            level.gameManager.spriteBatch.Draw(texture, body[0].Position, null, Color.White, body[0].Rotation, new Vector2(texture.Width / 2, texture.Height / 2), 1, SpriteEffects.None, 0);
            level.gameManager.spriteBatch.End();
        }
    }
}
Circle.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;


namespace Serialization.Levels.LevelElements
{
    /// 
    /// Элемент уровня: круг
    /// 
    class Circle:LevelElement
    {
        /// 
        /// Тестура круга
        /// 
        public Texture2D texture;
        /// 
        /// Масса тела
        /// 
        public float Mass = 2;
        /// 
        /// Конструктор
        /// 
        public Circle()
            : base()
        { }
        /// 
        /// Загрузка данных
        /// 
        /// 
Начальные параметрыpublic override void LoadContent(LevelReadElement element)
        {
            texture = level.gameManager.contentManager.Load("circle");
            body.Add(BodyFactory.Instance.CreateCircleBody(level.gameManager.physicsSimulator,texture.Width/2, Mass));
            geom.Add(GeomFactory.Instance.CreateCircleGeom(level.gameManager.physicsSimulator, body[0], texture.Width/2, 100));
            body[0].Position = element.Position;
            body[0].Rotation = element.Rotation;
            TypeName = element.TypeName;
        }
        /// 
        /// Отображение данных
        /// 
        /// 
Игровое времяpublic override void Draw(GameTime gameTime)
        {
            level.gameManager.spriteBatch.Begin();
            level.gameManager.spriteBatch.Draw(texture, body[0].Position, null, Color.White, body[0].Rotation, new Vector2(texture.Width / 2, texture.Height / 2), 1, SpriteEffects.None, 0);
            level.gameManager.spriteBatch.End();
        }
    }
}
Land.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

namespace Serialization.Levels.LevelElements
{
    /// 
    /// Элемент уровня: земля
    /// 
    class Land:LevelElement
    {
        /// 
        /// Тестура земли
        /// 
        public Texture2D texture;
        /// 
        /// Масса тела
        /// 
        public float Mass=1000;
        /// 
        /// Конструктор
        /// 
        public Land()
            : base()
        { }
        /// 
        /// Загрузка данных
        /// 
        /// 
Начальные параметрыpublic override void LoadContent(LevelReadElement element)
        {
            texture = level.gameManager.contentManager.Load("land");
            body.Add(BodyFactory.Instance.CreateRectangleBody(level.gameManager.physicsSimulator, texture.Width, texture.Height, Mass));
            geom.Add(GeomFactory.Instance.CreateRectangleGeom(level.gameManager.physicsSimulator, body[0], texture.Width, texture.Height));
            body[0].Position = element.Position;
            body[0].Rotation = element.Rotation;
            body[0].IsStatic = true;
            TypeName = element.TypeName;
        }
        /// 
        /// Отображение данных
        /// 
        /// 
Игровое времяpublic override void Draw(GameTime gameTime)
        {
            level.gameManager.spriteBatch.Begin();
            level.gameManager.spriteBatch.Draw(texture, body[0].Position, null, Color.White, body[0].Rotation, new Vector2(texture.Width / 2, texture.Height / 2), 1, SpriteEffects.None, 0);
            level.gameManager.spriteBatch.End();
        }
    }
}
Весь файл проэкта.

Комментарии