netlib.narod.ru< Назад | Оглавление | Далее >

В путь

Как вы могли предположить, мы начнем с создания проекта Windows Game с именем RockRain. Solution Explorer в вашей Visual Studio должен выглядеть как на рис. 3.2.


Рис. 3.2. Окно XNA Game Solution с созданным проектом Rock Rain

Рис. 3.2. Окно XNA Game Solution с созданным проектом Rock Rain


Папка Content — это специальный элемент в проектах XNA Game. Сюда вы поместите все игровые ресурсы, такие как изображения, звуки и т.д., — то есть все, что должно быть загружено через конвейер содержимого.

Рисуем фон

Начнем с размещения фона в вашей игре. Для космической игры нет ничего лучше, чем изображение галактики! Добавьте файл SpaceBackground.dds в папку Content. Вы найдете этот файл в области Source Code/Download на сайте издательства Apress http://www.apress.com.

Теперь вы должны загрузить эту текстуру так, чтобы она занимала весь экран игры. Сначала определите в вашем коде текстуру. Добавьте следующий атрибут в ваш класс Game1:

// Фоновая текстура
private Texture2D backgroundTexture;

Вы должны загрузить текстуру, используя объект spriteBatch, как было показано в предыдущей главе. Сперва объявим его в классе Game1:

private SpriteBatch spriteBatch = null;

Сейчас, как вы видели в предыдущей главе, вы загружаете эту текстуру и инициализируете объект spriteBatch в методе LoadContent:

// Создание нового SpriteBatch,
// который будет использован для рисования текстур
spriteBatch = new SpriteBatch(GraphicsDevice);

// Загрузка всех текстур
backgroundTexture =
       content.Load<Texture2D>("SpaceBackground");
}

И, наконец, вы можете нарисовать фон. Добавьте следующий код в метод Draw класса Game1:

// Рисуем текстуру фона в отдельном проходе
spriteBatch.Begin();
spriteBatch.Draw(backgroundTexture,new Rectangle(0, 0,
                 graphics.GraphicsDevice.DisplayMode.Width,
                 graphics.GraphicsDevice.DisplayMode.Height),
                 Color.LightGray);
spriteBatch.End();

Теперь вы можете запустить игру, нажав F5. Если все сделано правильно, результат должен быть похож на рис. 3.3.


Рис. 3.3. Фон Rock Rain

Рис. 3.3. Фон Rock Rain


Создание игрового компонента для игрока

Игрок представлен в игре как маленький космический корабль, управляемый с помощью игрового пульта Xbox 360 или клавиатуры PC. Изображение космического корабля находится в файле RockRain.png. Добавьте его к проекту в папку Content. Эта текстура содержит изображения космического корабля игрока и астероидов, которых игрок должен избегать (рис. 3.4).


Рис. 3.4. Текстура игрока и астероида

Рис. 3.4. Текстура игрока и астероида


Как мы поступали с фоном, сперва объявим текстуру в классе Game1:

private Texture2D meteorTexture;

Затем загрузим ее в методе LoadContent сразу после загрузки текстуры фона:

meteorTexture = content.Load<Texture2D>("RockRain");

 

ПРИМЕЧАНИЕ
Графика в этой и следующей главе построена с использованием SpriteLIB GPL, коллекции статических и анимированных графических объектов (общеизвестных также как спрайты), расположенной на сайте http://www.flyingyogi.com/fun/spritelib.html.

Сейчас вы создадите класс, представляющий в игре игрока. Добавьте к проекту новый GameComponent, назовите файл Ship.cs (как на рис. 3.5) и щелкните OK. К проекту будет добавлен новый файл, содержащий класс, наследуемый от GameComponent. Этот GameComponent будет видим в игре; следовательно его надо будет рисовать. Для этого выполним наследование от DrawableGameComponent, чтобы у нас был метод Draw, который можно использовать в игре для рисования.


Рис. 3.5. Добавление нового GameComponent

Рис. 3.5. Добавление нового GameComponent


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

