| Невозможно пройти тесты, в окне с вопросами пусто |
Взаимодействие объектов
14.3. Обработка попадания точки в пределы объекта
Создадим простую игру на базе проекта P8_2. Игрок должен за минимальное время попасть по каждому из 10 находящихся на экране объектов, коснувшись его. Объект, которого коснулись, уничтожается.
Здесь нам понадобится алгоритм проверки попадания точки в пределы спрайта. Как и прежде, спрайты представлены прямоугольниками. Для проверки попадания точки в прямоугольник, о котором известна координата его левой верхней точки, ширина и высота, воспользуемся таким алгоритмом (листинг 14.6.). A – это прямоугольник, B – это точка.
Если (А.X+A.Ширина > B.X И A.X < B.X И A.Y+A.Высота>В.Y И A.Y<B.Y) Тогда Есть столкновение Иначе> Нет столкновенияЛистинг 14.6. Алгоритм проверки попадания точки в прямоугольник
Создадим новый проект – P8_3, на базе проекта P8_2. В листинге 14.7 вы можете видеть код объекта Game1.
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 P8_3
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D texture;
//Количество набранных очков
int Score;
//Завершена ли игра
bool IsWin = false;
//Для хранения сообщения, выводимого пользователю
string message;
//Для хранения шрифта
SpriteFont MyFont;
//Для хранения позиции последнего касания
Vector2 touchLoc=new Vector2(-1,-1);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
//Модификация счёта игры
public void SetScore()
{
Score++;
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
Score = 0;
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Загружаем шрифт
MyFont = Content.Load<SpriteFont>("MyFont");
Services.AddService(typeof(SpriteBatch), spriteBatch);
texture = Content.Load<Texture2D>("BallandBats");
CreateNewObject();
}
protected void CreateNewObject()
{
//Цикл от 1 до 10
for (int i = 0; i < 10; i++)
{
//Добавляем в список компонентов новый компонент класса spriteComp
Components.Add(new spriteComp(this, ref texture,
new Rectangle(16, 203, 17, 17), i));
}
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Если цель игры не достигнута – выводим
//информацию о текущем состоянии игры
if (IsWin == false)
message = "Destroyed " + Score.ToString() + " in " + gameTime.TotalGameTime.Seconds + " sec.";
//Если цель достигнута – выводим сообщение об этом
if (Score == 10 && IsWin == false)
{
message = "You complete destruction in " + gameTime.TotalGameTime.Seconds + " sec.";
IsWin = true;
}
//Сбросим позицию касания в такую, в которой заведомо не могут оказаться
//игровые объекты
touchLoc = new Vector2(-1, -1);
//Получаем коллекцию объектов, содержащих информацию о касаниях экрана
TouchCollection touchLocations = TouchPanel.GetState();
//Перебираем коллекцию, присваивая объекту координаты касания
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed)
{
//Сохраняем координату касания в переменную touchLock
touchLoc = touchLocation.Position;
}
}
base.Update(gameTime);
}
//Получить позицию последнего касания экрана
public Vector2 GetPos()
{
return touchLoc;
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// ЗАДАЧА: добавьте здесь код отрисовки
spriteBatch.Begin();
//Выведем игровые объекты
base.Draw(gameTime);
//Выведем надпись
spriteBatch.DrawString(MyFont, message, new Vector2(0, 0), Color.Red);
spriteBatch.End();
}
}
}
Листинг
14.7.
Код класса Game1
Здесь мы создаем 10 объектов класса spriteComp и проверяем, не достигнута ли цель игры – уничтожение всех объектов. Для контроля количества уничтоженных объектов используем переменную Score, для контроля за достижением цели – переменную IsWin.
Обработку касаний мы производим здесь же, сохраняя последнюю позицию касания в переменной touchLock. Для того, чтобы исключить случайное "попадание" спрайта в позицию касания, мы, если касания нет (и при создании переменной) устанавливаем ее значение в такое, в котором ни один спрайт, в соответствии с логикой игры, оказаться не может (-1, -1).
Основная работа ведется в объектах класса spriteComp. В листинге 14.8 вы можете найти соответствующий им код.
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.Media;
using Microsoft.Devices;
namespace P8_3
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class spriteComp : Microsoft.Xna.Framework.DrawableGameComponent
{
protected Texture2D sprTexture;
protected Rectangle sprRectangle;
protected Vector2 sprPosition;
protected Rectangle scrBounds;
//Для генерирования случайных чисел
protected Random randNum;
//Объект для доступа к основному игровому объекту
protected Game1 myGame;
//Цвет спрайта
protected Color sprColor;
//Скорость перемещения
public Vector2 speed;
public spriteComp(Game1 game, ref Texture2D newTexture,
Rectangle newRectangle, int Seed)
: base(game)
{
sprTexture = newTexture;
sprRectangle = newRectangle;
//Инициализируем счетчик
randNum = new Random(Seed);
myGame = game;
scrBounds = new Rectangle(0, 0,
game.Window.ClientBounds.Height,
game.Window.ClientBounds.Width);
//Устанавливаем стартовую позицию спрайта
setSpriteToStart();
//Если спрайт сталкивается с каким-нибудь спрайтом - изменим его позицию
while (howManyCollides() > 0)
{
setSpriteToStart();
}
//Зададим случайный цвет для придания изображению
//соответствующего оттенка
sprColor = new Color((byte)randNum.Next(0, 255), (byte)randNum.Next(0, 255), (byte)randNum.Next(0, 255));
//Переменная для хранения скорости пока пуста
speed = new Vector2((float)randNum.Next(-5, 5), (float)randNum.Next(-5, 5));
// ЗАДАЧА: здесь создаются дочерние компоненты
}
//Проверка, не установлены ли спрайты в позиции с перекрытием других спрайтов
int howManyCollides()
{
int howMany = 0;
foreach (spriteComp spr in myGame.Components)
{
if (this != spr)
{
if (this.sprCollide(spr))
{
howMany++;
}
}
}
return howMany;
}
//Установка спрайта в случайную стартовую позицию
void setSpriteToStart()
{
sprPosition.X = (float)randNum.NextDouble() * (scrBounds.Width - sprRectangle.Width);
sprPosition.Y = (float)randNum.NextDouble() * (scrBounds.Height - sprRectangle.Height);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Перемещение спрайта
public virtual void Move()
{
sprPosition += speed;
}
//Проверка допустимости перемещения
void Check()
{
if (sprPosition.X < scrBounds.Left)
{
sprPosition.X = scrBounds.Left;
speed.X *= -1;
}
if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
{
sprPosition.X = scrBounds.Width - sprRectangle.Width;
speed.X *= -1;
}
if (sprPosition.Y < scrBounds.Top)
{
sprPosition.Y = scrBounds.Top;
speed.Y *= -1;
}
if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
{
sprPosition.Y = scrBounds.Height - sprRectangle.Height;
speed.Y *= -1;
}
}
public bool sprCollide(spriteComp 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 PointCollide(Vector2 point)
{
return (this.sprPosition.X + this.sprRectangle.Width > point.X &&
this.sprPosition.X < point.X &&
this.sprPosition.Y + this.sprRectangle.Height > point.Y &&
this.sprPosition.Y < point.Y);
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
//Вызов метода, проверяющего, коснулся ли пользователь спрайта
TouchCollide();
//Вызов метода для перемещения спрайта
Move();
//Проверка на столкновение с границами экрана
Check();
//Вызов проверки на столкновение с другими спрайтами
IsSpriteCollide();
//Возможно, при коррекции спрайта относительно другого спрайта,
//произошло перекрытие с другим спрайтом или спрайтами
//это приводит к "зависанию" спрайтов - они остаются на одном
//месте в "сцепленном" состоянии. Для того, чтобы этого избежать,
//мы корректируем позиции спрайтов до тех пор, пока каждый из них
//гарантированно не окажется вне других спрайтов
while (howManyCollides() > 0)
{
IsSpriteCollide();
}
base.Update(gameTime);
}
//Проверим, коснулся ли пользователь спрайта
void TouchCollide()
{
//Если точка касания попала в спрайт
if (PointCollide(myGame.GetPos()))
{
//Изменим счёт игры
myGame.SetScore();
//Включим вибрацию
VibrateController.Default.Start(TimeSpan.FromMilliseconds(100));
//Уничтожим игровой объект
this.Dispose();
}
}
//Если спрайт перекрыл другой спрайт - изменить его скорость и
//применить изменения к позиции спрайта
void IsSpriteCollide()
{
foreach (spriteComp spr in myGame.Components)
{
if (spr != this)
{
if (this.sprCollide(spr))
{
this.speed *= -1;
this.sprPosition += this.speed;
}
}
}
}
public override void Draw(GameTime gameTime)
{
SpriteBatch sprBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
sprBatch.Draw(sprTexture, sprPosition, sprRectangle, sprColor);
base.Draw(gameTime);
}
}
}
Листинг
14.8.
Код игрового компонента
В этом коде мы поступаем так же, как и в предыдущем примере. Главная особенность – кроме прочего, мы контролируем попадания прикосновений к экрану в границы спрайтов. Если такое попадание произошло, мы меняем счет игры, включаем кратковременную вибрацию и уничтожаем игровой объект. Напомним, что для использования вибрации нужно подключить библиотеку Microsoft.Phone (рис. 14.7) и пространство имён Microsoft.Devices.
На рис. 14.8 вы можете видеть игровой экран проекта P8_3 после попадания по трём спрайтам
Рассмотрим еще один пример проверки на "попадание" точки в определенную область.

