Разработка компьютерных игр для Windows Phone 7 с использованием технологий Silverlight и XNA

Анимация, эффекты

Разбить на страницы
Показывать лекцию целиком

Цель работы: Научиться применять графические эффекты и приемы анимации при работе с трехмерной графикой в XNA

27.1. Анимация персонажей

Компьютерные игры наполнены анимированными персонажами. XNA содержит встроенные средства, поддерживающие базовые операции по анимации трехмерных персонажей. В частности, это средства для работы с так называемым скелетом персонажа. Скелет состоит из взаимосвязанных костей, управляя которыми можно анимировать модель. Создание анимированных моделей – это отдельная, достаточно трудоемкая задача, которая требует серьезных познаний в области 3D-моделирования. Для подготовки анимированных трехмерных моделей вы можете использовать практически любой 3D-редактор.

XNA содержит лишь базовые средства для управления скелетами объектов. Так, мы можем получить коллекцию костей объекта и проводить с ними какие-либо преобразования, вызывающие движение модели. Однако, такой подход ограничен – с его использованием весьма сложно создать реалистичное движение для сложных моделей. На основе базовых средств XNA разработаны библиотеки кода, которые содержат компоненты, позволяющие анимировать персонажи на более высоком уровне. В частности, одну из таких библиотек – XNAAnimation – можно найти на сайте http://www.codeplex.com/xnanimation.

Здесь мы рассмотрим базовый подход к анимации модели. Он заключается в следующем. Можно менять параметры отдельных костей модели и выводить ее на экран с учетом этих изменений. Мы использовали бесплатную модель Ballpen.fbx, взятую с сайта http://www.turbosquid.com.

Создадим новый проект P18_1. Загрузим в него модель Ballpen.fbx.

Код проекта сосредоточен в классе Game1.cs, его код вы можете найти в листинге 27.1.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace P20_1
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        //Матрицы
        Matrix viewMatrix;
        Matrix projMatrix;
        //Модель
        Model pen;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь логику инициализации

            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            //Загрузка модели
            pen = Content.Load<Model>("ballpen");
            //Соотношение сторон
            float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
               (float)graphics.GraphicsDevice.Viewport.Height;
            //Устанавливаем камеру
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 30, 80), new Vector3(-32, 0, 0), new Vector3(0, 1, 0));
            //Устанавливаем проекционную матрицу
            projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 1000.0f);
        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            //Перемещение частей ручки
            PenMove();
            base.Update(gameTime);
        }
        void PenMove()
        {
            //Поворот зажима - он представлен родительской костью сети №3
            //Умножим его матрицу трансформации на матрицу поворота по оси X
            pen.Meshes[3].ParentBone.Transform = pen.Meshes[3].ParentBone.Transform * 
                   Matrix.CreateRotationX(MathHelper.ToRadians(-1.0f));

            TouchCollection touchLocations = TouchPanel.GetState();
            foreach (TouchLocation touchLocation in touchLocations)
            {
                //Кнопка ручки перемещается при прикосновении к экрану
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    //При прикосновении к левой части экрана
                    //выходит из ручки
                    if (touchLocation.Position.X < 400)
                    {
                        //Умножим матрицу трансформации родительской костью сети №1
                        //На матрицу трансляции, перемещающей ее на 2 позиции влево по оси Х
                        pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform * 
                                Matrix.CreateTranslation(new Vector3(-20, 0, 0));
                    }
                    //При прикосновении к правой части экрана - входит в ручку
                    else
                    {
                        //Умножим матрицу трансформации родительской кости сети №1
                        //На матрицу трансляции, перемещающую ее на 2 позиции по оси X вправо
                        pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform * 
                              Matrix.CreateTranslation(new Vector3(20, 0, 0));
                    }
                }
            }

        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            //Новый массив матриц размером, соответствующим количеству
            //костей в скелете модели
            Matrix[] absoluteTransformations = new Matrix[pen.Bones.Count];
            //Скопировать матрицы трансформации костей в массив матриц
            pen.CopyAbsoluteBoneTransformsTo(absoluteTransformations);

            foreach (ModelMesh mesh in pen.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.LightingEnabled = true;
                    effect.EnableDefaultLighting();
                    effect.Projection = projMatrix;
                    effect.View = viewMatrix;
                    //Установим новую мировую матрицу для родительской кости текущей сети
                    //это приводит к перемещению кнопки и к перемещению зажима
                    //Так же здесь мы уменьшаем модель, применяя коэффициент масштабирования 0,15
                    effect.World = absoluteTransformations[mesh.ParentBone.Index] * Matrix.CreateScale(0.15f);
                }
                //Выводим подготовленную сеть
                mesh.Draw();
            }

            base.Draw(gameTime);
        }
    }
}

