| Невозможно пройти тесты, в окне с вопросами пусто |
Основы обработки сенсорного ввода, перемещение объектов
11.3. Разработка игрового компонента с функциями перемещения и с ограничениями
Можно заметить, что в предыдущих примерах игровые объекты легко пересекали границы игрового поля. В реальных проектах обычно требуется, чтобы подвижный объект не пересекал этих границ. Это значит, что, во-первых – нам нужно узнать координаты границ экрана, а во-вторых – нужно создать такой код, отвечающий за перемещение объекта, который прежде чем переместить объект в новую позицию, проверял бы допустимость такого перемещения. Кроме того, для управления спрайтом в данном примере мы создадим экранный элемент управления.
Создадим новый игровой проект (P5_3), в целом, аналогичный P3_3, разработанному в лабораторной работе №3.
Этот проект содержит игровой компонент, который в нашем примере и будет содержать весь необходимый код. Код компонента вы можете видеть в листинге 11.4
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;
namespace P5_3
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class spriteComp : Microsoft.Xna.Framework.DrawableGameComponent
{
//Изображение
private Texture2D sprTexture;
//Прямоугольник, ограничивающий спрайт
private Rectangle sprRectangle;
//Координата спрайта
private Vector2 sprPosition;
//Границы экрана
private Rectangle scrBounds;
//Направление движения спрайта
private Vector2 sprMove = new Vector2(0,0);
public spriteComp(Game game, ref Texture2D newTexture,
Rectangle newRectangle, Vector2 newPosition
)
: base(game)
{
sprTexture = newTexture;
sprRectangle = newRectangle;
sprPosition = newPosition;
//Работаем в портретном режиме, высота - 480, ширина - 800
scrBounds = new Rectangle(0, 0,
game.Window.ClientBounds.Height,
game.Window.ClientBounds.Width
);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
//Изменение координат в соответствии с данными, имеющимися в sprMove
sprPosition.Y=sprPosition.Y+sprMove.Y;
sprPosition.X = sprPosition.X + sprMove.X;
//Если вышли за пределы экрана - исправляем
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;
}
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
SpriteBatch sprBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
sprBatch.Draw(sprTexture, sprPosition, sprRectangle, Color.White);
base.Draw(gameTime);
}
//Метод для установки направления движения спрайта
public void Move(Vector2 move)
{
sprMove = move;
}
}
}
Листинг
11.4.
Код игрового компонента
Раcсмотрим ключевые моменты этого кода. Здесь мы используем переменную scrBound, в которой храним прямоугольник, соответствующий экрану. Его мы будем использовать для проверки на пересечение спрайтом границы экрана. Мы заполняем данные этого прямоугольника в конструкторе компонента. Объект Rectangle оперирует данными в следующей последовательности – Координата X левого верхнего угла, координата Y левого верхнего угла, ширина, высота. Свойство Height содержит высоту экрана в портретном режиме (800), Width – ширину (480), поэтому, для задания ширины прямоугольника мы используем свойство Height, для задания высоты – Width.
Переменная sprMove содержит скорость движения спрайта по координатам X и Y. По умолчанию она равна 0,0, то есть – спрайт не движется. В методе Update мы прибавляем данные скорости по X и по Y, хранящиеся в данной переменной, к координатам спрайта. Сначала – прибавляем, а потом сверяем полученные значения новых координат с координатами границ экрана. Если выясняется, что спрайт пересёк одну из границ, мы модифицируем координаты спрайта таким образом, чтобы спрайт, при таком пересечении, "упирался" бы в границу экрана. Делаем это мы с учётом размеров самого спрайта, а не ориентируемся лишь на левый верхний угол.
За изменение скорости движения спрайта в определенном направлении отвечает метод Move. Он принимает, при вызове его из основной программы, параметр типа Vector2, значение этого параметра принимает переменная sprMove, которая, в дальнейшем, используется при перемещении спрайта.
Рассмотрим теперь основной код игры. Код класса Game1 вы можете видеть в листинге 11.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 P5_3
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
spriteComp gameObject;
Texture2D texture;
//Изображение стрелок
Texture2D textureArrows;
//Шаг перемещения спрайта, то есть - скорость
float sprSpeed = 4;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
Services.AddService(typeof(SpriteBatch), spriteBatch);
texture = Content.Load<Texture2D>("BallandBats");
CreateNewObject();
textureArrows = Content.Load<Texture2D>("Arrows");
// ЗАДАЧА: используйте здесь this.Content для загрузки контента игры
}
protected void CreateNewObject()
{
gameObject = new spriteComp(this, ref texture,
new Rectangle(18, 9, 17, 88), new Vector2(100, 150));
Components.Add(gameObject);
}
/// <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();
//Получаем коллекцию объектов, содержащих информацию о касаниях экрана
TouchCollection touchLocations = TouchPanel.GetState();
//Перебираем коллекцию, присваивая объекту координаты касания
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed)
{
//Стрелка "Влево"
if (touchLocation.Position.X > 500 && touchLocation.Position.X < 600
&& touchLocation.Position.Y > 380 && touchLocation.Position.Y < 480)
{
gameObject.Move(new Vector2(-sprSpeed,0));
}
//Стрелка "Вправо"
if (touchLocation.Position.X > 700 && touchLocation.Position.X < 800
&& touchLocation.Position.Y > 380 && touchLocation.Position.Y < 480)
{
gameObject.Move(new Vector2(sprSpeed, 0));
}
//Стрелка "Вниз"
if (touchLocation.Position.X > 600 && touchLocation.Position.X < 700
&& touchLocation.Position.Y > 380 && touchLocation.Position.Y < 480)
{
gameObject.Move(new Vector2(0, sprSpeed));
}
//Стрелка "Вверх"
if (touchLocation.Position.X > 600 && touchLocation.Position.X < 700
&& touchLocation.Position.Y > 280 && touchLocation.Position.Y < 380)
{
gameObject.Move(new Vector2(0, -sprSpeed));
}
}
if (touchLocation.State == TouchLocationState.Released)
{
gameObject.Move(new Vector2(0,0));
}
}
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
//Сначала выведем изображение стрелок
spriteBatch.Draw(textureArrows, new Rectangle(500, 280, 300, 200), Color.White);
//Игровой компонент будет выведен поверх изображения стрелок
base.Draw(gameTime);
spriteBatch.End();
}
}
}
Листинг
11.5.
Код игрового проекта
Здесь мы используем изображение прямоугольника из изображения BallAndBats и изображение четырёх стрелок из изображения Arrows. Стрелки нарисованы на прозрачном фоне, для хранения этого изображения используется PNG-файл. Каждая из стрелок ограничена прямоугольником размером 100х100 пикселей, блок стрелок имеет ширину 300 пикселей, высоту – 200. Он размещен в правом нижнем углу экрана. Переменая sprSpeed задаёт скорость движения спрайта в выбранном пользователем направлении.
В методе Update мы пользуемся уже известным из прошлых примеров механизмом. Но здесь нет прямого воздействия координаты касания экрана на координаты спрайта. Здесь мы проверям координату касания, и, если она совпадает с областью, занимаемой одной из стрелок (вернее – попадает в квадрат, ограничивающий эту стрелку), передаём в игровой объект желаемое направление (координата Х или Y, положительное или отрицательное значение) и скорость (она задаётся единой для всех перемещений через переменную sprSpeed).
Обратите внимание – мы проверяем статус нажатия каждый цикл Update (30 раз в секунду, другими словами, в соответствии с настройками по умолчанию). Если нажатие зафиксировано – мы проверяем координаты, если координаты совпадают с одной из стрелок, выполняем перемещение спрайта. При первоначальном обнаружении касания состояние объекта, представляющего касание, устанавливается в Pressed. В этот момент мы устанавливаем желаемую скорость и направление перемещения спрайта.
Если пользователь не убирает палец с экрана, неважно, перемещает он его или нет, состояние устанавливается в Moved. Нас, в данном случае, это состояние не интересует. При появлении события Pressed в соответствующей координате мы начинаем перемещение спрайта.
Когда пользователь убирает палец со стрелки, объект, представляющий касание, переходит в состояние Released и мы передаем в объект gameObject информацию о том, что перемещение объекта следует остановить.
Спрайт перемещается по экрану в направлении, заданном стрелкой, которой касается пользователь, до тех пор, пока он её касается.
На рис. 11.3 вы можете видеть игровой экран проекта P5_3.
