单个图像映射上的玩家碰撞

时间:2014-01-20 14:54:28

标签: c# image xna sprite collision

我遇到的问题是围绕如何使用名为Test Map.png的单个png包围我的大脑:

Test Map

屏幕周围有黑色边框,玩家可以使用较小的踩踏块来测试碰撞。我通过使用一个玩家类和主类Game1.cs来引力和更新游戏。我用这个球:

Black Ball

这是我在屏幕上移动的精灵。

以下是player.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Gravity_Test_V2
{
    class Player
    {
        public Texture2D Texture;

        public Vector2 Velocity;
        public Vector2 Position;
        public float ground;
        private float Speed;

        private Rectangle screenBound;

        public bool isJumping; //are we jumping or not
        public bool goingUp; //if going up or not 

        public float initialVelocity; //initial velocity
        private float jumpU; //How high the player can jump
        private float g; //gravity
        public float t; //time

        private KeyboardState prevKB;

        public Player(Texture2D Texture, Vector2 Position, float Speed, Rectangle screenBound)
        {
            this.Texture = Texture;
            this.Position = Position;
            ground = Position.Y;
            this.Speed = Speed;
            this.screenBound = screenBound;
            Velocity = Vector2.Zero;
            isJumping = goingUp = true;
            jumpU = 2.5f;
            g = -9.8f;
            t = 0;
        }

        public void Update(GameTime gameTime)
        {
            Position.X += (Velocity.X * Speed);
            //Set the Y position to be subtracted so that the upward movement would be done by decreasing the Y value
            Position.Y -= (Velocity.Y * Speed);

            goingUp = (Velocity.Y > 0);

            // TODO: Add your update logic here
            if (isJumping == true)
            {
                //motion equation using velocity: v = u + at
                Velocity.Y = (float)(initialVelocity + (g * t));
                //Increase the timer
                t += (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            if (isJumping == true && Position.Y > screenBound.Height - Texture.Height)
            {
                Position.Y = ground = screenBound.Height - Texture.Height;
                Velocity.Y = 0;
                isJumping = false;
                t = 0;
            }

            if (Position.X < 0)
            {
                //if Texture touches left side of the screen, set the position to zero and the velocity to zero.
                Position.X = 0;
                Velocity.X = 0;
            }
            else if (Position.X + Texture.Width > screenBound.Width)
            {
                //if Texture touches left side of the screen, set the position to zero and the velocity to zero.
                Position.X = screenBound.Width - Texture.Width;
                Velocity.X = 0;
            }
            if (Position.Y < 0)
            {
                //if the Texture touches the top of the screen, reset the timer and set the initial velocity to zero.
                Position.Y = 0;
                t = 0;
                initialVelocity = 0;
            }
        }

        public void Input(KeyboardState keyState)
        {
            if (keyState.IsKeyDown(Keys.Space) && (isJumping == false || Position.Y == ground))
            {
                isJumping = true;
                initialVelocity = jumpU;
            }
            if (keyState.IsKeyDown(Keys.Left) && !keyState.IsKeyDown(Keys.Right))
            {

                if (Velocity.X > -1.0f)
                {
                    Velocity.X -= (1.0f / 10);
                }
                else
                {
                    Velocity.X = -1.0f;
                }
            }
            else if (!keyState.IsKeyDown(Keys.Left) && keyState.IsKeyDown(Keys.Right))
            {
                if (Velocity.X < 1.0f)
                {
                    Velocity.X += (1.0f / 10);
                }
                else
                {
                    Velocity.X = 1.0f;
                }
            }
            else
            {
                if (Velocity.X > 0.05 || Velocity.X < -0.05)
                    Velocity.X *= 0.70f;
                else
                    Velocity.X = 0;
            }

            prevKB = keyState;
        }

        public void Fall()
        {
            t = 0;
            initialVelocity = 0;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(Texture, new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height), Color.White);
        }
    }
}

是否有一些简单的方法可以让玩家在测试图的纹理内部与Test Map.png发生碰撞?

编辑:2014年1月21日上午9点'ish'

第一级:

Level One Black and Transparent

编辑:2014年1月21日10:27

我使用了一个基于pixle的系统来测试玩家是否与一个对象相关,但我试图将该对象从游戏中分解为类,它将停止工作。我将我的动作和碰撞项目混合在一起,试着让它发挥作用。我拿player.cs(我没有改变)并将基于像素的碰撞添加到Game1.cs我需要知道如何制作播放器,女巫正由player.cs类控制,被Game1.cs类看到并被player.cs类调用时使用。

注意*我也改变了它,以便游戏使用基于像素的系统提供的下降三角形。当我能够完成这项工作时,我将添加测试图像。

目前玩家可以移动和跳跃,但不会被重新考虑为碰撞。

编辑:2014年1月21日10:34

我使用了2个项目:

碰撞: http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel

动态系统: http://gamepopper.co.uk/academic-projects/2012-2/jumping-platformer-example/

我把它们混合在一起并使用它们来制作我自己的平台。

Game1.cs

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 Collision_Test
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        KeyboardState prevKB;

        Player player;

        SpriteFont font;

        Texture2D personTexture;
        Texture2D blockTexture;

        // The color data for the images; used for per pixel collision
        Color[] personTextureData;
        Color[] blockTextureData;

        Vector2 personPosition;
        const int PersonMoveSpeed = 5;

        public static int screenWidth = 800;
        public static int screenHeight = 500;

        // Blocks
        List<Vector2> blockPositions = new List<Vector2>();
        float BlockSpawnProbability = 0.01f;
        const int BlockFallSpeed = 1;

        Random random = new Random();

        // For when a collision is detected
        bool personHit = false;

        // The sub-rectangle of the drawable area which should be visible on all TVs
        Rectangle safeBounds;
        // Percentage of the screen on every side is the safe area
        const float SafeAreaPortion = 0.05f;


        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            this.graphics.PreferredBackBufferWidth = screenWidth;
            this.graphics.PreferredBackBufferHeight = screenHeight;

            this.graphics.ApplyChanges();
        }

        protected override void Initialize()
        {
            base.Initialize();
            // Calculate safe bounds based on current resolution
            Viewport viewport = graphics.GraphicsDevice.Viewport;
            safeBounds = new Rectangle(
                (int)(viewport.Width * SafeAreaPortion),
                (int)(viewport.Height * SafeAreaPortion),
                (int)(viewport.Width * (1 - 2 * SafeAreaPortion)),
                (int)(viewport.Height * (1 - 2 * SafeAreaPortion)));
            // Start the player in the center along the bottom of the screen
            personPosition.X = (safeBounds.Width - personTexture.Width) / 2;
            personPosition.Y = safeBounds.Height - personTexture.Height;
        }


        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            blockTexture = Content.Load<Texture2D>("Block");
            personTexture = Content.Load<Texture2D>("Person");

            font = Content.Load<SpriteFont>("Font");

            player = new Player(personTexture, Vector2.Zero, 6.0f, new Rectangle(0, 0,
                this.graphics.PreferredBackBufferWidth,
                this.graphics.PreferredBackBufferHeight));

            // Extract collision data
            blockTextureData =
                new Color[blockTexture.Width * blockTexture.Height];
            blockTexture.GetData(blockTextureData);
            personTextureData =
                new Color[personTexture.Width * personTexture.Height];
            personTexture.GetData(personTextureData);

            // Create a sprite batch to draw those textures
            spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
        }

        void HandleInput(KeyboardState keyState)
        {
            player.Input(keyState);
            if (prevKB.IsKeyUp(Keys.F) && keyState.IsKeyDown(Keys.F))
            {
                this.graphics.ToggleFullScreen();
                this.graphics.ApplyChanges();
            }
        }

        protected override void Update(GameTime gameTime)
        {
            // Get input
            KeyboardState keyboard = Keyboard.GetState();
            GamePadState gamePad = GamePad.GetState(PlayerIndex.One);

            HandleInput(Keyboard.GetState());
            player.Update(gameTime);

            prevKB = Keyboard.GetState();

            // Allows the game to exit
            if (gamePad.Buttons.Back == ButtonState.Pressed ||
                keyboard.IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            // Spawn new falling blocks
            if (random.NextDouble() < BlockSpawnProbability)
            {
                float x = (float)random.NextDouble() *
                    (Window.ClientBounds.Width - blockTexture.Width);
                blockPositions.Add(new Vector2(x, -blockTexture.Height));
            }

            // Get the bounding rectangle of the person
            Rectangle personRectangle =
                new Rectangle((int)personPosition.X, (int)personPosition.Y,
                personTexture.Width, personTexture.Height);

            // Update each block
            personHit = false;
            for (int i = 0; i < blockPositions.Count; i++)
            {
                // Animate this block falling
                blockPositions[i] =
                    new Vector2(blockPositions[i].X,
                                blockPositions[i].Y + BlockFallSpeed);

                // Get the bounding rectangle of this block
                Rectangle blockRectangle =
                    new Rectangle((int)blockPositions[i].X, (int)blockPositions[i].Y,
                    blockTexture.Width, blockTexture.Height);

                // Check collision with person
                if (IntersectPixels(personRectangle, personTextureData,
                                    blockRectangle, blockTextureData))
                {
                    personHit = true;
                }

                // Remove this block if it have fallen off the screen
                if (blockPositions[i].Y > Window.ClientBounds.Height)
                {
                    blockPositions.RemoveAt(i);

                    // When removing a block, the next block will have the same index
                    // as the current block. Decrement i to prevent skipping a block.
                    i--;
                }
            }

            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 device = graphics.GraphicsDevice;

            // Change the background to red when the person was hit by a block
            if (personHit)
            {
                device.Clear(Color.Red);
            }
            else
            {
                device.Clear(Color.CornflowerBlue);
            }


            spriteBatch.Begin();

            player.Draw(spriteBatch);

            // Draw blocks
            foreach (Vector2 blockPosition in blockPositions)
                spriteBatch.Draw(blockTexture, blockPosition, Color.White);

            spriteBatch.End();


            base.Draw(gameTime);
        }


        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels
        /// between two sprites.
        /// </summary>
        /// <param name="rectangleA">Bounding rectangle of the first sprite</param>
        /// <param name="dataA">Pixel data of the first sprite</param>
        /// <param name="rectangleB">Bouding rectangle of the second sprite</param>
        /// <param name="dataB">Pixel data of the second sprite</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        static bool IntersectPixels(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;
        }
    }
}