Что касается поведения компонента, он должен только перемещаться под управлением игрового пульта Xbox 360 или клавиатуры, и проверять, что картинка остается «внутри» экрана. Космический корабль не должен исчезать, покидая заданные границы игрового окна.

Итак, вы четко определили две части DrawableGameComponent. В методе Draw вы копируете картинку космического корабля на экран, а в методе Update вы обновляете экран в соответствии с состоянием игрового пульта Xbox 360 или клавиатуры. Код класса выглядит так:

#region Инструкции Using
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion

namespace RockRain
{
    /// <summary>
    /// Игровой компонент, реализующий корабль игрока 
    /// </summary>
    public class Ship : Microsoft.Xna.Framework.DrawableGameComponent
    {
        protected Texture2D texture;
        protected Rectangle spriteRectangle;
        protected Vector2   position;

        // Ширина и высота спрайта в текстуре
        protected const int SHIPWIDTH  = 30;
        protected const int SHIPHEIGHT = 30;

        // Экранная область
        protected Rectangle screenBounds;

        public Ship(Game game, ref Texture2D theTexture)
                    : base(game)
        {
            texture = theTexture;
            position = new Vector2();

            // Создаем прямоугольник источника. Он показывает,
            // где картинка спрайта расположена на поверхности
            spriteRectangle = new Rectangle(31, 83, SHIPWIDTH, SHIPHEIGHT);

#if XBOX360
            // На Xbox 360 надо позаботиться о "безопасной"
            // области телевизионного экрана 
            screenBounds = new Rectangle(
                     (int)(Game.Window.ClientBounds.Width * 0.03f),
                     (int)(Game.Window.ClientBounds.Height * 0.03f),
                     Game.Window.ClientBounds.Width -
                         (int)(Game.Window.ClientBounds.Width * 0.03f),
                     Game.Window.ClientBounds.Height -
                         (int)(Game.Window.ClientBounds.Height * 0.03f));
#else
            screenBounds = new Rectangle(0, 0,
                     Game.Window.ClientBounds.Width,
                     Game.Window.ClientBounds.Height);
#endif
        }

        /// <summary>
        /// Помещаем корабль в стартовую позицию на экране
        /// </summary>
        public void PutinStartPosition()
        {
            position.X = screenBounds.Width / 2;
            position.Y = screenBounds.Height - SHIPHEIGHT;
        }

        /// <summary>
        /// Обновляем местоположение корабля
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            // Перемещаем корабль с пульта Xbox
            GamePadState gamepadstatus = GamePad.GetState(PlayerIndex.One);
            position.Y += (int)((gamepadstatus.ThumbSticks.Left.Y * 3) * -2);
            position.X += (int)((gamepadstatus.ThumbSticks.Left.X * 3) * 2);

            // Перемещаем корабль с клавиатуры
            KeyboardState keyboard = Keyboard.GetState();

            if (keyboard.IsKeyDown(Keys.Up))
            {
                position.Y -= 3;
            }
            if (keyboard.IsKeyDown(Keys.Down))
            {
                position.Y += 3;
            }
            if (keyboard.IsKeyDown(Keys.Left))
            {
                position.X -= 3;
            }
            if (keyboard.IsKeyDown(Keys.Right))
            {
                position.X += 3;
            }

            // Сохраняем корабль внутри экрана
            if (position.X < screenBounds.Left)
            {
                position.X = screenBounds.Left;
            }
            if (position.X > screenBounds.Width - SHIPWIDTH)
            {
                position.X = screenBounds.Width - SHIPWIDTH;
            }
            if (position.Y < screenBounds.Top)
            {
                position.Y = screenBounds.Top;
            }
            if (position.Y > screenBounds.Height - SHIPHEIGHT)
            {
                position.Y = screenBounds.Height - SHIPHEIGHT;
            }

             base.Update(gameTime);
         }

