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