На рис. 27.1 вы можете видеть игровой экран проекта.

(рис 27.1) Игровой экран проекта

27.2. Эффекты, EnvironmentMapEffect

Ранее для вывода трехмерных изображений мы пользовались шейдером BasicEffect. Он, действительно, очень часто используется и при разработке реальных игр. Однако, XNA имеет еще несколько встроенных шейдеров, в частности, SkinnedEffect, DualTextureEffect, AlphaTestEffect и EnvironmentMapEffect. Последним мы воспользуемся в нашем следующем примере для вывода трехмерного объекта, в котором отражается окружающий мир.

Прежде чем заниматься этим проектом, нам понадобится кубическая карта окружающего мира. Такую карту можно создать самостоятельно. Мы воспользовались готовыми изображениями с сайта http://www.humus.name/index.php?page=Textures. Здесь они представлены в разрешении 2048х2048, на рис. 27.2 приведена развёртка кубической карты, которой мы хотим воспользоваться в проекте.

(рис 27.2) Развертка кубической карты

Для того, чтобы собрать из этих изображений кубическую карту, мы можем воспользоваться различными инструментами, в частности, в нашем случае этим инструментом стал Adobe Photoshop CS с установленным плагином Nvidia Textute Tools. Загрузить плагин можно здесь: http://developer.nvidia.com/nvidia-texture-tools-adobe-photoshop.

После установки плагина и подготовки изображений, нужно, в нашем случае, создать новое изображение размером 12288х2048 пикселей. Ширина изображения определяется произведением разрешения исходных изображений (2048) и их количеством. Мы имеем 6 изображений, для каждой из поверхностей кубической карты. Далее, нужно расположить эти изображения в следующем порядке:

Правое, Левое, Верхнее, Нижнее, Переднее, Заднее.

В итоге у нас получилось такое изображение для последующей конверсии в кубическую карту, рис. 27.3.

(рис 27.3) Изображения кубической карты, подготовленные в Photoshop

Высота наших изображений – 2048, для работы нам понадобится карта, собранная из фрагментов по 512 пикселей в высоту. Изменим размер изображения (Image > Image Size) таким образом, чтобы его высота составила 512 пикселей, ширина, соответственно 3072 пикселя.

Учитывая то, что у нас уже установлены дополнения от Nvidia, выполним команду File > Save as и в появившемся окне выберем формат файла D3D/DDS. Появится окно, в котором нужно настроить параметры выходного файла. Эти настройки важны, на рис. 27.4 приведено окно с правильными настройками.

(рис 27.4) Настройки для сохранения кубической карты

После того, как карта сохранена, мы можем загружать её в проект. Кроме того, в проекте мы используем изображение для создания фона – мы вырезали один из фрагментов изображения, используемого для создания кубической карты. Далее, здесь же мы используем трехмерную модель. В целом, проект подготовлен с использованием примера Microsoft, распространяемого по лицензии MS-PL, оттуда же взята и трехмерная модель.

На рис. 27.5 приведено окно Обозреватель решений для проекта P20_2.

(рис 27.5) Обозреватель решений

Мы собираемся применять при выводе модели шейдер EnvironmentMapEffect, прежде чем заработает код, использующий этот шейдер, нам понадобится указать его в поле Эффект по умолчанию окна настройки свойств трехмерной модели, рис. 27.6.

(рис 27.6) Настройка свойств модели

Кроме того, в проект мы добавили текстуру для модели, однако, текстура обрабатывается средствами файла модели, поэтому нужно отключить для неё параметры Обработчик контента в значение Обработка не требуется и Действие при построении в значение Нет. На самом деле, текстуру можно было бы не включать в состав контента, просто скопировав в папку, в которой хранится файл с моделью в структуре папок нашего проекта.

После вышеописанной подготовки всё готово к реализации примера, его код приведен в листинге 27.2

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace P20_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        //Для вывода двумерных изображений
        SpriteBatch spriteBatch;
        //Трёхмерная модель
        Model model;
        //Фон
        Texture2D background;
        //Кубическая карта окружающей среды
        TextureCube cubeMap;
        //Показатели, влияющие на наложение карты среды
        //Цвет  
        float amount = 1.0f;
        //Коэффициент Френеля 
        float fresnel = 0.1f;
        //Альфа-фактор
        float specular = 0.3f;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
            //Параметры экрана
            graphics.PreferredBackBufferWidth = 480;
            graphics.PreferredBackBufferHeight = 800;
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            //Фон
            background = Content.Load<Texture2D>("back");
            // Модель
            model = Content.Load<Model>("saucer");
            //Кубическая карта
            cubeMap = Content.Load<TextureCube>("cubemap");

        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // Вывод фонового изображения
            spriteBatch.Begin(0, BlendState.Opaque);
            spriteBatch.Draw(background, new Rectangle(0, 0, 480, 800), Color.White);
            spriteBatch.End();

            //// Вычисление матриц для управления камерой
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            Matrix rotation = Matrix.CreateRotationX(time * 0.3f) *
                              Matrix.CreateRotationY(time);

            Matrix view = Matrix.CreateLookAt(new Vector3(4500, -400, 0),
                                              new Vector3(0, -400, 0),
                                              Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    GraphicsDevice.Viewport.AspectRatio,
                                                                    10, 10000);

            Matrix[] transforms = new Matrix[model.Bones.Count];

            model.CopyAbsoluteBoneTransformsTo(transforms);
            //Настройка параметров графического устройства
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

            //Вывод модели с использованием EnvironmentMapEffect
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (EnvironmentMapEffect effect in mesh.Effects)
                {
                    
                    Matrix world = transforms[mesh.ParentBone.Index] * rotation;
                    //Настройка параметров эффекта
                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                    effect.EnvironmentMap = cubeMap;
                    effect.EnableDefaultLighting();
                    effect.EnvironmentMapAmount = amount;
                    effect.FresnelFactor = fresnel * 2;
                    effect.EnvironmentMapSpecular = new Vector3(1, 1, 0.5f) * specular;
                }
                mesh.Draw();
            }

            base.Draw(gameTime);
        }
    }
}

На рис. 27.7 вы можете видеть игровое окно проекта.

(рис 27.7) Игровое окно проекта

27.3. Выводы

В этой лекции мы рассмотрели приёмы скелетной анимации персонажей, создали кубическую карту окружающей среды и ознакомились с применением EnvironmentMapEffect для создания эффекта зеркального объекта, в котором отражается окружающий мир.

27.4. Задание

Найдите или создайте скелетную трехмерную модель и анимируйте её в зависимости от особенностей модели. Самостоятельно изучите возможности эффектов SkinnedEffect, DualTextureEffect, AlphaTestEffect.

27.5. Дополнительные материалы

К данной лабораторной работе прилагается видеоурок

и демонстрационный проект.

Страницы:

Цель работы: Научиться применять графические эффекты и приемы анимации при работе с трехмерной графикой в XNA

27.1. Анимация персонажей

Компьютерные игры наполнены анимированными персонажами. XNA содержит встроенные средства, поддерживающие базовые операции по анимации трехмерных персонажей. В частности, это средства для работы с так называемым скелетом персонажа. Скелет состоит из взаимосвязанных костей, управляя которыми можно анимировать модель. Создание анимированных моделей – это отдельная, достаточно трудоемкая задача, которая требует серьезных познаний в области 3D-моделирования. Для подготовки анимированных трехмерных моделей вы можете использовать практически любой 3D-редактор.

XNA содержит лишь базовые средства для управления скелетами объектов. Так, мы можем получить коллекцию костей объекта и проводить с ними какие-либо преобразования, вызывающие движение модели. Однако, такой подход ограничен – с его использованием весьма сложно создать реалистичное движение для сложных моделей. На основе базовых средств XNA разработаны библиотеки кода, которые содержат компоненты, позволяющие анимировать персонажи на более высоком уровне. В частности, одну из таких библиотек – XNAAnimation – можно найти на сайте http://www.codeplex.com/xnanimation.

Здесь мы рассмотрим базовый подход к анимации модели. Он заключается в следующем. Можно менять параметры отдельных костей модели и выводить ее на экран с учетом этих изменений. Мы использовали бесплатную модель Ballpen.fbx, взятую с сайта http://www.turbosquid.com.

Создадим новый проект P18_1. Загрузим в него модель Ballpen.fbx.