        /// <summary>
        /// Рисуем спрайт корабля
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // Получаем текущий пакет спрайтов
            SpriteBatch sBatch =
                (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            // Рисуем корабль
            sBatch.Draw(texture, position, spriteRectangle, Color.White);

            base.Draw(gameTime);
        }

        /// <summary>
        /// Получение ограничивающего прямоугольника корабля на экране 
        /// </summary>
        public Rectangle GetBounds()
        {
            return new Rectangle((int)position.X, (int)position.Y,
                                 SHIPWIDTH, SHIPHEIGHT);
        }
    }
}

Обратите внимание, что в методе Draw вы не создаете SpriteBatch, как делали, когда визуализировали фоновую текстуру. В идеале (согласно концепции «пакета») вы не должны постоянно создавать и уничтожать объекты SpriteBatch, поскольку это ставит под угрозу быстродействие приложения. Вы должны создать «глобальный» SpriteBatch и использовать его в ваших классах. Однако это создает жесткую связь между вашим GameComponent и глобальным атрибутом конкретной игры (что нежелательно). XNA имеет замечательное решение, позволяющее предоставлять такие «глобальные» объекты и в то же время обеспечивать легкое повторное использование кода компонентов: игровые службы (game services).

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

В данном случае метод Draw ищет активный SpriteBatch непосредственно в GameServices и использует его для рисования себя на экране. Конечно, кто-то должен добавить этот SpriteBatch в GameServices. Итак, добавьте следующий код сразу после создания SpriteBatch в методе LoadContent класса Game1.

// Добавляем службу SpriteBatch 
Services.AddService(typeof(SpriteBatch), spriteBatch);

Все игровые компоненты вашей игры будут использовать этот SpriteBatch.

Давайте немного поговорим о классе. Метод Update проверяет клавиатуру и игровой пульт Xbox 360 для обновления атрибута Position и изменения местоположения космического корабля на экране. В этом методе вы также проверяете, находится ли космический корабль внутри границ экрана. Если нет, код возвращает корабль внутрь видимой области экрана.

Метод GetBound просто возвращает прямоугольник, являющийся границами изображения космического корабля на экране. Позже вы используете этот прямоугольник для проверки столкновений с астероидами. И, наконец, PutinStartPosition помещает космический корабль в начальную позицию, расположенную в центре нижней части экрана. Этот метод вызывается, когда вам надо поместить корабль в исходную позицию, например, в начале нового раунда.

Давайте теперь проверим наш GameComponent. Создайте метод Start, который будет использоваться для инициализации игровых объектов (на данный момент только объекта игрока), как показано в следующем коде:

/// <summary>
/// Инициализация игрового раунда
/// </summary>
private void Start()
{
    // Создаем (если необходимо) игрока
    // и помещаем его в начальную позицию
    if (player == null)
    {
        // Добавляем компонент игрока
        player = new Ship(this, ref meteorTexture);
        Components.Add(player);
    }
    player.PutinStartPosition();
}

Заметьте, что атрибут player содержит ссылку на GameComponent игрока. Вам также надо добавить этот компонент к списку компонентов в самом Game, чтобы у XNA была возможность вызывать методы Draw и Update этого объекта в игре, как вы видели в предыдущей главе.

И, наконец, объявим атрибут player в классе Game1:

private Ship player;

Теперь давайте на немного вернемся к игровой логике в целом. Игровая логика обычно реализуется в методе Update класса Game. В вашем случае можно начать со следующего кода:

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

    if ((gamepadstatus.Buttons.Back == ButtonState.Pressed) ||
        (keyboard.IsKeyDown(Keys.Escape)))
    {
        Exit();
    }

    // Запуск, если еще не запущено
    if (player == null)
    {
        Start();
    }

    // Обновление всех других компонентов
    base.Update(gameTime);
}

В начале код проверяет, нажал ли пользователь клавишу Esc или кнопку Back на игровом пульте Xbox 360, что приводит к завершению игры. Затем, если необходимо, код запускает игру через метод Start.

