Основы разработки компьютерных игр в XNA Game Studio

Игровая физика

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

Примеры к лабораторной работе

Задачи работы

  • Создать шаблон платформенной игры
  • Немного теории

  • Строго говоря, наше знакомство с игровой физикой началось тогда, когда мы начали перемещать объекты по игровому полю, используя для их перемещения понятие скорости. В нашем случае это – скорость измерения координат. Столкновения объектов – это так же пример реализации физических законов, когда мы имитируем столкновение твердых тел в пространстве. Любые другие взаимодействия объектов так же являются примерами применения физических законов в компьютерных играх.
  • Для реализации более сложных взаимодействий требуется построение физической модели игрового объекта, которая учитывает значимые воздействия на этот объект.
  • Шаблон платформенной игры

    Платформенные игры или платформеры получили такое название потому, что игрок управляет персонажем, который перемещается по площадкам – платформам. В ходе прохождения игры он переходит или перепрыгивает с платформы на платформу, собирает так называемые бонусы, сражается с компьютерными "врагами" и т.д. Действие подобных игр происходит в двумерной среде. Разработаем шаблон такой игры.

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

    Прежде чем начинать разработку, создадим несколько изображений, которые будем использовать в качестве графических образов игровых объектов. На рис. 9.1. вы можете видеть эти объекты.

    (рис 9.1) Изображения для визуализации игровых объектов и фона

    Перечислим объекты, которые понадобятся нам для создания прототипа платформенной игры.

    Объект игрока (me.png) – им управляет играющий.

    Фон (background.png) – фоновое изображение.

    Бонус №1 (bonus1.png) – бонус первого вида. Если объект игрока соприкасается с этим бонусом, игроку начисляется 100 очков.

    Бонус №2 (bonus2.png) – бонус второго вида. Если объект игрока соприкасается с этим бонусом, игроку добавляется 1 "жизнь".

    Враг (enemy.png) – объект "врага". Если игрок соприкасается с объектом врага – количество "жизней" уменьшается на 1. При количестве жизней меньше 0 игра заканчивается. Если игрок сможет прыгнуть и приземлиться на объект врага – враг будет уничтожен, а игрок получит 50 очков.

    Лестница (ladder.png) – игрок может подниматься и опускаться по лестнице там, где другим способом ему не пройти.

    Стена (wall.png) – стена. Стена непроницаема для объекта игрока. Объект игрока может подпрыгнуть только тогда, когда снизу находится стена.

    Создадим новый стандартный проект, назовем его P5_1. Добавим в папку Content нужные ресурсы. Все объекты (кроме объекта игрока), которыми мы будем пользоваться, имеют размеры 64х64 пикселя, объект игрока имеет размер 32х32 пикселя. Для начала нам нужно разработать систему конструирования игровых уровней, в данном случае – систему конструирования игровых экранов. Мы пользуемся разрешением игрового окна 640х512 пикселей, то есть – 8х10 элементов размера 64х64 пикселя. Игровой экран разбит на 80 ячеек, в каждой из которых может быть один из игровых объектов.

    Для хранения информации о содержимом каждой из этих ячеек используем двумерный массив. Каждая ячейка массива соответствует ячейке игрового экрана. Для того, чтобы получить координаты ячейки, соответствующие ячейке массива с индексом (i, j), достаточно умножить каждый из элементов индекса на 64. Верхней левой ячейке экрана соответствует элемент массива с индексом (0, 0), ячейке в правом нижнем углу – элемент (7,9).

    Мы назвали этот массив Layer, в листинге 9.1. приведен код его инициализации.

    Layer = new int[8, 10] { 
       { 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
       { 1, 5, 1, 3, 5, 1, 1, 2, 1, 5 }, 
       { 0, 0, 0, 1, 2, 1, 1, 2, 0, 4 }, 
       { 0, 1, 3, 0, 2, 0, 0, 2, 0, 3 }, 
       { 0, 5, 0, 0, 1, 1, 1, 2, 0, 1 }, 
       { 0, 0, 0, 1, 0, 0, 0, 2, 0, 1 },
       { 6, 0, 1, 3, 4, 0, 5, 2, 0, 1 },
       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
       };

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

    В табл. 9.1. приведено соответствие чисел, используемых в таблице-конструкторе уровня и объектов.

    Номера объектов для размещения в массиве Layer
    Номер Название объекта
    0 Объект отсутствует
    1 Стена
    2 Лестница
    3 Бонус №1
    4 Бонус №2
    5 Враг
    6 Игрок

    На рис. 9.2. приведено изображение игрового экрана, сконструированного в соответствии с таблицей и содержимым массива.

    (рис 9.2) Игровой экран, сконструированный в соответствии с массивом Layer

    На рис. 9.3. приведено окно проекта.

    (рис 9.3) Окно проекта

    В табл. 9.2. приведено описание классов, которые мы используем в данном проекте.

    Классы проекта P5_1
    Имя файла Описание
    Game1.cs Класс стандартного проекта
    gBaseClass.cs Базовый класс – родитель классов игровых объектов. Он содержит процедуры визуализации объектов и их расстановки по игровому полю. Этот класс наследует класс DrawableGameComponent
    Bonus1.cs Класс для создания объектов бонусов вида №1
    Bonus2.cs Класс для создания объектов бонусов вида №2
    Enemy.cs Класс для создания объектов-врагов
    Ladder.cs Класс для создания лестниц
    Me.cs Класс для создания объекта игрока. Именно он содержит основную часть игровой логики, в том числе – реализацию игровой физики для объекта-игрока
    Wall.cs Класс для создания стен

    В листинге 9.2. приведен код класса 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 P5_1
    {
        
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            Texture2D txtBackground;
            Texture2D txtBonus1;
            Texture2D txtBonus2;
            Texture2D txtEnemy;
            Texture2D txtLadder;
            Texture2D txtMe;
            Texture2D txtWall;
            //Массив для конструирования уровня
            public int[,] Layer;
            Rectangle recBackround = new Rectangle(0, 0, 640, 512);
            Rectangle recSprite = new Rectangle(0, 0, 64, 64);
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
            }
    
            
            protected override void Initialize()
            {
                // TODO: Add your initialization logic here
                Layer = new int[8, 10] { 
                { 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
                { 1, 5, 1, 3, 5, 1, 1, 2, 1, 5 }, 
                { 0, 0, 0, 1, 2, 1, 1, 2, 0, 4 }, 
                { 0, 1, 3, 0, 2, 0, 0, 2, 0, 3 }, 
                { 0, 5, 0, 0, 1, 1, 1, 2, 0, 1 }, 
                { 0, 0, 0, 1, 0, 0, 0, 2, 0, 1 },
                { 6, 0, 1, 3, 4, 0, 5, 2, 0, 1 },
                { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
                };
                //Устанавливаем разрешение игрового окна
                //640х512
    
                graphics.PreferredBackBufferWidth = 640;
                graphics.PreferredBackBufferHeight = 512;
                graphics.ApplyChanges();
                base.Initialize();
            }
    
            protected override void LoadContent()
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                Services.AddService(typeof(SpriteBatch), spriteBatch);
                txtBackground = Content.Load<Texture2D>("background");
                txtBonus1 = Content.Load<Texture2D>("bonus1");
                txtBonus2 = Content.Load<Texture2D>("bonus2");
                txtEnemy = Content.Load<Texture2D>("enemy");
                txtLadder = Content.Load<Texture2D>("ladder");
                txtMe = Content.Load<Texture2D>("me");
                txtWall = Content.Load<Texture2D>("wall");
                //Вызываем процедуру расстановки объектов в игровом окне
                AddSprites ();
    
                        }
            //Процедура расстановки объектов в игровом окне
            void AddSprites()
            {
                //Переменные для временного хранения адреса
                //объекта-игрока
                int a=0,b=0; 
                //Просматриваем массив Layer
                for (int i = 0; i < 8; i++)
                {
                    for (int j = 0; j < 10; j++)
                    {
                        //Если элемент с индексом (i,j) равен 1 - 
                        //устанавливаем в соответствующую позицию элемент с
                        //номером 1, то есть - стену
                        if (Layer[i,j]==1) 
                            Components.Add(new GameObj.Wall(this, ref txtWall, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 2)
                            Components.Add(new GameObj.Ladder(this, ref txtLadder, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 3)
                            Components.Add(new GameObj.Bonus1(this, ref txtBonus1, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 4)
                            Components.Add(new GameObj.Bonus2(this, ref txtBonus2, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 5)
                            Components.Add(new GameObj.Enemy(this, ref txtEnemy, new Vector2(j, i), recSprite));
                        //Если обнаружен объект игрока - запишем его координаты
                        if (Layer[i, j] == 6)
                        {
                            a = i;
                            b = j;
                        }  
                    }
                }
               //Последним установим объект игрока - так он гарантированно
               //расположен поверх всех остальных объектов
                    Components.Add(new GameObj.Me(this, ref txtMe, new Vector2(b, a), new Rectangle (0,0,32,32)));
            }
    
            protected override void UnloadContent()
            {
                txtBackground.Dispose();
                txtBonus1.Dispose();
                txtBonus2.Dispose();
                txtEnemy.Dispose();
                txtLadder.Dispose();
                txtMe.Dispose ();
                txtWall.Dispose();
                spriteBatch.Dispose();
            }
    
            protected override void Update(GameTime gameTime)
            {
                base.Update(gameTime);
            }
    
            protected override void Draw(GameTime gameTime)
            {
                spriteBatch.Begin();
                //выведем фоновое изображение
                spriteBatch.Draw(txtBackground, recBackround ,Color.White);
                //Выведем игровые объекты
                base.Draw(gameTime);
                spriteBatch.End();
            }
        }
    }

    Теперь рассмотрим код класса gBaseClass (листинг 9.3.).

    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 P5_1.GameObj
    {
        
        public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
        {
            Texture2D sprTexture;
            public Vector2 sprPosition;
            public Rectangle sprRectangle;
            public gBaseClass(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle)
                : base(game)
            {
                sprTexture = _sprTexture;
                //Именно здесь производится перевод индекса элемента массива
                //в координаты на игровом экране
                sprPosition = _sprPosition * 64;
                sprRectangle = _sprRectangle;
            }
    
            
            public override void Initialize()
            {
                base.Initialize();
            }
    
            public override void Update(GameTime gameTime)
            {
                base.Update(gameTime);
            }
            public override void Draw(GameTime gameTime)
            {
                SpriteBatch sprBatch =
                    (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
                sprBatch.Draw(sprTexture, sprPosition, Color.White);
                base.Draw(gameTime);
            }
        }
    }

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

    Классы Bonus1, Bonus2, Ladder, Wall, Enemy полностью наследуют код класса gBaseComponent, фактически, являясь его точными копиями. Можно было бы отказаться от разработки этих классов, создав соответствующие объекты путем создания объекта класса gBaseComponent. Однако, подход с созданием отдельного класса для каждого компонента предпочтительнее по нескольким причинам. Во-первых – наличие самостоятельных классов для отдельных компонентов позволяет удобно осуществлять различные проверки при работе с игровым объектом. Во-вторых – если понадобится расширить функциональность одного из объектов (например, перемещать объект Enemy и т.д.) – отдельные классы готовы для модификаций. В листинге 9.4. приведен код класса Bonus1. Остальные классы, как уже было сказано, идентичны ему.

    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 P5_1.GameObj
    {
        public class Bonus1 : gBaseClass
        {
            public Bonus1(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle)
            {
            }
    
            public override void Initialize()
            {
                base.Initialize();
            }
    
            public override void Update(GameTime gameTime)
            {
                base.Update(gameTime);
            }
        }
    }

    Рассмотрим код класса Me (листинг 9.5.) – это основной игровой компонент, который реализует игровой процесс.

    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 P5_1.GameObj
    {
        /// <summary>
        /// This is a game component that implements IUpdateable.
        /// </summary>
        public class Me : gBaseClass
        {
            //Прямоугольник, представляющий игровое окно
            Rectangle scrBounds;
            //Скорость, с которой будет перемещаться спрайт
            float sprSpeed=2;
            //"Сила тяжести"
            float sprGravity = 0.4f;
            //"Ускорение свободного падения"
            float sprAcceleration = 0.03f;
            //Скорость при падении
            float sprGrAcc = 0;
            //Скорость, с которой объект будет подпрыгивать
            float sprJump = 70;
            //Переменная для хранения количества "жизней" объекта
            int sprLives = 2;
            //Переменная для хранения набранных очков
            int sprScores = 0;
           
    
            public Me(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle)
            {
    
                scrBounds = new Rectangle(0, 0,
                  game.Window.ClientBounds.Width,
                  game.Window.ClientBounds.Height);
    
               
                // TODO: Construct any child components here
            }
    
            /// <summary>
            /// Allows the game component to perform any initialization it needs to before starting
            /// to run.  This is where it can query for any required services and load content.
            /// </summary>
            public override void Initialize()
            {
    
              
                base.Initialize();
            }
            //Проверка на столкновение с границами экрана
            void Check()
            {
                if (sprPosition.X < scrBounds.Left)
                {
                    sprPosition.X = scrBounds.Left;
                }
                if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
                {
                    sprPosition.X = scrBounds.Width - sprRectangle.Width;
                }
                if (sprPosition.Y < scrBounds.Top)
                {
                    sprPosition.Y = scrBounds.Top;
                }
                if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
                {
                    sprPosition.Y = scrBounds.Height - sprRectangle.Height;
                }
            }
            //Процедуры, которые используются для перемещения объекта в 
            //одном из указанных направлений
            void MoveUp(float speed)
            {
                this.sprPosition.Y -= speed;
            }
            void MoveDown(float speed)
            {
                this.sprPosition.Y += speed;
            }
            void MoveLeft(float speed)
            {
                this.sprPosition.X -= speed;
            }
            void MoveRight(float speed)
            {
                this.sprPosition.X += speed;
            }
            //Функция, которая проверяет, находится ли непосредственно под нашим объектом
            //объект типа Wall - то есть стена. Наш алгоритм обработки столкновений удерживает
            //объект на небольшом расстоянии от стены, не давая ему пройти сквозь нее.
            //Поэтому при проверке к координате Y объекта добавляется 1. Эта функция используется
            //при проверке возможности совершения объектом прыжка - он может подпрыгнуть
            //только в том случае, если под ним есть стена.
    
            bool IsWallIsInTheBottom()
            {
                int Collision = 0;
                foreach (gBaseClass spr in Game.Components)
                {
    
                    if (spr.GetType() == (typeof(Wall)))
                    {
                        if (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X 
                             this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width 
                             this.sprPosition.Y+1 + this.sprRectangle.Height+1 > spr.sprPosition.Y 
                             this.sprPosition.Y+1 < spr.sprPosition.Y + spr.sprRectangle.Height) Collision++;
                    }
                }
    
                if (Collision > 0) return true;
                else
                    return false;
    
            }
    
            
    
            //Функция используется как вспомогательная
            //Она проверяет, сталкивается ли наш объект с объектом
            //класса gBaseClass и возвращает True если столкновение есть
            bool IsCollideWithObject(gBaseClass 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 IsCollideWithLadder()
            {
                foreach (gBaseClass spr in Game.Components)
                {
                    if (spr.GetType() == (typeof(Ladder)))
                    {
                        if (this.sprPosition.X + this.sprRectangle.Width+1 > spr.sprPosition.X 
                            this.sprPosition.X+1 < spr.sprPosition.X + spr.sprRectangle.Width 
                            this.sprPosition.Y + this.sprRectangle.Height+1 > spr.sprPosition.Y 
                            this.sprPosition.Y+1 < spr.sprPosition.Y + spr.sprRectangle.Height)
                            
                            return true;
                    }
               }
               return false;
            }
    
            //Процедура, отвечающая за перемещение игрового объекта
             void Move()
            {
    
    
                KeyboardState kbState = Keyboard.GetState();
                 //При нажатии кнопки "Вверх"
                if (kbState.IsKeyDown(Keys.Up))
                {
                    //Если под объектом находится стена
                    //и он не соприкасается с лестницей
                    //Объект подпрыгивает
                    if (IsWallIsInTheBottom()==true IsCollideWithLadder ()==false )
                    {
    
                        MoveUp(sprJump);
                       //При прыжке проводится проверка на контакт со стеной
                        //Которая может быть расположена над объектом
                        //при необходимости его координаты корректируются
                        while (IsCollideWithWall())
                        {
                            MoveDown((sprSpeed / 10));
                        }
                    }
                    //Если объект находится на лестнице
                    //Он перемещается вверх с обычной скоростью
                    //без прыжков
                    if (IsCollideWithLadder() == true)
                    {
                        MoveUp(sprSpeed);
    
                       while (IsCollideWithWall())
                        {
                            MoveDown((sprSpeed / 10));
                        }
                      
                    }
                        
               }
                 //При нажатии кнопки "Вниз"
                 //Происходит обычная процедура перемещения
                 //объекта с проверкой
                if (kbState.IsKeyDown(Keys.Down))
                {
                    MoveDown(sprSpeed);
                    while (IsCollideWithWall())
                    {
                        MoveUp((sprSpeed / 10));
                    }
                }
                 //Точно так же обрабатывается
                 //нажатие кнопки "Влево"
                if (kbState.IsKeyDown(Keys.Left))
                {
                    
    
                    MoveLeft(sprSpeed);
                    while (IsCollideWithWall())
                    {
                        MoveRight((sprSpeed / 10));
                    }
                }
                 //Аналогично - перемещение вправо
                if (kbState.IsKeyDown(Keys.Right))
                {
                    
                    MoveRight(sprSpeed);
                    while (IsCollideWithWall())
                    {
                        MoveLeft((sprSpeed / 10));
                    }
                }
    
               
            }
            //Проверяем столкновение объекта со стеной
            //Результат проверки нужен для обработки перемещений 
            bool IsCollideWithWall()
            {
             foreach (gBaseClass  spr in Game.Components)
                {
                  if (spr.GetType() == (typeof(Wall)))
                    {
                        if (IsCollideWithObject(spr)) return true;
                    }
                }
                
               return false ;
            }
            //В этой процедуре мы проверяем столкновения с объектами-бонусами
            //и объектами-врагами, причем, наш объект может уничтожать врагов,
            //прыгая на них сверху. С точки зрения данной процедуры такой прыжок
            //ничем не отличается от обычного контакта с объектом-врагом и приводит к
            //проигрышу. Поэтому проверка на прыжок нашего объекта на вражеский объект
            //вынесена в отдельную процедуру
            void IsCollideWithAny()
            {
                //Заводим переменную для временного хранения
                //ссылки на объект, с которым столкнулся игровой объект
                gBaseClass FindObj = null;
                //Проверка на столкновение с объектами Bonus1
                foreach (gBaseClass spr in Game.Components)
                {
    
                    if (spr.GetType() == (typeof(Bonus1)))
                    {
                        if (IsCollideWithObject(spr))
                        {
                           
                            
                            FindObj = spr;
                           
                        }
                    }
                }
    
                //Если было столкновение - уничтожаем объект, 
                //с которым было столкновение
                //и увеличиваем число очков
                if (FindObj != null)
                {
                    sprScores += 100;
                    FindObj.Dispose();
                    FindObj = null;
                }
                //Проверка на столкновение с объектами Bonus2
    
                foreach (gBaseClass spr in Game.Components)
                {
                    if (spr.GetType() == (typeof(Bonus2)))
                    {
                                        if (IsCollideWithObject(spr))
                        {
                           
                            
                            FindObj = spr;
                        }
                    }
                }
                //Если столкновение было 
                //уничтожим объект
                //Увеличим число "жизней"
              if (FindObj != null)
                {
                    sprLives += 1;
                    FindObj.Dispose();
                    FindObj = null;
                }
                //Проверка на столкновение с объектами Enemy
                    foreach (gBaseClass spr in Game.Components)
                        {
                            if (spr.GetType() == (typeof(Enemy)))
                            {
                                if (IsCollideWithObject(spr))
                                {
                                    FindObj = spr;
                                }
                            }
                    }
                     
            if (FindObj != null)
            {
                               
                    FindObj.Dispose();
    
                    sprLives--;
            }
    
            }
            //Этот метод реализует игровую "силу тяжести"
            //Объект, который находится в свободном пространстве
            //падает вниз
     
            void GoToDown()
            {
       // перемещается вниз
                //при перемещении проверяется столкновение
                //объекта со стеной
                
    
    
                if (IsCollideWithLadder() == false)
                {
                    if (sprGrAcc == 0) sprGrAcc = sprAcceleration;
                    
                    MoveDown(sprGrAcc);
                    while (IsCollideWithWall())
                    {
                        MoveUp((sprSpeed / 10));
                    }
                    sprGrAcc += sprAcceleration;
                    if (IsWallIsInTheBottom()) sprGrAcc = 0;
                    if (IsCollideWithLadder()) sprGrAcc = 0;
                }
            }
            //Метод, проверяющий, не "прыгнул" ли игровой объект
            //на "голову" объекту-врагу
            //Обратите внимание на то, что процедура проверки условия на первый взгляд кажется похожей
            //на обычные проверки, однако таковой не является
            void IsKillEnemy()
            {
                gBaseClass FindObj = null;
                foreach (gBaseClass spr in Game.Components)
                {
                    if (spr.GetType() == (typeof(Enemy)))
                    {
                        //Если игровой объект находится в пределах координат X объекта-врага
                        //Если при этом игровой объект находится выше, чем объект-враг
                        //менее чем на 35 пикселей (если считать по верхней стороне прямоугольника
                        //описанного около объектов - это значит, что игровой объект
                        //"прыгнул" на объект-врага и уничтожил его.
                        if (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(spr.sprPosition .Y -this.sprPosition .Y)<35)
                        {
                            //Если условие выполняется - сохраним ссылку на объект врага
                            FindObj = spr;
                        }
                    }
                }
                //Если был удачный прыжок на врага
                //добавим игроку очков и уничтожим объект класса Enemy
                if (FindObj != null)
                {
                    sprScores += 50;
                    FindObj.Dispose();
                }
            }
            /// <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();
                //Применим к объекту "силу тяжести"
                GoToDown();
                //Проверим, не прыгнул ли наш объект на объект класса Enemy
                //Вызов этого метода предшествует вызову метода IsCollideWithAny()
                //Иначе прыжок на врага с целью уничтожить его может обернуться
                //Печальными последствиями для нашего объекта
                IsKillEnemy();
                //Проверим на другие столкновения
                IsCollideWithAny();
                
                Game.Window.Title = "У вашего персонажа " + sprLives.ToString() +
                    " жизни(ей) и " + sprScores + " очков";
                if (sprLives < 0)
                {
                    Game.Window.Title = "Вы проиграли";
                    this.Dispose();
                }
                base.Update(gameTime);
            }
            
        }
    }

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

    Метод GoToDown() перемещает объект вниз до тех пор, пока он не коснется объекта-стены. Если объект "стоит" на стене (метод IsWallIsInTheBottom() ) – он может подпрыгнуть. Если объект касается лестницы (IsCollideWithLadder()) – "сила тяжести" на него не действует, он может перемещаться по лестнице в любом направлении, однако не может подпрыгивать. Если объект сталкивается с бонусом №1 (IsCollideWithAny())– ему начисляются очки, если с бонусом №2 – жизни. Если объект столкнулся с врагом – одна жизнь теряется, если объект "прыгнул" на врага (IsKillEnemy()) – объект врага уничтожается, игроку начисляются очки.

    Задание

    Разработайте собственные спрайты для визуализации объектов.

    Реализуйте самостоятельно игру, которая описана выше.

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

    Страницы:

    Примеры к лабораторной работе

    Задачи работы

  • Создать шаблон платформенной игры
  • Немного теории

  • Строго говоря, наше знакомство с игровой физикой началось тогда, когда мы начали перемещать объекты по игровому полю, используя для их перемещения понятие скорости. В нашем случае это – скорость измерения координат. Столкновения объектов – это так же пример реализации физических законов, когда мы имитируем столкновение твердых тел в пространстве. Любые другие взаимодействия объектов так же являются примерами применения физических законов в компьютерных играх.
  • Для реализации более сложных взаимодействий требуется построение физической модели игрового объекта, которая учитывает значимые воздействия на этот объект.
  • Шаблон платформенной игры

    Платформенные игры или платформеры получили такое название потому, что игрок управляет персонажем, который перемещается по площадкам – платформам. В ходе прохождения игры он переходит или перепрыгивает с платформы на платформу, собирает так называемые бонусы, сражается с компьютерными "врагами" и т.д. Действие подобных игр происходит в двумерной среде. Разработаем шаблон такой игры.

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

    Прежде чем начинать разработку, создадим несколько изображений, которые будем использовать в качестве графических образов игровых объектов. На рис. 9.1. вы можете видеть эти объекты.

    (рис 9.1) Изображения для визуализации игровых объектов и фона

    Перечислим объекты, которые понадобятся нам для создания прототипа платформенной игры.

    Объект игрока (me.png) – им управляет играющий.

    Фон (background.png) – фоновое изображение.

    Бонус №1 (bonus1.png) – бонус первого вида. Если объект игрока соприкасается с этим бонусом, игроку начисляется 100 очков.

    Бонус №2 (bonus2.png) – бонус второго вида. Если объект игрока соприкасается с этим бонусом, игроку добавляется 1 "жизнь".

    Враг (enemy.png) – объект "врага". Если игрок соприкасается с объектом врага – количество "жизней" уменьшается на 1. При количестве жизней меньше 0 игра заканчивается. Если игрок сможет прыгнуть и приземлиться на объект врага – враг будет уничтожен, а игрок получит 50 очков.

    Лестница (ladder.png) – игрок может подниматься и опускаться по лестнице там, где другим способом ему не пройти.

    Стена (wall.png) – стена. Стена непроницаема для объекта игрока. Объект игрока может подпрыгнуть только тогда, когда снизу находится стена.

    Создадим новый стандартный проект, назовем его P5_1. Добавим в папку Content нужные ресурсы. Все объекты (кроме объекта игрока), которыми мы будем пользоваться, имеют размеры 64х64 пикселя, объект игрока имеет размер 32х32 пикселя. Для начала нам нужно разработать систему конструирования игровых уровней, в данном случае – систему конструирования игровых экранов. Мы пользуемся разрешением игрового окна 640х512 пикселей, то есть – 8х10 элементов размера 64х64 пикселя. Игровой экран разбит на 80 ячеек, в каждой из которых может быть один из игровых объектов.

    Для хранения информации о содержимом каждой из этих ячеек используем двумерный массив. Каждая ячейка массива соответствует ячейке игрового экрана. Для того, чтобы получить координаты ячейки, соответствующие ячейке массива с индексом (i, j), достаточно умножить каждый из элементов индекса на 64. Верхней левой ячейке экрана соответствует элемент массива с индексом (0, 0), ячейке в правом нижнем углу – элемент (7,9).

    Мы назвали этот массив Layer, в листинге 9.1. приведен код его инициализации.

    Layer = new int[8, 10] { 
       { 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
       { 1, 5, 1, 3, 5, 1, 1, 2, 1, 5 }, 
       { 0, 0, 0, 1, 2, 1, 1, 2, 0, 4 }, 
       { 0, 1, 3, 0, 2, 0, 0, 2, 0, 3 }, 
       { 0, 5, 0, 0, 1, 1, 1, 2, 0, 1 }, 
       { 0, 0, 0, 1, 0, 0, 0, 2, 0, 1 },
       { 6, 0, 1, 3, 4, 0, 5, 2, 0, 1 },
       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
       };

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

    В табл. 9.1. приведено соответствие чисел, используемых в таблице-конструкторе уровня и объектов.

    Номера объектов для размещения в массиве Layer
    Номер Название объекта
    0 Объект отсутствует
    1 Стена
    2 Лестница
    3 Бонус №1
    4 Бонус №2
    5 Враг
    6 Игрок

    На рис. 9.2. приведено изображение игрового экрана, сконструированного в соответствии с таблицей и содержимым массива.

    (рис 9.2) Игровой экран, сконструированный в соответствии с массивом Layer

    На рис. 9.3. приведено окно проекта.

    (рис 9.3) Окно проекта

    В табл. 9.2. приведено описание классов, которые мы используем в данном проекте.

    Классы проекта P5_1
    Имя файла Описание
    Game1.cs Класс стандартного проекта
    gBaseClass.cs Базовый класс – родитель классов игровых объектов. Он содержит процедуры визуализации объектов и их расстановки по игровому полю. Этот класс наследует класс DrawableGameComponent
    Bonus1.cs Класс для создания объектов бонусов вида №1
    Bonus2.cs Класс для создания объектов бонусов вида №2
    Enemy.cs Класс для создания объектов-врагов
    Ladder.cs Класс для создания лестниц
    Me.cs Класс для создания объекта игрока. Именно он содержит основную часть игровой логики, в том числе – реализацию игровой физики для объекта-игрока
    Wall.cs Класс для создания стен

    В листинге 9.2. приведен код класса 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 P5_1
    {
        
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            Texture2D txtBackground;
            Texture2D txtBonus1;
            Texture2D txtBonus2;
            Texture2D txtEnemy;
            Texture2D txtLadder;
            Texture2D txtMe;
            Texture2D txtWall;
            //Массив для конструирования уровня
            public int[,] Layer;
            Rectangle recBackround = new Rectangle(0, 0, 640, 512);
            Rectangle recSprite = new Rectangle(0, 0, 64, 64);
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
            }
    
            
            protected override void Initialize()
            {
                // TODO: Add your initialization logic here
                Layer = new int[8, 10] { 
                { 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
                { 1, 5, 1, 3, 5, 1, 1, 2, 1, 5 }, 
                { 0, 0, 0, 1, 2, 1, 1, 2, 0, 4 }, 
                { 0, 1, 3, 0, 2, 0, 0, 2, 0, 3 }, 
                { 0, 5, 0, 0, 1, 1, 1, 2, 0, 1 }, 
                { 0, 0, 0, 1, 0, 0, 0, 2, 0, 1 },
                { 6, 0, 1, 3, 4, 0, 5, 2, 0, 1 },
                { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
                };
                //Устанавливаем разрешение игрового окна
                //640х512
    
                graphics.PreferredBackBufferWidth = 640;
                graphics.PreferredBackBufferHeight = 512;
                graphics.ApplyChanges();
                base.Initialize();
            }
    
            protected override void LoadContent()
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                Services.AddService(typeof(SpriteBatch), spriteBatch);
                txtBackground = Content.Load<Texture2D>("background");
                txtBonus1 = Content.Load<Texture2D>("bonus1");
                txtBonus2 = Content.Load<Texture2D>("bonus2");
                txtEnemy = Content.Load<Texture2D>("enemy");
                txtLadder = Content.Load<Texture2D>("ladder");
                txtMe = Content.Load<Texture2D>("me");
                txtWall = Content.Load<Texture2D>("wall");
                //Вызываем процедуру расстановки объектов в игровом окне
                AddSprites ();
    
                        }
            //Процедура расстановки объектов в игровом окне
            void AddSprites()
            {
                //Переменные для временного хранения адреса
                //объекта-игрока
                int a=0,b=0; 
                //Просматриваем массив Layer
                for (int i = 0; i < 8; i++)
                {
                    for (int j = 0; j < 10; j++)
                    {
                        //Если элемент с индексом (i,j) равен 1 - 
                        //устанавливаем в соответствующую позицию элемент с
                        //номером 1, то есть - стену
                        if (Layer[i,j]==1) 
                            Components.Add(new GameObj.Wall(this, ref txtWall, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 2)
                            Components.Add(new GameObj.Ladder(this, ref txtLadder, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 3)
                            Components.Add(new GameObj.Bonus1(this, ref txtBonus1, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 4)
                            Components.Add(new GameObj.Bonus2(this, ref txtBonus2, new Vector2(j, i), recSprite));
                        if (Layer[i, j] == 5)
                            Components.Add(new GameObj.Enemy(this, ref txtEnemy, new Vector2(j, i), recSprite));
                        //Если обнаружен объект игрока - запишем его координаты
                        if (Layer[i, j] == 6)
                        {
                            a = i;
                            b = j;
                        }  
                    }
                }
               //Последним установим объект игрока - так он гарантированно
               //расположен поверх всех остальных объектов
                    Components.Add(new GameObj.Me(this, ref txtMe, new Vector2(b, a), new Rectangle (0,0,32,32)));
            }
    
            protected override void UnloadContent()
            {
                txtBackground.Dispose();
                txtBonus1.Dispose();
                txtBonus2.Dispose();
                txtEnemy.Dispose();
                txtLadder.Dispose();
                txtMe.Dispose ();
                txtWall.Dispose();
                spriteBatch.Dispose();
            }
    
            protected override void Update(GameTime gameTime)
            {
                base.Update(gameTime);
            }
    
            protected override void Draw(GameTime gameTime)
            {
                spriteBatch.Begin();
                //выведем фоновое изображение
                spriteBatch.Draw(txtBackground, recBackround ,Color.White);
                //Выведем игровые объекты
                base.Draw(gameTime);
                spriteBatch.End();
            }
        }
    }

    Теперь рассмотрим код класса gBaseClass (листинг 9.3.).

    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 P5_1.GameObj
    {
        
        public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
        {
            Texture2D sprTexture;
            public Vector2 sprPosition;
            public Rectangle sprRectangle;
            public gBaseClass(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle)
                : base(game)
            {
                sprTexture = _sprTexture;
                //Именно здесь производится перевод индекса элемента массива
                //в координаты на игровом экране
                sprPosition = _sprPosition * 64;
                sprRectangle = _sprRectangle;
            }
    
            
            public override void Initialize()
            {
                base.Initialize();
            }
    
            public override void Update(GameTime gameTime)
            {
                base.Update(gameTime);
            }
            public override void Draw(GameTime gameTime)
            {
                SpriteBatch sprBatch =
                    (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
                sprBatch.Draw(sprTexture, sprPosition, Color.White);
                base.Draw(gameTime);
            }
        }
    }

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

    Классы Bonus1, Bonus2, Ladder, Wall, Enemy полностью наследуют код класса gBaseComponent, фактически, являясь его точными копиями. Можно было бы отказаться от разработки этих классов, создав соответствующие объекты путем создания объекта класса gBaseComponent. Однако, подход с созданием отдельного класса для каждого компонента предпочтительнее по нескольким причинам. Во-первых – наличие самостоятельных классов для отдельных компонентов позволяет удобно осуществлять различные проверки при работе с игровым объектом. Во-вторых – если понадобится расширить функциональность одного из объектов (например, перемещать объект Enemy и т.д.) – отдельные классы готовы для модификаций. В листинге 9.4. приведен код класса Bonus1. Остальные классы, как уже было сказано, идентичны ему.

    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 P5_1.GameObj
    {
        public class Bonus1 : gBaseClass
        {
            public Bonus1(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle)
            {
            }
    
            public override void Initialize()
            {
                base.Initialize();
            }
    
            public override void Update(GameTime gameTime)
            {
                base.Update(gameTime);
            }
        }
    }

    Рассмотрим код класса Me (листинг 9.5.) – это основной игровой компонент, который реализует игровой процесс.

    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 P5_1.GameObj
    {
        /// <summary>
        /// This is a game component that implements IUpdateable.
        /// </summary>
        public class Me : gBaseClass
        {
            //Прямоугольник, представляющий игровое окно
            Rectangle scrBounds;
            //Скорость, с которой будет перемещаться спрайт
            float sprSpeed=2;
            //"Сила тяжести"
            float sprGravity = 0.4f;
            //"Ускорение свободного падения"
            float sprAcceleration = 0.03f;
            //Скорость при падении
            float sprGrAcc = 0;
            //Скорость, с которой объект будет подпрыгивать
            float sprJump = 70;
            //Переменная для хранения количества "жизней" объекта
            int sprLives = 2;
            //Переменная для хранения набранных очков
            int sprScores = 0;
           
    
            public Me(Game game, ref Texture2D _sprTexture,
                Vector2 _sprPosition, Rectangle _sprRectangle)
                : base(game, ref _sprTexture, _sprPosition, _sprRectangle)
            {
    
                scrBounds = new Rectangle(0, 0,
                  game.Window.ClientBounds.Width,
                  game.Window.ClientBounds.Height);
    
               
                // TODO: Construct any child components here
            }
    
            /// <summary>
            /// Allows the game component to perform any initialization it needs to before starting
            /// to run.  This is where it can query for any required services and load content.
            /// </summary>
            public override void Initialize()
            {
    
              
                base.Initialize();
            }
            //Проверка на столкновение с границами экрана
            void Check()
            {
                if (sprPosition.X < scrBounds.Left)
                {
                    sprPosition.X = scrBounds.Left;
                }
                if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
                {
                    sprPosition.X = scrBounds.Width - sprRectangle.Width;
                }
                if (sprPosition.Y < scrBounds.Top)
                {
                    sprPosition.Y = scrBounds.Top;
                }
                if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
                {
                    sprPosition.Y = scrBounds.Height - sprRectangle.Height;
                }
            }
            //Процедуры, которые используются для перемещения объекта в 
            //одном из указанных направлений
            void MoveUp(float speed)
            {
                this.sprPosition.Y -= speed;
            }
            void MoveDown(float speed)
            {
                this.sprPosition.Y += speed;
            }
            void MoveLeft(float speed)
            {
                this.sprPosition.X -= speed;
            }
            void MoveRight(float speed)
            {
                this.sprPosition.X += speed;
            }
            //Функция, которая проверяет, находится ли непосредственно под нашим объектом
            //объект типа Wall - то есть стена. Наш алгоритм обработки столкновений удерживает
            //объект на небольшом расстоянии от стены, не давая ему пройти сквозь нее.
            //Поэтому при проверке к координате Y объекта добавляется 1. Эта функция используется
            //при проверке возможности совершения объектом прыжка - он может подпрыгнуть
            //только в том случае, если под ним есть стена.
    
            bool IsWallIsInTheBottom()
            {
                int Collision = 0;
                foreach (gBaseClass spr in Game.Components)
                {
    
                    if (spr.GetType() == (typeof(Wall)))
                    {
                        if (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X 
                             this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width 
                             this.sprPosition.Y+1 + this.sprRectangle.Height+1 > spr.sprPosition.Y 
                             this.sprPosition.Y+1 < spr.sprPosition.Y + spr.sprRectangle.Height) Collision++;
                    }
                }
    
                if (Collision > 0) return true;
                else
                    return false;
    
            }
    
            
    
            //Функция используется как вспомогательная
            //Она проверяет, сталкивается ли наш объект с объектом
            //класса gBaseClass и возвращает True если столкновение есть
            bool IsCollideWithObject(gBaseClass 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 IsCollideWithLadder()
            {
                foreach (gBaseClass spr in Game.Components)
                {
                    if (spr.GetType() == (typeof(Ladder)))
                    {
                        if (this.sprPosition.X + this.sprRectangle.Width+1 > spr.sprPosition.X 
                            this.sprPosition.X+1 < spr.sprPosition.X + spr.sprRectangle.Width 
                            this.sprPosition.Y + this.sprRectangle.Height+1 > spr.sprPosition.Y 
                            this.sprPosition.Y+1 < spr.sprPosition.Y + spr.sprRectangle.Height)
                            
                            return true;
                    }
               }
               return false;
            }
    
            //Процедура, отвечающая за перемещение игрового объекта
             void Move()
            {
    
    
                KeyboardState kbState = Keyboard.GetState();
                 //При нажатии кнопки "Вверх"
                if (kbState.IsKeyDown(Keys.Up))
                {
                    //Если под объектом находится стена
                    //и он не соприкасается с лестницей
                    //Объект подпрыгивает
                    if (IsWallIsInTheBottom()==true IsCollideWithLadder ()==false )
                    {
    
                        MoveUp(sprJump);
                       //При прыжке проводится проверка на контакт со стеной
                        //Которая может быть расположена над объектом
                        //при необходимости его координаты корректируются
                        while (IsCollideWithWall())
                        {
                            MoveDown((sprSpeed / 10));
                        }
                    }
                    //Если объект находится на лестнице
                    //Он перемещается вверх с обычной скоростью
                    //без прыжков
                    if (IsCollideWithLadder() == true)
                    {
                        MoveUp(sprSpeed);
    
                       while (IsCollideWithWall())
                        {
                            MoveDown((sprSpeed / 10));
                        }
                      
                    }
                        
               }
                 //При нажатии кнопки "Вниз"
                 //Происходит обычная процедура перемещения
                 //объекта с проверкой
                if (kbState.IsKeyDown(Keys.Down))
                {
                    MoveDown(sprSpeed);
                    while (IsCollideWithWall())
                    {
                        MoveUp((sprSpeed / 10));
                    }
                }
                 //Точно так же обрабатывается
                 //нажатие кнопки "Влево"
                if (kbState.IsKeyDown(Keys.Left))
                {
                    
    
                    MoveLeft(sprSpeed);
                    while (IsCollideWithWall())
                    {
                        MoveRight((sprSpeed / 10));
                    }
                }
                 //Аналогично - перемещение вправо
                if (kbState.IsKeyDown(Keys.Right))
                {
                    
                    MoveRight(sprSpeed);
                    while (IsCollideWithWall())
                    {
                        MoveLeft((sprSpeed / 10));
                    }
                }
    
               
            }
            //Проверяем столкновение объекта со стеной
            //Результат проверки нужен для обработки перемещений 
            bool IsCollideWithWall()
            {
             foreach (gBaseClass  spr in Game.Components)
                {
                  if (spr.GetType() == (typeof(Wall)))
                    {
                        if (IsCollideWithObject(spr)) return true;
                    }
                }
                
               return false ;
            }
            //В этой процедуре мы проверяем столкновения с объектами-бонусами
            //и объектами-врагами, причем, наш объект может уничтожать врагов,
            //прыгая на них сверху. С точки зрения данной процедуры такой прыжок
            //ничем не отличается от обычного контакта с объектом-врагом и приводит к
            //проигрышу. Поэтому проверка на прыжок нашего объекта на вражеский объект
            //вынесена в отдельную процедуру
            void IsCollideWithAny()
            {
                //Заводим переменную для временного хранения
                //ссылки на объект, с которым столкнулся игровой объект
                gBaseClass FindObj = null;
                //Проверка на столкновение с объектами Bonus1
                foreach (gBaseClass spr in Game.Components)
                {
    
                    if (spr.GetType() == (typeof(Bonus1)))
                    {
                        if (IsCollideWithObject(spr))
                        {
                           
                            
                            FindObj = spr;
                           
                        }
                    }
                }
    
                //Если было столкновение - уничтожаем объект, 
                //с которым было столкновение
                //и увеличиваем число очков
                if (FindObj != null)
                {
                    sprScores += 100;
                    FindObj.Dispose();
                    FindObj = null;
                }
                //Проверка на столкновение с объектами Bonus2
    
                foreach (gBaseClass spr in Game.Components)
                {
                    if (spr.GetType() == (typeof(Bonus2)))
                    {
                                        if (IsCollideWithObject(spr))
                        {
                           
                            
                            FindObj = spr;
                        }
                    }
                }
                //Если столкновение было 
                //уничтожим объект
                //Увеличим число "жизней"
              if (FindObj != null)
                {
                    sprLives += 1;
                    FindObj.Dispose();
                    FindObj = null;
                }
                //Проверка на столкновение с объектами Enemy
                    foreach (gBaseClass spr in Game.Components)
                        {
                            if (spr.GetType() == (typeof(Enemy)))
                            {
                                if (IsCollideWithObject(spr))
                                {
                                    FindObj = spr;
                                }
                            }
                    }
                     
            if (FindObj != null)
            {
                               
                    FindObj.Dispose();
    
                    sprLives--;
            }
    
            }
            //Этот метод реализует игровую "силу тяжести"
            //Объект, который находится в свободном пространстве
            //падает вниз
     
            void GoToDown()
            {
       // перемещается вниз
                //при перемещении проверяется столкновение
                //объекта со стеной
                
    
    
                if (IsCollideWithLadder() == false)
                {
                    if (sprGrAcc == 0) sprGrAcc = sprAcceleration;
                    
                    MoveDown(sprGrAcc);
                    while (IsCollideWithWall())
                    {
                        MoveUp((sprSpeed / 10));
                    }
                    sprGrAcc += sprAcceleration;
                    if (IsWallIsInTheBottom()) sprGrAcc = 0;
                    if (IsCollideWithLadder()) sprGrAcc = 0;
                }
            }
            //Метод, проверяющий, не "прыгнул" ли игровой объект
            //на "голову" объекту-врагу
            //Обратите внимание на то, что процедура проверки условия на первый взгляд кажется похожей
            //на обычные проверки, однако таковой не является
            void IsKillEnemy()
            {
                gBaseClass FindObj = null;
                foreach (gBaseClass spr in Game.Components)
                {
                    if (spr.GetType() == (typeof(Enemy)))
                    {
                        //Если игровой объект находится в пределах координат X объекта-врага
                        //Если при этом игровой объект находится выше, чем объект-враг
                        //менее чем на 35 пикселей (если считать по верхней стороне прямоугольника
                        //описанного около объектов - это значит, что игровой объект
                        //"прыгнул" на объект-врага и уничтожил его.
                        if (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(spr.sprPosition .Y -this.sprPosition .Y)<35)
                        {
                            //Если условие выполняется - сохраним ссылку на объект врага
                            FindObj = spr;
                        }
                    }
                }
                //Если был удачный прыжок на врага
                //добавим игроку очков и уничтожим объект класса Enemy
                if (FindObj != null)
                {
                    sprScores += 50;
                    FindObj.Dispose();
                }
            }
            /// <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();
                //Применим к объекту "силу тяжести"
                GoToDown();
                //Проверим, не прыгнул ли наш объект на объект класса Enemy
                //Вызов этого метода предшествует вызову метода IsCollideWithAny()
                //Иначе прыжок на врага с целью уничтожить его может обернуться
                //Печальными последствиями для нашего объекта
                IsKillEnemy();
                //Проверим на другие столкновения
                IsCollideWithAny();
                
                Game.Window.Title = "У вашего персонажа " + sprLives.ToString() +
                    " жизни(ей) и " + sprScores + " очков";
                if (sprLives < 0)
                {
                    Game.Window.Title = "Вы проиграли";
                    this.Dispose();
                }
                base.Update(gameTime);
            }
            
        }
    }

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

    Метод GoToDown() перемещает объект вниз до тех пор, пока он не коснется объекта-стены. Если объект "стоит" на стене (метод IsWallIsInTheBottom() ) – он может подпрыгнуть. Если объект касается лестницы (IsCollideWithLadder()) – "сила тяжести" на него не действует, он может перемещаться по лестнице в любом направлении, однако не может подпрыгивать. Если объект сталкивается с бонусом №1 (IsCollideWithAny())– ему начисляются очки, если с бонусом №2 – жизни. Если объект столкнулся с врагом – одна жизнь теряется, если объект "прыгнул" на врага (IsKillEnemy()) – объект врага уничтожается, игроку начисляются очки.

    Задание

    Разработайте собственные спрайты для визуализации объектов.

    Реализуйте самостоятельно игру, которая описана выше.

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

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