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

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

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

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;


namespace P10_1.GameObjects
{
    /// <summary>
    /// Это игровой компонент, реализующий интерфейс IUpdateable.
    /// </summary>
    public class Me :gBaseClass
    {
        //Прямоугольник, представляющий игровое окно
        Rectangle scrBounds;
        //Скорость, с которой будет перемещаться спрайт
        float sprSpeed=2;
        //"Ускорение свободного падения"
        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, SpriteFont _myFont)
            : base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
        {
              scrBounds = new Rectangle(16, 0,
             768,
             448);
        }

      
 //Проверка на столкновение с границами экрана
        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;
        }

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

            base.Initialize();
        }

//Перемещение объекта
 void Move(int Direction)
        {

            //Вверх
            if (Direction==1)
            {
                //Если под объектом находится стена
                //и он не соприкасается с лестницей
                //Объект подпрыгивает
                if (IsWallIsInTheBottom()==true&& IsCollideWithLadder ()==false )
                {

                    MoveUp(sprJump);
                   //При прыжке проводится проверка на контакт со стеной
                    //Которая может быть расположена над объектом
                    //при необходимости его координаты корректируются
                    while (IsCollideWithWall())
                    {
                        MoveDown((sprSpeed / 10));
                    }
                }
                //Если объект находится на лестнице
                //Он перемещается вверх с обычной скоростью
                //без прыжков
                if (IsCollideWithLadder() == true)
                {
                    MoveUp(sprSpeed);

                   while (IsCollideWithWall())
                    {
                        MoveDown((sprSpeed / 10));
                    }
                  
                }
                    
           }
             //Вниз
             //Происходит обычная процедура перемещения
             //объекта с проверкой
            if (Direction==2)
            {
                MoveDown(sprSpeed);
                while (IsCollideWithWall())
                {
                    MoveUp((sprSpeed / 10));
                }
            }
             //Точно так же обрабатывается
             //движение Влево
            if (Direction==3)
            {
                

                MoveLeft(sprSpeed);
                while (IsCollideWithWall())
                {
                    MoveRight((sprSpeed / 10));
                }
            }
             //Аналогично - перемещение вправо
            if (Direction==4)
            {
                
                MoveRight(sprSpeed);
                while (IsCollideWithWall())
                {
                    MoveLeft((sprSpeed / 10));
                }
            }
               
        }
//Обработка сенсорного ввода
 void TouchInput()
 {
            //Реализуем управление объектом
            //Получаем коллекцию объектов, содержащих информацию о касаниях экрана
            TouchCollection touchLocations = TouchPanel.GetState();
            //Перебираем коллекцию, присваивая объекту координаты касания
            foreach (TouchLocation touchLocation in touchLocations)
            {
                if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State==TouchLocationState.Moved)
                {
                    //Стрелка "Влево"
                    if (touchLocation.Position.X > 500 && touchLocation.Position.X < 600 
                        && touchLocation.Position.Y > 380 && touchLocation.Position.Y < 480)
                    {
                      Move(3);
                    }
                    //Стрелка "Вправо"
                    if (touchLocation.Position.X > 700 && touchLocation.Position.X < 800 
                        && touchLocation.Position.Y > 380 && touchLocation.Position.Y < 480)
                    {
                       Move(4);
                    }
                    //Стрелка "Вниз"
                    if (touchLocation.Position.X > 600 && touchLocation.Position.X < 700 
                        && touchLocation.Position.Y > 380 && touchLocation.Position.Y < 480)
                    {
                       Move(2);
                    }
                    //Стрелка "Вверх"
                    if (touchLocation.Position.X > 600 && touchLocation.Position.X < 700 
                        && touchLocation.Position.Y > 280 && touchLocation.Position.Y < 380)
                    {
                       Move(1);
                    }
                }
                      
            }
 }

        /// <summary>
        /// Позволяет игровому компоненту обновиться.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        public override void Update(GameTime gameTime)
        {
            //Обработаем касания экрана для реализации перемещения объекта
            TouchInput();
            //Проверим, не вышел ли он за границы экрана, если надо
            //исправим его позицию
            Check();
            //Применим к объекту "силу тяжести"
            GoToDown();
            //Проверим, не прыгнул ли наш объект на объект класса Enemy
            //Вызов этого метода предшествует вызову метода IsCollideWithAny()
            //Иначе прыжок на врага с целью уничтожить его может обернуться
            //Печальными последствиями для нашего объекта
            IsKillEnemy();
            //Проверим на другие столкновения
            IsCollideWithAny();
            
           message = "You have " + sprLives.ToString() +
                " live(s) and " + sprScores + " points";
            if (sprLives < 0)
            {
               message = "You lose";
                this.Dispose();
            }
            base.Update(gameTime);
        }

       
    }
}
Листинг 16.5. Код класса Me

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

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

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

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

16.2. Выводы

В этой лабораторной работе была рассмотрена методика разработки платформенной игры с использованием элементов моделирования физических явлений.

16.3. Задание

  • Разработайте собственные спрайты для визуализации объектов.
  • Реализуйте самостоятельно игру, которая описана выше.
  • Доработайте объект Enemy таким образом, чтобы он мог автоматически (по параметрам, переданным при его конструировании) перемещаться по экрану в горизонтальной или вертикальной плоскости, отталкиваясь от других объектов (кроме лестницы).
Гулич Анна
Гулич Анна
Невозможно пройти тесты, в окне с вопросами пусто
Сашечка Огнев
Сашечка Огнев
Россия, Красноярский край
Андрей Корягин
Андрей Корягин
Россия, Пенза, Вазерская средняя школа, 2001