Одна деталь осталась забыта. Метод Draw вашей игры рисует только фон. Вам надо сделать, чтобы он рисовал все остальные GameComponent игры, так что добавьте следующий код сразу после кода, рисующего фон:

// Начало визуализации спрайтов
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

// Рисуем игровые компоненты (включая спрайты)
base.Draw(gameTime);

// Конец визуализации спрайтов
spriteBatch.End();

Сохраните и выполните код. Теперь вы можете перемещать космический корабль по экрану с помощью игрового пульта Xbox 360 или клавиш управления курсором на PC. Заметьте, что вся логика перемещения космического корабля обрабатывается его собственным, созданным вами, компонентом, однако XNA автоматически вызывает его метод Update через вызов base.Update() класса Game1. Создавая астероиды вы руководствуетесь теми же принципами. Различие в том, что игрок не может перемещать астероиды.

Создание астероидов

Концепции, использованные при создании компонента для игрока остаются неизменными и при создании астероидов. Единственное различие в том, что начальная позиция астероида и его перемещение зависят от случайного фактора. Код для астероидов такой:

#region Инструкции Using
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace FirstGame
{
    /// <summary>
    /// Игровой компонент, реализующий камни,
    /// которых игрок должен избегать
    /// </summary>
    public class Meteor : Microsoft.Xna.Framework.DrawableGameComponent
    {
        protected Texture2D texture;
        protected Rectangle spriteRectangle;
        protected Vector2 position;
        protected int Yspeed;
        protected int Xspeed;
        protected Random random;

        // Ширина и высота спрайта в текстуре
        protected const int METEORWIDTH = 45;
        protected const int METEORHEIGHT = 45;

        public Meteor(Game game, ref Texture2D theTexture)
                     : base(game)
        {
            texture = theTexture;
            position = new Vector2();

            // Создаем прямоугольник источника. Он показывает, где
            // картинка спрайта расположена на поверхности 
            spriteRectangle = new Rectangle(20, 16, METEORWIDTH, METEORHEIGHT);

            // Инициализация генератора случайных чисел
            // и помещение астероида в его исходную позицию
            random = new Random(this.GetHashCode());
            PutinStartPosition();
        }

        /// <summary>
        /// Инициализация местоположения и скорости астероида
        /// </summary>
        protected void PutinStartPosition()
        {
            position.X = random.Next(
                        Game.Window.ClientBounds.Width - METEORWIDTH);
            position.Y = 0;

            Yspeed = 1 + random.Next(9);
            Xspeed = random.Next(3) - 1;
        }

        /// <summary>
        /// Позволяет игровому компоненту рисовать содержимое на экране игры
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // Получаем текущий пакет спрайтов
            SpriteBatch sBatch =
                (SpriteBatch) Game.Services.GetService(typeof(SpriteBatch));

            // Рисуем астероид
            sBatch.Draw(texture, position, spriteRectangle, Color.White);

            base.Draw(gameTime);
        }

        /// <summary>
        /// Позволяет игровому компоненту обновлять себя 
        /// </summary>
        /// <param name="gameTime">Предоставляет снимок значения таймера</param>
        public override void Update(GameTime gameTime)
        {
            // Проверяем, видим ли астероид
            if ((position.Y >= Game.Window.ClientBounds.Height) ||
                (position.X >= Game.Window.ClientBounds.Width) ||
                (position.X <= 0))
            {
                PutinStartPosition();
            }

            // Перемещение астероида
            position.Y += Yspeed;
            position.X += Xspeed;

            base.Update(gameTime);
        }

        /// <summary>
        /// Проверка пересечения астероида с заданным прямоугольником
        /// </summary>
        /// <param name="rect">Проверяемый прямоугольник</param>
        /// <returns>true, если есть столкновение</returns>
        public bool CheckCollision(Rectangle rect)
        {
            Rectangle spriterect = new Rectangle(
                              (int)position.X, (int)position.Y,
                              METEORWIDTH,     METEORHEIGHT);
            return spriterect.Intersects(rect);
        }
    }
}

