Я создаю клон PacMan просто для удовольствия, и я использую идеальный по пикселям алгоритм столкновения, чтобы определить, когда PacMan сталкивается с лабиринтом и точками (который еще не был реализован в игре). Я знаю, что правильно использую алгоритм, но PacMan сталкивается с лабиринтом, когда и лабиринт, и PacMan не находятся рядом друг с другом. Я рисую для доказательства, показывая экран отладки и игру. Вот изображение и код ниже:
Основной код:
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 PacMan_Bytes
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
enum Direction
{
Left,
Right,
Up,
Down
};
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D maze;
Texture2D pacman;
Color[] mazeclr = new Color[484 * 483];
Color[] pacmanclr = new Color[20 * 27];
Rectangle dest;
Rectangle src;
int frame = 2;
float rotation = 0.0f;
Direction dir;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 484;
graphics.PreferredBackBufferHeight = 483;
dest = new Rectangle(50, 455, 20, 27);
src = new Rectangle(frame, 0, 20, 27);
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
maze = Content.Load<Texture2D>("maze");
pacman = Content.Load<Texture2D>("pacman");
maze.GetData<Color>(mazeclr);
// Get the colors that comprises the image for pixel perfect collision.
pacman.GetData<Color>(0, src, pacmanclr, 0, 20 * 27);
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
this.TargetElapsedTime = TimeSpan.FromSeconds(1.0f / 13.0f);
// Each frame check if the PacMan has collided with the maze.
bool collision = PacmanAnimator.CollidedWithMaze(dest, pacmanclr, maze.Bounds, mazeclr);
// If PacMan doesn't collide with the maze allow the PacMan to move.
if (collision == false)
{
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
PacmanAnimator.AnimateAndMoveLeft(ref rotation, ref frame, ref src, ref dest);
dir = Direction.Left;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
PacmanAnimator.AnimateAndMoveRight(ref rotation, ref frame, ref src, ref dest);
dir = Direction.Right;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
PacmanAnimator.AnimateAndMoveUp(ref rotation, ref frame, ref src, ref dest);
dir = Direction.Up;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
PacmanAnimator.AnimateAndMoveDown(ref rotation, ref frame, ref src, ref dest);
dir = Direction.Down;
}
}
// If collision is true move the PacMan away from the maze's border.
else if (collision == true)
{
if (dir == Direction.Down)
{
dest.Y -= 1;
}
else if (dir == Direction.Up)
{
dest.Y += 1;
}
else if (dir == Direction.Left)
{
dest.X += 1;
}
else if (dir == Direction.Right)
{
dest.X -= 1;
}
}
if (dest.X < 0)
{
dest.X = graphics.PreferredBackBufferWidth - 20;
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Transparent);
spriteBatch.Begin();
spriteBatch.Draw(maze, Vector2.Zero, Color.White);
spriteBatch.Draw(pacman, dest, src, Color.White, rotation, new Vector2(20 / 2, 24 / 2), SpriteEffects.None, 1);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Класс PacmanAnimator:
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 PacMan_Bytes
{
static class PacmanAnimator
{
// Animates and moves the PacMan to the left.
public static void AnimateAndMoveLeft(ref float rotation, ref int frame, ref Rectangle src, ref Rectangle dest)
{
switch (frame)
{
case 2:
{
frame = 40;
break;
}
case 40:
{
frame = 2;
break;
}
}
src.X = frame;
dest.X -= 7;
rotation = MathHelper.ToRadians(180.0f);
}
// Animates and moves the sprite to the right.
public static void AnimateAndMoveRight(ref float rotation, ref int frame, ref Rectangle src, ref Rectangle dest)
{
switch (frame)
{
case 2:
{
frame = 40;
break;
}
case 40:
{
frame = 2;
break;
}
}
src.X = frame;
dest.X += 7;
rotation = 0.0f;
}
// Moves and animates the sprite upward.
public static void AnimateAndMoveUp(ref float rotation, ref int frame, ref Rectangle src, ref Rectangle dest)
{
switch (frame)
{
case 2:
{
frame = 40;
break;
}
case 40:
{
frame = 2;
break;
}
}
src.X = frame;
dest.Y -= 7;
rotation = MathHelper.ToRadians(270.0f);
}
// Moves the sprite downward and animates it.
public static void AnimateAndMoveDown(ref float rotation, ref int frame, ref Rectangle src, ref Rectangle dest)
{
switch (frame)
{
case 2:
{
frame = 40;
break;
}
case 40:
{
frame = 2;
break;
}
}
src.X = frame;
dest.Y += 7;
rotation = MathHelper.ToRadians(90.0f);
}
// Subroutine that deals with collision.
public static bool CollidedWithMaze(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) +
(y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) +
(y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
}
// No intersection found
return false;
}
}
}
PS. Спрайт PacMan имеет прозрачный фон, а черные области, которые вы видите на лабиринте, прозрачны. Так что я знаю, что это не проблема. Кстати простите за такой длинный пост.
Редактировать: Добавлены комментарии, чтобы сделать код более понятным.