Невозможно пройти тесты, в окне с вопросами пусто |
Взаимодействие объектов
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 после попадания по трём спрайтам
Рассмотрим еще один пример проверки на "попадание" точки в определенную область.