Обратите внимание, что метод PutinStartPosition помещает астероид в случайную позицию по горизонтали вверху экрана и также формирует вертикальную и горизонтальную скорости смещения астероида, которые используются для обновления в каждом вызове метода Update класса.

Обратите внимание, что метод CheckCollision проверяет, пересекается ли прямоугольник, описывающий астероид, с прямоугольником, переданным как параметр, и очевидно, что это будет прямоугольник, описывающий местоположение на экране космического корабла игрока.

Теперь поместим астероиды на экран. Добавьте следующий код в метод Start класса Game1:

// Добавляем астероиды
for (int i = 0; i < STARTMETEORCOUNT; i++)
{
    Components.Add(new Meteor(this, ref meteorTexture));
}

Константа STARTMETEORCOUNT определяет начальное количество появляющихся в игре астероидов. Объявите ее в классе Game1 следующим образом:

private const int STARTMETEORCOUNT = 10;

Запустите программу, нажав F5. Полюбуйтесь красотой метеоритного дождя. Обратите внимание, что каждый экземпляр компонента Meteor ведет себя независимо, точно так же, как и компонент Ship.

Создание игровой логики

Ваши игровые компоненты практически готовы. Теперь надо заставить работать их вместе. Когда космический корабль игрока сталкивается с астероидом, игра будет перезапускаться.

У вас есть метод Start, который инициализирует игровые компоненты, а теперь мы создадим метод с именем DoGameLogic, исполняющий собственно игровую логику. Сейчас этот метод будет только перебирать список игровых компонентов, проверяя, не столкнулся ли какой-нибудь астероид с космическим кораблем игрока. Если произошло столкновение, астероиды должны быть удалены из игры, чтобы их можно было заново создать в исходных позициях. Код таков:

/// <summary>
/// Выполнение игровой логики
/// </summary>
private void DoGameLogic()
{
    // Проверка столкновений
    bool hasCollision = false;
    Rectangle shipRectangle = player.GetBounds();

    foreach (GameComponent gc in Components)
    {
        if (gc is Meteor)
        {
            hasCollision = ((Meteor)gc).CheckCollision(shipRectangle);
            if (hasCollision)
            {
                // Удаление всех предыдущих астероидов
                RemoveAllMeteors();

                // Запускаем снова
                Start();

                break;
            }
        }
    }
}

/// <summary>
/// Удаление всех астероидов
/// </summary>
private void RemoveAllMeteors()
{
    for (int i = 0; i < Components.Count; i++)
    {
        if (Components[i] is Meteor)
        {
            Components.RemoveAt(i);
            i--;
        }
    }
}

Теперь вызовем метод DoGameLogic из метода Update класса Game1, непосредственно перед строкой, содержащей вызов base.Update(gameTime). Это вызывает вашу игровую логику внутри игрового цикла. Запустите программу и убедитесь, что когда космический корабль сталкивается с астероидом программа возвращает все объекты в их исходные позиции и продолжает этот цикл, пока пользователь не покинет приложение.

А сейчас немного усложним жизнь игроку. В вашей игре по прошествии определенного времени будет добавляться новый астероид. Поскольку все астероиды ведут себя независимо, вам надо только добавить к игре новый компонент Meteor, и он сделает все остальное. Это выполняется с помощью метода, показанного в приведенном ниже коде. Вызывайте этот метод из метода DoGameLogic после цикла foreach.

/// <summary>
/// Проверяем, пришло ли время для нового камня!
/// </summary>
private void CheckforNewMeteor()
{
    // Добавляем астероид каждые ADDMETEORTIME
    if ((System.Environment.TickCount - lastTickCount) > ADDMETEORTIME)
    {
        lastTickCount = System.Environment.TickCount;
        Components.Add(new Meteor(this, ref meteorTexture));
        rockCount++;
    }
}

Константа с именем ADDMETEORTIME представляет интервал в миллисекундах, через который должен быть добавлен новый астероид. Объявите ее в классе Game1 следующим образом:

