Опубликован: 14.08.2012 | Уровень: специалист | Доступ: платный
Самостоятельная работа 8:

Взаимодействие объектов

14.2. Обработка столкновений автоматически перемещаемых объектов

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

Создадим новый стандартный игровой проект для XNA P8_2.

На рис. 14.55. вы можете видеть окно Обозреватель решений этого проекта.

Игровой экран проекта P8_2

Рис. 14.5. Игровой экран проекта P8_2

В листинге 14.4 вы можете видеть основной код игры

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.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace P8_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        //Текстура
        Texture2D texture;
        //Для работы со случайными числами
        Random randNum;

        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()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Services.AddService(typeof(SpriteBatch), spriteBatch);
            texture = Content.Load<Texture2D>("BallandBats");
            randNum = new Random();
            CreateNewObject();

        }
        //Новые компоненты добавляются в список, объектые переменные
        //для работы с ними мы не используем
        protected void CreateNewObject()
        {
            //Цикл от 1 до случайного числа в диапазоне 20,200
            for (int i = 0; i < randNum.Next(20, 200); i++)
            {
                //Добавляем в список компонентов новый компонент класса spriteComp
                Components.Add(new spriteComp(this, ref texture,
                new Rectangle(16, 203, 17, 17), i));
            }
        }

        /// <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.CornflowerBlue);

            // ЗАДАЧА: добавьте здесь код отрисовки

            spriteBatch.Begin();
            base.Draw(gameTime);
            spriteBatch.End();

        }
    }
}
Листинг 14.4. Код игры

Код игры предельно прост. Его главная функция заключается в загрузке ресурсов и создании игровых объектов. Их мы добавляем в список компонентов, не создавая объектных переменных. Основная логика объекта реализована в игровом компоненте, листинг 14.5.

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;


namespace P8_2
{
    /// <summary>
    /// Это игровой компонент, реализующий интерфейс IUpdateable.
    /// </summary>
    public class spriteComp : Microsoft.Xna.Framework.DrawableGameComponent
    {
        protected Texture2D sprTexture;
        protected Rectangle sprRectangle;
        protected Vector2 sprPosition;
        protected Rectangle scrBounds;
        //Для генерирования случайных чисел
        protected Random randNum;
        //Объект для доступа к основному игровому объекту
        protected Game myGame;
        //Цвет спрайта
        protected Color sprColor;
        //Скорость перемещения
        public Vector2 speed;

        public spriteComp(Game game, ref Texture2D newTexture,
            Rectangle newRectangle, int Seed)
            : base(game)
        {
            sprTexture = newTexture;
            sprRectangle = newRectangle;
            //Инициализируем счетчик
            randNum = new Random(Seed);
            myGame = game;
            scrBounds = new Rectangle(0, 0,
                game.Window.ClientBounds.Height,
                game.Window.ClientBounds.Width);
            //Устанавливаем стартовую позицию спрайта
            setSpriteToStart();
            //Если спрайт сталкивается с каким-нибудь спрайтом - изменим его позицию
            while (howManyCollides() > 0)
            {
                setSpriteToStart();
            }
            //Зададим случайный цвет для придания изображению
            //соответствующего оттенка
            sprColor = new Color((byte)randNum.Next(0, 255), (byte)randNum.Next(0, 255), (byte)randNum.Next(0, 255));
            //Переменная для хранения скорости пока пуста
            speed = new Vector2((float)randNum.Next(-5, 5), (float)randNum.Next(-5, 5));

            // ЗАДАЧА: здесь создаются дочерние компоненты
        }

        //Проверка, не установлены ли спрайты в позиции с перекрытием других спрайтов
        int howManyCollides()
        {
            int howMany = 0;
            foreach (spriteComp spr in myGame.Components)
            {
                if (this != spr)
                {
                    if (this.sprCollide(spr))
                    {
                        howMany++;
                    }
                }
            }
            return howMany;
        }
        //Установка спрайта в случайную стартовую позицию
        void setSpriteToStart()
        {
            sprPosition.X = (float)randNum.NextDouble() * (scrBounds.Width - sprRectangle.Width);
            sprPosition.Y = (float)randNum.NextDouble() * (scrBounds.Height - sprRectangle.Height);

        }

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

