Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Знакомство с XNA Framework и XNA Game Studio  »  XNA - Создание компонентов

XNA Game Studio: Создание компонентов в MSVC#2008




В этой статье мы создадим игровой компонент XNA (XNA GameComponent). Игровые компоненты позволяют разделять исходный код игровой логики на отдельные файлы, названия которым XNA Framework присваивает автоматически.1
Мы возьмём код, подсчитывающий FPS в ранее созданном проекте PerfomanceBenchmark, и создадим на его основе отдельный игровой компонент.
На всякий случай приведём полный исходный код Game1.cs из данного проекта:
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;

namespace PerfomanceBenchmark
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private float fps;
        private float updateInterval = 1.0f;
        private float timeSinceLastUpdate = 0.0f;
        private float framecount = 0;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Не синхронизировать наш метод Draw с вертикальной перерисовкой (retrace) нашего монитора.
            graphics.SynchronizeWithVerticalRetrace = false;
            // Не вызывать метод Update в каждом кадре на частоте кадров по умолчанию (default rate), т.е. 60 раз в секунду.
            IsFixedTimeStep = false;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            float elapsed = (float)gameTime.ElapsedRealTime.TotalSeconds;
            framecount++;
            timeSinceLastUpdate += elapsed;
            if (timeSinceLastUpdate > updateInterval)
            {
                fps = framecount / timeSinceLastUpdate; // Подразумевается fps, превышающий updateInterval
#if XBOX360
                System.Diagnostics.Debug.WriteLine("FPS: " + fps.ToString() + " - RT: ") +
                gameTime.ElapsedRealTime.TotalSeconds.ToString() + " - GT: " +
                gameTime.ElapsedRealTime.TotalSeconds.ToString());
#else
                Window.Title = "FPS: " + fps.ToString() + " - RT: " +
                    gameTime.ElapsedRealTime.TotalSeconds.ToString() + " - GT: " +
                    gameTime.ElapsedGameTime.TotalSeconds.ToString();
#endif
                framecount = 0;
                timeSinceLastUpdate -= updateInterval;
            }

            base.Draw(gameTime);
        }
    }
}



Рис. 1 Добавляем в текущий Проект новый игровой компонент XNA.
Рис. 1 Добавляем в текущий Проект новый игровой компонент XNA.



  • Добавь в Проект PerfomanceBenchmark файл исходного кода FPS.cs. Для этого в Обозревателе Решения (Solution Explorer) щёлкни правой кнопкой мыши по названию Проекта (выделен жирным) и во всплывающем меню выбери: Add -> New Item... (Добавить -> Новый объект). В появившемся окне выбора шаблона в разделе Categories выбери XNA Game Studio 3.1 (или любую актуальную версию). В правой части в разделе Templates отметь значок Game Component, в поле Name ниже введи имя FPS.cs и нажим Add (Добавить) (см. Рис.1).

Новый компонент FPS.cs появится в обозревателе решения, и откроется страница с его исходным кодом.
В объявлении класса компонента видим строку:
public class FPS : Microsoft.Xna.Framework.GameComponent
Класс игрового компонента наследуется от класса Microsoft.Xna.Framework.GameComponent. Такой принцип удобен в случаях, когда необходимо лишь обновить значения внутренних переменных (обычно с помощью метода Update). Для наших подсчётов FPS необходимо, чтобы новый компонент экспонировал свой собственный метод Draw, т.к. только так можно выяснить, сколько раз в секунду мы отрисовываем игрвой мир на экран. Поэтому первым делом в исходном коде FPS.cs необходимо заменить родительский класс в объявлении класса FPS с GameComponent на DrawableGameComponent.
  • Удали весь исходный код FPS.cs и замени на следующий:
FPS.cs
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;

namespace XNADemo
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public sealed class FPS : Microsoft.Xna.Framework.DrawableGameComponent
    {
        private float fps;
        private float updateInterval = 1.0f;
        private float timeSinceLastUpdate = 0.0f;
        private float framecount = 0;

        public FPS(Game game)
            : this(game, false, false, game.TargetElapsedTime) { }

        public FPS(Game game, bool synchWithVerticalRetrace,
                   bool isFixedTimeStep, TimeSpan targetElapsedTime)
            : base(game)
        {
            GraphicsDeviceManager graphics =
                (GraphicsDeviceManager)Game.Services.GetService(
                typeof(IGraphicsDeviceManager));

            graphics.SynchronizeWithVerticalRetrace = synchWithVerticalRetrace;
            Game.IsFixedTimeStep = isFixedTimeStep;
            Game.TargetElapsedTime = targetElapsedTime;
        }


        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public sealed override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public sealed override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here

            base.Update(gameTime);
        }

        public sealed override void Draw(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedRealTime.TotalSeconds;
            framecount++;
            timeSinceLastUpdate += elapsed;
            if (timeSinceLastUpdate > updateInterval)
            {
                fps = framecount / timeSinceLastUpdate;

#if XBOX360
                System.Diagnostics.Debug.WriteLine("FPS: " + fps.ToString());
#else
                Game.Window.Title = "FPS: " + fps.ToString();
#endif
                framecount = 0;
                timeSinceLastUpdate -= updateInterval;
            }

            base.Draw(gameTime);
        }
    }
}


  • Сохрани Решение (File -> Save All).