private const int ADDMETEORTIME = 5000;

Эти 5 секунд (или 5000 миллисекунд) являются «магическим числом», и вы можете позже менять его, регулируя трудность игры. Два новых атрибута хранят количество добавленных астероидов (rockCount) и время для вычисления желаемого интервала (lastTickCount). Объявите их следующим образом:

private const int ADDMETEORTIME = 5000;
private int lastTickCount;
private int rockCount;

Вы должны инициализировать эти атрибуты в методе Start, так что добавьте к нему следующий код:

// Инициализация счетчика
lastTickCount = System.Environment.TickCount;

// Сброс счетчика камней
rockCount = STARTMETEORCOUNT;

Итак, каждые пять секунд в игру добавляется новый астероид. Запустите игру снова и посмотрите, как долго сумеете продержаться не столкнувшись с камнем.

Добавление звуков

Как вы видели в главе 2, добавление звуков к игре это больше чем просто добавление файлов WAV в ваш проект. Из-за различия форматов между PC и Xbox игры XNA могут использовать только файлы, созданные утилитой XACT.

Для Rock Rain вам нужны три файла WAV, которые вы найдете в разделе Source Code/Download на сайте издательства Apress http://www.apress.com.

Создайте новый проект XACT, и добавьте эти файлы WAV, чтобы создать волновой банк, звуковой банк (для Backmusic.wav) и звуковые реплики. Сохраните проект как audio.xap и добавьте его в папку Content вашего игрового проекта.

Прежде чем добавлять код для воспроизведения звуков, создайте еще один GameComponent, который поможет вам в выполнении этой задачи. Добавьте новый GameComponent и сохраните его как AudioComponent.cs. Сразу после этого добавьте следующий код:

#region Инструкции Using
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
#endregion

namespace RockRain
{
    /// <summary>
    /// Поддержка звука в игре
    /// </summary>
    public class AudioComponent : Microsoft.Xna.Framework.GameComponent
    {
        private AudioEngine audioEngine;
        private WaveBank waveBank;
        private SoundBank soundBank;

        public AudioComponent(Game game)
                : base(game)
        {
        }

        /// <summary>
        /// Позволяет игровому компоненту производить любую инициализацию,
        /// необходимую перед запуском. Здесь вы можете запросить любые
        /// необходимые службы и загрузить содержимое 
        /// </summary>
        public override void Initialize()
        {
            // Инициализация звукового движка
            audioEngine = new AudioEngine("Content\\audio.xgs");
            waveBank = new WaveBank(audioEngine, "Content\\Wave Bank.xwb");
            if (waveBank != null)
            {
                soundBank = new SoundBank(audioEngine, "Content\\Sound Bank.xsb");
            }

            base.Initialize();
        }

