Невозможно пройти тесты, в окне с вопросами пусто |
Основы обработки сенсорного ввода, перемещение объектов
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.