Код проекта сосредоточен в классе Game1.cs, его код вы можете найти в листинге 27.1.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace P20_1
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        //Матрицы
        Matrix viewMatrix;
        Matrix projMatrix;
        //Модель
        Model pen;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь логику инициализации

            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            //Загрузка модели
            pen = Content.Load<Model>("ballpen");
            //Соотношение сторон
            float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
               (float)graphics.GraphicsDevice.Viewport.Height;
            //Устанавливаем камеру
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 30, 80), new Vector3(-32, 0, 0), new Vector3(0, 1, 0));
            //Устанавливаем проекционную матрицу
            projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 1000.0f);
        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            //Перемещение частей ручки
            PenMove();
            base.Update(gameTime);
        }
        void PenMove()
        {
            //Поворот зажима - он представлен родительской костью сети №3
            //Умножим его матрицу трансформации на матрицу поворота по оси X
            pen.Meshes[3].ParentBone.Transform = pen.Meshes[3].ParentBone.Transform * 
                   Matrix.CreateRotationX(MathHelper.ToRadians(-1.0f));

            TouchCollection touchLocations = TouchPanel.GetState();
            foreach (TouchLocation touchLocation in touchLocations)
            {
                //Кнопка ручки перемещается при прикосновении к экрану
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    //При прикосновении к левой части экрана
                    //выходит из ручки
                    if (touchLocation.Position.X < 400)
                    {
                        //Умножим матрицу трансформации родительской костью сети №1
                        //На матрицу трансляции, перемещающей ее на 2 позиции влево по оси Х
                        pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform * 
                                Matrix.CreateTranslation(new Vector3(-20, 0, 0));
                    }
                    //При прикосновении к правой части экрана - входит в ручку
                    else
                    {
                        //Умножим матрицу трансформации родительской кости сети №1
                        //На матрицу трансляции, перемещающую ее на 2 позиции по оси X вправо
                        pen.Meshes[1].ParentBone.Transform = pen.Meshes[1].ParentBone.Transform * 
                              Matrix.CreateTranslation(new Vector3(20, 0, 0));
                    }
                }
            }

        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            //Новый массив матриц размером, соответствующим количеству
            //костей в скелете модели
            Matrix[] absoluteTransformations = new Matrix[pen.Bones.Count];
            //Скопировать матрицы трансформации костей в массив матриц
            pen.CopyAbsoluteBoneTransformsTo(absoluteTransformations);

            foreach (ModelMesh mesh in pen.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.LightingEnabled = true;
                    effect.EnableDefaultLighting();
                    effect.Projection = projMatrix;
                    effect.View = viewMatrix;
                    //Установим новую мировую матрицу для родительской кости текущей сети
                    //это приводит к перемещению кнопки и к перемещению зажима
                    //Так же здесь мы уменьшаем модель, применяя коэффициент масштабирования 0,15
                    effect.World = absoluteTransformations[mesh.ParentBone.Index] * Matrix.CreateScale(0.15f);
                }
                //Выводим подготовленную сеть
                mesh.Draw();
            }

            base.Draw(gameTime);
        }
    }
}

На рис. 27.1 вы можете видеть игровой экран проекта.

(рис 27.1) Игровой экран проекта

27.2. Эффекты, EnvironmentMapEffect

Ранее для вывода трехмерных изображений мы пользовались шейдером BasicEffect. Он, действительно, очень часто используется и при разработке реальных игр. Однако, XNA имеет еще несколько встроенных шейдеров, в частности, SkinnedEffect, DualTextureEffect, AlphaTestEffect и EnvironmentMapEffect. Последним мы воспользуемся в нашем следующем примере для вывода трехмерного объекта, в котором отражается окружающий мир.

Прежде чем заниматься этим проектом, нам понадобится кубическая карта окружающего мира. Такую карту можно создать самостоятельно. Мы воспользовались готовыми изображениями с сайта http://www.humus.name/index.php?page=Textures. Здесь они представлены в разрешении 2048х2048, на рис. 27.2 приведена развёртка кубической карты, которой мы хотим воспользоваться в проекте.

(рис 27.2) Развертка кубической карты

Для того, чтобы собрать из этих изображений кубическую карту, мы можем воспользоваться различными инструментами, в частности, в нашем случае этим инструментом стал Adobe Photoshop CS с установленным плагином Nvidia Textute Tools. Загрузить плагин можно здесь: http://developer.nvidia.com/nvidia-texture-tools-adobe-photoshop.

После установки плагина и подготовки изображений, нужно, в нашем случае, создать новое изображение размером 12288х2048 пикселей. Ширина изображения определяется произведением разрешения исходных изображений (2048) и их количеством. Мы имеем 6 изображений, для каждой из поверхностей кубической карты. Далее, нужно расположить эти изображения в следующем порядке:

Правое, Левое, Верхнее, Нижнее, Переднее, Заднее.

В итоге у нас получилось такое изображение для последующей конверсии в кубическую карту, рис. 27.3.

(рис 27.3) Изображения кубической карты, подготовленные в Photoshop

Высота наших изображений – 2048, для работы нам понадобится карта, собранная из фрагментов по 512 пикселей в высоту. Изменим размер изображения (Image > Image Size) таким образом, чтобы его высота составила 512 пикселей, ширина, соответственно 3072 пикселя.