        /// <summary>
        /// Позволяет игровому компоненту обновлять себя
        /// </summary>
        /// <param name="gameTime">Предоставляет снимок значения таймера</param>
        public override void Update(GameTime gameTime)
        {
            audioEngine.Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// Воспроизведение реплики
        /// </summary>
        /// <param name="cue">Реплика для воспроизведения</param>
        public void PlayCue(string cue)
        {
            soundBank.PlayCue(cue);
        }
    }
}

Это простой компонент, но он поможет вам с вашими звуковыми эффектами. Этот класс просто инкапсулирует методы для загрузки и воспроизведения звука, которые вы видели в предыдущей главе. Прежде чем делать что-либо еще, объявите объект этого класса в классе Game1, чтобы вы могли использовать его:

// Звуковой помошник
private AudioComponent audioComponent;

Инициализируйте его в методе Initialize класса Game1:

audioComponent = new AudioComponent(this);
Components.Add(audioComponent);

// Запуск фоновой музыки
audioComponent.PlayCue("backmusic");

Также добавьте следующий код внутрь метода DoGameLogic, чтобы звук взрыва воспроизводился перед вызовом метода Start:

audioComponent.PlayCue("explosion");

Затем добавьте следующий код в CheckforNewMeteor сразу после строки, содержащей rockCount++, чтобы был слышен звук, когда к игре добавляется новый астероид:

audioComponent.PlayCue("newmeteor");

Запустите игру и посмотрите, как звуковые эффекты делают ее еще более увлекательной.

Добавляем табло со счетом

Табло счета Rock Rain показывает только текущее количество астероидов на экране. Как вы видели в предыдущей главе, сперва вам надо создать источник в игре и затем рисовать его. Заметьте, что табло со счетом — это типичный GameComponent, но чтобы показать, что создавать игровой компонент для этого совсем не обязательно, давайте нарисуем табло в методе Draw класса Game1.

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

private SpriteFont gameFont;

Инициализируйте объект в методе LoadGraphicsContent точно так же, как делали с другим содержимым — внутри инструкции if, как показано ниже:

// Загрузка игрового шрифта
gameFont = content.Load<SpriteFont>("Content\\font");

Теперь вам надо только нарисовать это сразу после рисования игровых спрайтов. Итак, добавьте следующий код в метод Draw класса Game1:

// Рисуем счет
spriteBatch.Begin();
spriteBatch.DrawString(gameFont, "Rocks: " + rockCount.ToString(),
                       new Vector2(15, 15), Color.YellowGreen);
spriteBatch.End();

Обратите внимание, что вы используете отдельный объект spriteBatch только для рисования табло. Таким образом этапы рисования фона, спрайтов и табло разделяются также и в видеокарте, что позволяет избежать «беспорядка» который могла бы испытать видеокарта.

ПРИМЕЧАНИЕ
Будьте бдительны, используя в ваших играх шрифты от сторонних производителей. Некоторые шрифты, такие как TrueType и OpenType, особенно шрифты используемые в Windows, не свободны от лицензионных отчислений и имеют ограничения, связанные с их использованием. Есть большое разнообразие свободно распространяемых шрифтов, которые могут быть получены на сайтах в Интернете.

Взболтай, детка!

Ваша игра почти готова. Давайте добавим к игре еще один эффект: вибрацию. Когда игрок врезается в астероид, в дополнение к звуку взрыва, мы сделаем вибрацию игрового пульта Xbox 360, чтобы можно было почувствовать произошедшее столкновение.

Как вы видели в предыдущей главе, включить и выключить вибрацию игрового пульта Xbox 360 можно с помощью метода SetVibration. Перейдем к созданию невизуального GameComponent, который поможет вам с этим эффектом. Итак, обычным образом добавим к проекту GameComponent и добавим следующий код:

#region Инструкции Using
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
#endregion

namespace FirstGame
{
    /// <summary>
    /// Этот компонент помогает трясти ваш пульт Xbox 360
    /// </summary>
    public class SimpleRumblePad : Microsoft.Xna.Framework.GameComponent
    {
        private int time;
        private int lastTickCount;

        public SimpleRumblePad(Game game)
                 : base(game)
        {
        }

