Невозможно пройти тесты, в окне с вопросами пусто |
Игровая физика
Рассмотрим код класса 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 таким образом, чтобы он мог автоматически (по параметрам, переданным при его конструировании) перемещаться по экрану в горизонтальной или вертикальной плоскости, отталкиваясь от других объектов (кроме лестницы).