Опубликован: 14.08.2012 | Доступ: свободный | Студентов: 880 / 20 | Оценка: 5.00 / 5.00 | Длительность: 09:59:00
Специальности: Программист
Самостоятельная работа 8:

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

14.3. Обработка попадания точки в пределы объекта

Создадим простую игру на базе проекта P8_2. Игрок должен за минимальное время попасть по каждому из 10 находящихся на экране объектов, коснувшись его. Объект, которого коснулись, уничтожается.

Здесь нам понадобится алгоритм проверки попадания точки в пределы спрайта. Как и прежде, спрайты представлены прямоугольниками. Для проверки попадания точки в прямоугольник, о котором известна координата его левой верхней точки, ширина и высота, воспользуемся таким алгоритмом (листинг 14.6.). A – это прямоугольник, B – это точка.

Если (А.X+A.Ширина > B.X И
	A.X < B.X И
	A.Y+A.Высота>В.Y И
	A.Y<B.Y)
Тогда
	Есть столкновение
Иначе>
	Нет столкновения
Листинг 14.6. Алгоритм проверки попадания точки в прямоугольник

Создадим новый проект – P8_3, на базе проекта P8_2. В листинге 14.7 вы можете видеть код объекта Game1.

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_3
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        //Количество набранных очков
        int Score;
        //Завершена ли игра
        bool IsWin = false;
        //Для хранения сообщения, выводимого пользователю
        string message;
        //Для хранения шрифта
        SpriteFont MyFont;
        //Для хранения позиции последнего касания
        Vector2 touchLoc=new Vector2(-1,-1);
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }
        //Модификация счёта игры
        public void SetScore()
        {
            Score++;
        }
        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь логику инициализации
            Score = 0;
            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            //Загружаем шрифт
            MyFont = Content.Load<SpriteFont>("MyFont");
            Services.AddService(typeof(SpriteBatch), spriteBatch);
            texture = Content.Load<Texture2D>("BallandBats");
            CreateNewObject();

        }
        protected void CreateNewObject()
        {
            //Цикл от 1 до 10
            for (int i = 0; i < 10; 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();
            //Если цель игры не достигнута – выводим 
            //информацию о текущем состоянии игры
            if (IsWin == false)
                message = "Destroyed " + Score.ToString() + " in " + gameTime.TotalGameTime.Seconds + " sec.";
            //Если цель достигнута – выводим сообщение об этом
            if (Score == 10 && IsWin == false)
            {
                message = "You complete destruction in " + gameTime.TotalGameTime.Seconds + " sec.";
                IsWin = true;
            }
            //Сбросим позицию касания в такую, в которой заведомо не могут оказаться
            //игровые объекты
            touchLoc = new Vector2(-1, -1);
            //Получаем коллекцию объектов, содержащих информацию о касаниях экрана
            TouchCollection touchLocations = TouchPanel.GetState();
            //Перебираем коллекцию, присваивая объекту координаты касания
            foreach (TouchLocation touchLocation in touchLocations)
            {
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    //Сохраняем координату касания в переменную touchLock
                    touchLoc = touchLocation.Position;
                }
            }
            base.Update(gameTime);
        }
        //Получить позицию последнего касания экрана
        public Vector2 GetPos()
        {
            return touchLoc;
        }

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

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

            spriteBatch.Begin();
            //Выведем игровые объекты
            base.Draw(gameTime);
            //Выведем надпись
            spriteBatch.DrawString(MyFont, message, new Vector2(0, 0), Color.Red);
            spriteBatch.End();

        }
    }
}
Листинг 14.7. Код класса Game1

Здесь мы создаем 10 объектов класса spriteComp и проверяем, не достигнута ли цель игры – уничтожение всех объектов. Для контроля количества уничтоженных объектов используем переменную Score, для контроля за достижением цели – переменную IsWin.

Обработку касаний мы производим здесь же, сохраняя последнюю позицию касания в переменной touchLock. Для того, чтобы исключить случайное "попадание" спрайта в позицию касания, мы, если касания нет (и при создании переменной) устанавливаем ее значение в такое, в котором ни один спрайт, в соответствии с логикой игры, оказаться не может (-1, -1).

Основная работа ведется в объектах класса spriteComp. В листинге 14.8 вы можете найти соответствующий им код.

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.Devices;


namespace P8_3
{
    /// <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 Game1 myGame;
        //Цвет спрайта
        protected Color sprColor;
        //Скорость перемещения
        public Vector2 speed;
        
        public spriteComp(Game1 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);
        }
        //Попала ти точка касания в спрайт
        bool PointCollide(Vector2 point)
        {


            return (this.sprPosition.X + this.sprRectangle.Width > point.X &&
                this.sprPosition.X < point.X &&
                this.sprPosition.Y + this.sprRectangle.Height > point.Y &&
                this.sprPosition.Y < point.Y);

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



            //Вызов метода, проверяющего, коснулся ли пользователь спрайта
            TouchCollide();
            //Вызов метода для перемещения спрайта
            Move();
            //Проверка на столкновение с границами экрана
            Check();
            //Вызов проверки на столкновение с другими спрайтами
            IsSpriteCollide();
            //Возможно, при коррекции спрайта относительно другого спрайта,
            //произошло перекрытие с другим спрайтом или спрайтами
            //это приводит к "зависанию" спрайтов - они остаются на одном 
            //месте в "сцепленном" состоянии. Для того, чтобы этого избежать,
            //мы корректируем позиции спрайтов до тех пор, пока каждый из них
            //гарантированно не окажется вне других спрайтов
            while (howManyCollides() > 0)
            {
                IsSpriteCollide();
            }

            base.Update(gameTime);
        }

        //Проверим, коснулся ли пользователь спрайта
        void TouchCollide()
        {
            //Если точка касания попала в спрайт
            if (PointCollide(myGame.GetPos()))
            {
                //Изменим счёт игры
                myGame.SetScore();
                //Включим вибрацию
                VibrateController.Default.Start(TimeSpan.FromMilliseconds(100));
                //Уничтожим игровой объект
                this.Dispose();
            }
        }

        //Если спрайт перекрыл другой спрайт - изменить его скорость и
        //применить изменения к позиции спрайта
        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.8. Код игрового компонента

В этом коде мы поступаем так же, как и в предыдущем примере. Главная особенность – кроме прочего, мы контролируем попадания прикосновений к экрану в границы спрайтов. Если такое попадание произошло, мы меняем счет игры, включаем кратковременную вибрацию и уничтожаем игровой объект. Напомним, что для использования вибрации нужно подключить библиотеку Microsoft.Phone (рис. 14.7) и пространство имён Microsoft.Devices.

Проект P8_3

Рис. 14.7. Проект P8_3

На рис. 14.8 вы можете видеть игровой экран проекта P8_3 после попадания по трём спрайтам

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

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

Рассмотрим еще один пример проверки на "попадание" точки в определенную область.

Гулич Анна
Гулич Анна
Невозможно пройти тесты, в окне с вопросами пусто