2 个答案:

答案 0 :(得分:1)

如果纹理的背景是透明的,您可以使用Per-Pixel Collision detection.

基本上它会检查像素而不是矩形框来确定是否发生了碰撞。鉴于你的“球员”是一个球,无论如何使用它可能是一个好主意。

答案 1 :(得分:0)

考虑到你的地图只是黑色和白色,您可以在开始游戏之前处理它并获得每个Rectangle黑色区域的坐标,然后通过检查球边界框和矩形之间的交叉点来使用它来使用简单的碰撞检测。

例如,在伪代码中:

你有一个清单:

List<Rectangle> rects = new List<Rectangle>();

void LoadMap()
{
    Texture2D map = Content.Load<Texture2D>(@"TexturePath");
    //Get a 1D array with image's pixels
    Color[] map_pixels = new Color[map.Width * map.Height];
    map.GetData(map_pixels);

    //Convert it in a 2D array
    Color[,] map_pixels_2D = new Color[map.Width, map.Height];
    for (int x = 0; x < map.Width; x++)
       for (int y = 0; y < map.Height; y++)
          map_pixels_2D[x, y] = map_pixels[x + y * map.Width];

    //**NOTE THAT**: From here it is just an example, probably not working good,
    //I wrote it just to share the idea
    //Here goes the code to trace rectangles in the map
    Rectangle r = Rectangle.Empty;
    bool NWvertex_done = false, NEvertex_done = false, SWvertex_done = false;
    for (int x = 0; x < map.Width; x++)
    {
       if (!SWvertex_done)
       {
          if (map_pixels_2D[x, y+1] == Color.White); //last bottom vertex
          {
             r.Height = r.Y + y;
             SWvertex_done = true;
             rects.Add(r);
             NWvertex_done = false;
             NEvertex_done = false;
             r = Rectangle.Empty;
          }
       }
       for (int y = 0; y < map.Height; y++)
       {
          if (map_pixels_2D[x, y] != Color.White
          {
             if (!NWvertex_done)
             {
               SWvertex_done = false;
               r.X = x;
               r.Y = y;
               NWvertex_done = true;
             }
             else if(!NEvertex_done)
             {
               if (map_pixels_2D[x, y+1] == Color.White); //last right vertex
               {
                 r.Width = r.X + x;
                 NEvertex_done = true;
               }
             }
          }
       } 
     }
}
public override void Update(GameTime gametime)
{
     //maybe other things
     //

     foreach (Rectangle rect in rects)
     {
        //Better with Distance of ball-center and rect
        if (ballRect.Intersect(rect))
        {
           //there is a collision!
           //Do something
        }
        break;
     }

     //maybe other things
     //
}