Одним из ключевых моментов в игре является столкновения игровых объектов. Например, герой натыкается со стеной, пуля попадает в героя и так далее.
К счастью, проверка столкновений двухмерных объектов в MonoGame довольно проста. Каждый спрайт представляет некоторое прямоугольное пространство. Проверка на пересечение его с другим прямоугольником фактически будет означать проверку на столкновение.
Прямоугольник в MonoGame представлен классом Rectangle. Его метод Intersects() позволяет узнать, пересекается ли он с другим прямоугольником.
Например, пусть у нас есть два спрайта good.png и evil.png, импортированные в проект.
Определим их столкновение, и если столкновение имеется, тогда окрасим игровое поле в красный цвет:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Game1 { public class Game1 : Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D goodTexture; // наш спрайт Texture2D evilTexture; // второй спрайт Vector2 goodSpritePosition; // позиция нашего спрайта Vector2 evilSpritePosition; // позиция второго спрайта Point goodSpriteSize; // размер нашего спрайта Point evilSpriteSize; // размер второго спрайта float goodSpriteSpeed = 5f; float evilSpriteSpeed = 2f; Color color = Color.CornflowerBlue; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; goodSpritePosition = Vector2.Zero; // помещаем второй спрайт на середину по оси Х evilSpritePosition = new Vector2(Window.ClientBounds.Width / 2, 0); } protected override void Initialize() { base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); goodTexture = Content.Load<Texture2D>("good"); evilTexture = Content.Load<Texture2D>("evil"); // Устанавливаем размеры спрайтов goodSpriteSize = new Point(goodTexture.Width, goodTexture.Height); evilSpriteSize = new Point(evilTexture.Width, evilTexture.Height); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { KeyboardState keyboardState = Keyboard.GetState(); if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || keyboardState.IsKeyDown(Keys.Escape)) Exit(); // перемещаем второй спрайт evilSpritePosition.X += evilSpriteSpeed; if (evilSpritePosition.X > Window.ClientBounds.Width - evilTexture.Width || evilSpritePosition.X < 0) evilSpriteSpeed *= -1; // перемещаем наш спрайт клавиатурой if (keyboardState.IsKeyDown(Keys.Left)) goodSpritePosition.X -= goodSpriteSpeed; if (keyboardState.IsKeyDown(Keys.Right)) goodSpritePosition.X += goodSpriteSpeed; if (keyboardState.IsKeyDown(Keys.Up)) goodSpritePosition.Y -= goodSpriteSpeed; if (keyboardState.IsKeyDown(Keys.Down)) goodSpritePosition.Y += goodSpriteSpeed; // проверяем, не убежал ли наш спрайт с игрового поля if (goodSpritePosition.X < 0) goodSpritePosition.X = 0; if (goodSpritePosition.Y < 0) goodSpritePosition.Y = 0; if (goodSpritePosition.X > Window.ClientBounds.Width - goodSpriteSize.X) goodSpritePosition.X = Window.ClientBounds.Width - goodSpriteSize.X; if (goodSpritePosition.Y > Window.ClientBounds.Height - goodSpriteSize.Y) goodSpritePosition.Y = Window.ClientBounds.Height - goodSpriteSize.Y; if (Collide()) color = Color.Red; else color = Color.CornflowerBlue; base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(color); spriteBatch.Begin(); spriteBatch.Draw(goodTexture, goodSpritePosition, Color.White); spriteBatch.Draw(evilTexture, evilSpritePosition, Color.White); spriteBatch.End(); base.Draw(gameTime); } protected bool Collide() { Rectangle goodSpriteRect = new Rectangle((int)goodSpritePosition.X, (int)goodSpritePosition.Y, goodSpriteSize.X, goodSpriteSize.Y); Rectangle evilSpriteRect = new Rectangle((int)evilSpritePosition.X, (int)evilSpritePosition.Y, evilSpriteSize.X, evilSpriteSize.Y); return goodSpriteRect.Intersects(evilSpriteRect); } } }
Нашим спрайтом мы управляем клавиатурой, а второй спрайт просто перемещается вперед и назад по оси X.
Вся обработка столкновений заключена в методе Collide()
:
protected bool Collide() { Rectangle goodSpriteRect = new Rectangle((int)goodSpritePosition.X, (int)goodSpritePosition.Y, goodSpriteSize.X, goodSpriteSize.Y); Rectangle evilSpriteRect = new Rectangle((int)evilSpritePosition.X, (int)evilSpritePosition.Y, evilSpriteSize.X, evilSpriteSize.Y); return goodSpriteRect.Intersects(evilSpriteRect); }
В данном случае формируем два прямоугольника и применяем метод Intersects()
. Если он возвращает true, значит оба прямоугольника
пересекаются.
Это довольно простой способ проверки столкновения двухмерных объектов, в то же время он не идеален. Так как вполне возможно, что у нас могут быть более сложные игровые персонажи, которые не являются прямоугольными. Такие объекты либо надо разбивать на отдельные прямоугольники и затем эти прямоугольники проверять на пересечение. Либо пожертвовать точностью вычислений столкновения, и определять его приблизительно на основе пересечения спрайтов.
В некоторых играх есть такое действие, как остановка игры, когда пользователь, например, убирает мышку с игрового поля. В своей игре мы можем добавить подобное поведение, причем довольно легко. Для этого изменим код метода Update():
protected override void Update(GameTime gameTime) { if(IsActive) { KeyboardState keyboardState = Keyboard.GetState(); if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || keyboardState.IsKeyDown(Keys.Escape)) Exit(); // остальной код метода base.Update(gameTime); } }
Флаг IsActive
указывает, активно ли приложение. И если мы выведем указатель мыши за пределы игрового окна, то игровой цикл приостановится.