            base.Initialize();
        }
        //Перемещение спрайта
        public virtual void Move()
        {
            sprPosition += speed;
        }
        //Проверка допустимости перемещения
        void Check()
        {
            if (sprPosition.X < scrBounds.Left)
            {
                sprPosition.X = scrBounds.Left;
                speed.X *= -1;
            }
            if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
            {
                sprPosition.X = scrBounds.Width - sprRectangle.Width;
                speed.X *= -1;
            }
            if (sprPosition.Y < scrBounds.Top)
            {
                sprPosition.Y = scrBounds.Top;
                speed.Y *= -1;
            }
            if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
            {
                sprPosition.Y = scrBounds.Height - sprRectangle.Height;
                speed.Y *= -1;
            }
        }

        public bool sprCollide(spriteComp spr)
        {
            return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X &&
                    this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width &&
                    this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y &&
                    this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
        }

        /// <summary>
        /// Позволяет игровому компоненту обновиться.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        public override void Update(GameTime gameTime)
        {
            // ЗАДАЧА: добавьте здесь код обновления
            //Вызов метода для перемещения спрайта
            Move();
            //Проверка на столкновение с границами экрана
            Check();
            //Вызов проверки на столкновение с другими спрайтами
            IsSpriteCollide();
            //Возможно, при коррекции спрайта относительно другого спрайта,
            //произошло перекрытие с другим спрайтом или спрайтами
            //это приводит к "зависанию" спрайтов - они остаются на одном 
            //месте в "сцепленном" состоянии. Для того, чтобы этого избежать,
            //мы корректируем позиции спрайтов до тех пор, пока каждый из них
            //гарантированно не окажется вне других спрайтов
            while (howManyCollides() > 0)
            {
                IsSpriteCollide();
            }

            base.Update(gameTime);
        }

        //Если спрайт перекрыл другой спрайт - изменить его скорость и
        //применить изменения к позиции спрайта
        void IsSpriteCollide()
        {
            foreach (spriteComp spr in myGame.Components)
            {
                if (spr != this)
                {
                    if (this.sprCollide(spr))
                    {
                        this.speed *= -1;
                        this.sprPosition += this.speed;
                    }
                }
            }
        }
        public override void Draw(GameTime gameTime)
        {
            SpriteBatch sprBatch =
                (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            sprBatch.Draw(sprTexture, sprPosition, sprRectangle, sprColor);
            base.Draw(gameTime);
        }

    }
}
Листинг 14.5. Код игрового компонента

При создании нового объекта мы случайным образом устанавливаем его стартовую позицию (setSpriteToStart()) делать это мы будем до тех пор, пока все объекты, расположенные на данный момент на экране, не будут расположены без пересечения. Для проверки на пересечение спрайтов мы используем функцию sprCollide(). Она возвратит истину, если текущий объект (this) пересекается с объектом, переданным в функцию. Эту функцию мы вызываем при начальной расстановке спрайтов и при проверке их взаимодействия. Так, если спрайты "столкнулись", что проверяется в процедуре IsSpriteCollide(), мы изменяем направление их движения на противоположное. То же самое происходит со спрайтом, столкнувшимся с границей экрана.

В процедуре Update() мы последовательно выполняем следующие операции. Пытаемся переместить спрайт с помощью метода Move(), проверяем, не столкнулся ли спрайт с границей экрана (Check()), не столкнулся ли спрайт с другим спрайтом (IsSpriteCollide()). В завершение процедуры мы, для того, чтобы исключить ситуацию, в которой коррекция положения спрайта привела к пересечению его с другим спрайтом, проверяем его на столкновение с другими спрайтами, вызывая метод IsSpriteCollide() до тех пор, пока каждый спрайт не окажется гарантированно вне других.

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

Игровой экран проекта P8_2

Рис. 14.6. Игровой экран проекта P8_2
Гулич Анна
Гулич Анна
Невозможно пройти тесты, в окне с вопросами пусто
Сашечка Огнев
Сашечка Огнев
Россия, Красноярский край
Андрей Корягин
Андрей Корягин
Россия, Пенза, Вазерская средняя школа, 2001