Учитывая то, что у нас уже установлены дополнения от Nvidia, выполним команду File > Save as и в появившемся окне выберем формат файла D3D/DDS. Появится окно, в котором нужно настроить параметры выходного файла. Эти настройки важны, на рис. 27.4 приведено окно с правильными настройками.

(рис 27.4) Настройки для сохранения кубической карты

После того, как карта сохранена, мы можем загружать её в проект. Кроме того, в проекте мы используем изображение для создания фона – мы вырезали один из фрагментов изображения, используемого для создания кубической карты. Далее, здесь же мы используем трехмерную модель. В целом, проект подготовлен с использованием примера Microsoft, распространяемого по лицензии MS-PL, оттуда же взята и трехмерная модель.

На рис. 27.5 приведено окно Обозреватель решений для проекта P20_2.

(рис 27.5) Обозреватель решений

Мы собираемся применять при выводе модели шейдер EnvironmentMapEffect, прежде чем заработает код, использующий этот шейдер, нам понадобится указать его в поле Эффект по умолчанию окна настройки свойств трехмерной модели, рис. 27.6.

(рис 27.6) Настройка свойств модели

Кроме того, в проект мы добавили текстуру для модели, однако, текстура обрабатывается средствами файла модели, поэтому нужно отключить для неё параметры Обработчик контента в значение Обработка не требуется и Действие при построении в значение Нет. На самом деле, текстуру можно было бы не включать в состав контента, просто скопировав в папку, в которой хранится файл с моделью в структуре папок нашего проекта.

После вышеописанной подготовки всё готово к реализации примера, его код приведен в листинге 27.2

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace P20_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        //Для вывода двумерных изображений
        SpriteBatch spriteBatch;
        //Трёхмерная модель
        Model model;
        //Фон
        Texture2D background;
        //Кубическая карта окружающей среды
        TextureCube cubeMap;
        //Показатели, влияющие на наложение карты среды
        //Цвет  
        float amount = 1.0f;
        //Коэффициент Френеля 
        float fresnel = 0.1f;
        //Альфа-фактор
        float specular = 0.3f;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
            //Параметры экрана
            graphics.PreferredBackBufferWidth = 480;
            graphics.PreferredBackBufferHeight = 800;
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            //Фон
            background = Content.Load<Texture2D>("back");
            // Модель
            model = Content.Load<Model>("saucer");
            //Кубическая карта
            cubeMap = Content.Load<TextureCube>("cubemap");

        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // Вывод фонового изображения
            spriteBatch.Begin(0, BlendState.Opaque);
            spriteBatch.Draw(background, new Rectangle(0, 0, 480, 800), Color.White);
            spriteBatch.End();

            //// Вычисление матриц для управления камерой
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            Matrix rotation = Matrix.CreateRotationX(time * 0.3f) *
                              Matrix.CreateRotationY(time);

            Matrix view = Matrix.CreateLookAt(new Vector3(4500, -400, 0),
                                              new Vector3(0, -400, 0),
                                              Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    GraphicsDevice.Viewport.AspectRatio,
                                                                    10, 10000);

            Matrix[] transforms = new Matrix[model.Bones.Count];

            model.CopyAbsoluteBoneTransformsTo(transforms);
            //Настройка параметров графического устройства
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

            //Вывод модели с использованием EnvironmentMapEffect
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (EnvironmentMapEffect effect in mesh.Effects)
                {
                    
                    Matrix world = transforms[mesh.ParentBone.Index] * rotation;
                    //Настройка параметров эффекта
                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                    effect.EnvironmentMap = cubeMap;
                    effect.EnableDefaultLighting();
                    effect.EnvironmentMapAmount = amount;
                    effect.FresnelFactor = fresnel * 2;
                    effect.EnvironmentMapSpecular = new Vector3(1, 1, 0.5f) * specular;
                }
                mesh.Draw();
            }

            base.Draw(gameTime);
        }
    }
}

На рис. 27.7 вы можете видеть игровое окно проекта.

(рис 27.7) Игровое окно проекта

27.3. Выводы

В этой лекции мы рассмотрели приёмы скелетной анимации персонажей, создали кубическую карту окружающей среды и ознакомились с применением EnvironmentMapEffect для создания эффекта зеркального объекта, в котором отражается окружающий мир.

27.4. Задание

Найдите или создайте скелетную трехмерную модель и анимируйте её в зависимости от особенностей модели. Самостоятельно изучите возможности эффектов SkinnedEffect, DualTextureEffect, AlphaTestEffect.

27.5. Дополнительные материалы

К данной лабораторной работе прилагается видеоурок

и демонстрационный проект.

Вернуться к учебному плану