        /// <summary>
        /// Позволяет игровому компоненту обновлять себя
        /// </summary>
        /// <param name="gameTime">Предоставляет снимок значения таймера</param>
        public override void Update(GameTime gameTime)
        {
            if (time > 0) {
                int elapsed = System.Environment.TickCount - lastTickCount;
                if (elapsed >= time)
                {
                    time = 0;
                    GamePad.SetVibration(PlayerIndex.One, 0, 0);
                }
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// Выключаем вибрацию
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            GamePad.SetVibration(PlayerIndex.One, 0, 0);

            base.Dispose(disposing);
        }

        /// <summary>
        /// Включаем вибрацию
        /// </summary>
        /// <param name="Time">Время вибрации</param>
        /// <param name="LeftMotor">Интенсивность левого двигателя</param>
        /// <param name="RightMotor">Интенсивность правого двигателя</param>
        public void RumblePad(int Time, float LeftMotor, float RightMotor)
        {
            lastTickCount = System.Environment.TickCount;
            time = Time;
            GamePad.SetVibration(PlayerIndex.One, LeftMotor, RightMotor);
        }
    }
}

В этом классе метод RumblePad получает в параметрах период времени, в течение которого пульт должен вибрировать и интенсивность вибрации левого и правого двигателей. Итак, снова как обычно добавьте следующее объявление в класс Game1:

// Эффект тряски
private SimpleRumblePad rumblePad;

Инициализируйте его в методе Initialize класса Game1:

rumblePad = new SimpleRumblePad(this);
Components.Add(rumblePad);

Включите вибрацию пульта сразу после воспроизведения звука взрыва в методе DoGameLogic:

// Встряхнуть!
rumblePad.RumblePad(500, 1.0f, 1.0f);

Поздравляю — вы только что закончили вашу первую игру!

Модификация и развертывание на Xbox 360

Вы знаете, что технология XNA позволяет вам создавать игры как для PC, так и для Xbox 360, так что если вы захотите сделать консольную версию Rock Rain, все, что для этого надо — создать копию данного проекта для Xbox 360. Просто щелкните правой кнопкой мыши по узлу проекта Rock Rain и выберите в меню команду Create Copy of Project for Xbox 360, как показано на рис. 3.6. Скомпилируйте его и все готово. Вы немедленно получите игру, которая работает на Xbox 360.


Рис. 3.6. Создание версии Rock Rain для Xbox 360

Рис. 3.6. Создание версии Rock Rain для Xbox 360


Однако, все не так просто. Во-первых, для развертывания ваших игр на Xbox 360, вам необходима подписка Creator's Club, которая позволит вашему PC корректно зарегистрировать консоль для коммуникации. Эта подписка платная и должна обновляться ежегодно или каждые три месяца. Помимо этого, для развертывания игр ваша консоль должна быть подключена к сети Xbox LIVE.

Также обратите внимание на различие между телевизорами (используемыми с консолями) и мониторами (используемыми с PC). На обычном мониторе PC у вас есть доступ ко всей области экрана, в то время как на телевизоре вы вынуждены использовать только часть, называемую безопасной областью (safe area). Если коротко, безопасная область — это термин, используемый в производстве телепрограмм для описания той части телевизионной картинки, которая видима на телевизионном экране.

Другими словами, не все, что вы поместите на экран, будет видимо на обычном телевизоре. Старые телевизоры показывают меньшую часть вне безопасной области, чем сделанные позднее. Плоские, плазменные и жидкокристаллические (LCD) экраны обычно могут показать большую часть «небезопасной» области.

Это приводит к проблеме касающейся краев экрана. Поскольку игрок не может выйти за пределы экрана, проблемой является точное определение видимых краев экрана. Обычно в игровой индустрии по физическим краям экрана оставляют от 3 до 5% неиспользуемого пространства.

Так, в ваш класс Ship, представляющий космический корабль игрока, в конструктор класса включен следующий код, в котором вы вычисляете размер экрана:

#if XBOX360
    // На Xbox 360 надо позаботиться о
    // "безопасной" области телевизионного экрана 
    screenBounds = new Rectangle((int)(Game.Window.ClientBounds.Width * 0.03f),
                                 (int)(Game.Window.ClientBounds.Height * 0.03f),
                                 Game.Window.ClientBounds.Width -
                                      (int)(Game.Window.ClientBounds.Width * 0.03f),
                                 Game.Window.ClientBounds.Height -
                                      (int)(Game.Window.ClientBounds.Height * 0.03f));
#else
    screenBounds = new Rectangle(0,0,Game.Window.ClientBounds.Width,
                                 Game.Window.ClientBounds.Height);
#endif

Вот что вы делаете: если проект предназначен для Xbox 360, компилируется код, который создает прямоугольник, определяющий края экрана с размером на 3 процента меньшим, чем прямоугольник в проекте PC, занимающий всю область монитора. Все просто.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz