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

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

Основная задача этого компонента – перевести координаты объекта, выраженные в индексе массива в координаты объекта на игровом экране и вывести объект на экран. Это достигается умножением индексов на 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);
        }
    }
}
Листинг 9.4. Код класса Bonus1

Рассмотрим код класса 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);
        }
        
    }
}
Листинг 9.5. Код класса Me

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

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

Задание

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

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

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

Alina Lasskaja
Alina Lasskaja

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