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

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

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

Создадим новый игровой проект P4_2, взяв за основу проект P3_7. Будем автоматически перемещать объекты с некоторой случайно заданной скоростью. При столкновении объекта с границей экрана изменим скорость по X и по Y на противоположную. Так же поступим при столкновении объектов друг с другом.

В листинге 8.5. вы можете видеть пример кода класса Game1.

using System;
using System.Collections.Generic;
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.Net;
using Microsoft.Xna.Framework.Storage;

namespace P4_2
{
    /// <summary>
    /// This is the main type for your game
    /// </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";
        }

        
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Services.AddService(typeof(SpriteBatch), spriteBatch);
            texture = Content.Load<Texture2D>("BallandBats");
            randNum = new Random();
            CreateNewObject();
            // TODO: use this.Content to load your game content here
        }

        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));
            }
        }

        
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            
            
            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            spriteBatch.Begin();
            base.Draw(gameTime);
            spriteBatch.End();
        }
    }
}
Листинг 8.5. Код класса Game1

Можно заметить, что в основном игровом классе мы лишь создаем случайное количество новых объектов класса spriteComp. Управление объектами и обработка столкновений ведется непосредственно в коде самих объектов. В листинге 8.6. вы можете видеть код объекта spriteComp.

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;

namespace P4_2
{
    /// <summary>
    /// This is a game component that implements 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.Width,
                game.Window.ClientBounds.Height);
            //Устанавливаем стартовую позицию спрайта
            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));
            // TODO: Construct any child components here
        }
        //Проверка, не установлены ли спрайты в позиции с перекрытием других спрайтов
        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);

        }
        public override void Initialize()
        {
            // TODO: Add your initialization code here

            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>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            //Вызов метода для перемещения спрайта
            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);
        }
    }
}
Листинг 8.6. Код объекта spriteComp

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

Переменная speed хранит скорость перемещения по X и по Y. В процедуре Move() мы прибавляем значение этой переменной к координате спрайта. После этого проводим две проверки. Первая – на столкновение с границей экрана – с помощью процедуры Check(). Если объект касается одной из сторон границы экрана, позиция объекта фиксируется на границе и соответствующая составляющая скорости объекта меняется на противоположную. Далее следует проверка на столкновение с другими спрайтами – с помощью процедуры IsSpriteCollide(). Здесь проводится обход всех спрайтов с проверкой на столкновение. Если столкновение произошло – скорость спрайта по обеим составляющим меняется на противоположную, изменения тут же применяются, далее происходит проверка на столкновение со всеми спрайтами (с помощью уже упомянутой процедуры howManyCollides ), корректировка положения спрайта производится до тех пор, пока не будет выяснено, что он не перекрывает ни одного из других спрайтов. На рис. 8.4. вы можете видеть игровой экран проекта P4_2.

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

увеличить изображение
Рис. 8.4. Игровой экран проекта P4_2

Рассмотрим еще один пример обработки столкновений.

Alina Lasskaja
Alina Lasskaja

Быть может кто-то из Вас знает игру Sims, к какому жанру она относиться? Жизненная симуляция, ролевая игра, там можно и дома строить.....

Дмитрий Кацман
Дмитрий Кацман
Израиль
Андрей Веденин
Андрей Веденин
Россия, Белгород