Исследуем код FPS.cs

Основное отличие от схожего кода из Проекта PerfomanceBenchmark состоит в том, что всё происходит внутри другого дочернего компонента, унаследованного от Microsoft.Xna.Framework.DrawableGameComponent. Но самые большие изменения претерпел конструктор класса FPS.
Как мы выяснили в статье XNA - Оценка производительности для измерения реального FPS необходимо заставить игровой цикл перерисовывать экран так быстро, насколько это возможно, не ожидая пока монитор выполнит обновление по вертикали (vertical refresh) прежде чем обновит экран. Мы легко можем разместить данный код внутри главного игрового класса Game1. Но с целью унификации замеров для других игровых проектов, лучше оставить его в отдельном компоненте FPS.cs. По этой же причине код конструктор главного игрового класса Game1 будет получать данные о текущем FPS из конструктора компонента FPS. Обычно в игровой компонент передаётся лишь инстанс текущей игры, но по мере необходимости можно также передавать и другие параметры. В нашем случае мы передаём значения, которые мы заранее подготовили. У нас есть конструктор по умолчанию (default constructor), который будет рендерить игру так быстро, насколько это возможно. Данные переменные относятся к игре, а не к компоненту.

Изменения в Game1.cs

Теперь, когда в нашем проекте есть полностью рабочий компонент для замера FPS, необходимо "прикрутить" его к игровому проекту Game1.
Это уже сделано, о чём говорит объявление закрытой переменной
private float fps;
в исходном коде Game1.cs.
Перейдём к конструктору игрового класса.
  • Найди в исходном коде Game1.cs след. фрагмент:
Фрагмент Game1.cs
...
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Не синхронизировать наш метод Draw с вертикальной перерисовкой (retrace) нашего монитора.
            graphics.SynchronizeWithVerticalRetrace = false;
            // Не вызывать метод Update в каждом кадре на частоте кадров по умолчанию (default rate), т.е. 60 раз в секунду.
            IsFixedTimeStep = false;
        }
...

  • Замени его на следующий:
Фрагмент Game1.cs
...
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

#if DEBUG
            fps = new FPS(this);
#else
            fps = new FPS(this, true, true, this.TargetElapsedTime);
#endif
            Components.Add(fps);
        }
...

Мы обнесли строки условной директивой компилятора #if DEBUG для рендеринга с максимальной скоростью.
Также мы можем изменить способ инициализации переменной fps путём явной отправки текущих значений в конструктор. Либо создать другую конфигурацию (т.н. профиль, PROFILE). При компилировании в режиме release или debug игра будет идти с нормальной скоростью, но FPS будет показывать "честный" FPS, нак котором работает движок (100 и более).
После инициализации объекта fps мы добавляем новый компонент в коллекцию компонентов игры (Components.Add(fps)). Далее XNA вызывает методы Update и Draw (а также другие виртуальные методы) одновременно с вызовами методов игры.

Часто бывает просто необходимо разделять игровую логику и игровые объекты, выводимые на экран. Конечно, нет смысла все игровые компоненты выделять в отдельные файлы, т.к. это. К примеру, если мы хотим отделить игровых врагов от объекта игрока, то есть смысл выделить объект игрока в отдельный компонент, как и менеджер врагов (enemy manager). Менеджер врагов, в свою очередь, может определять, какие именно враги будут выводиться на экран в данный момент, как они будут перемещаться и т.д.

Источники:


1. Chad Carter. Microsoft XNA Game Studio 3.0 Unleashed. - Sams Publishing. 2009


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Знакомство с XNA Framework и XNA Game Studio  »  XNA - Создание компонентов

Contributors to this page: slymentat .
Последнее изменение страницы Четверг 20 / Июнь, 2019 14:42:13 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108