XNA-像素碰撞Per-Pixel Collision(程式範例)

XNA-像素碰撞Per-Pixel Collision(程式範例)

    繼續上次矩形碰撞Rectangle Collision的範例程式,將它修改成使用像素偵測的方法去檢驗是否有碰撞到,這個碰撞的方法會比內建的矩形碰撞較準確,至於它大致做的方法在之前的文章如何實現碰撞偵測?有提到過,主要參考到XNA開發者俱樂部的Collision Series 2: 2D Per-Pixel Collision

    此範例也是運用鍵盤的上、下、左、右鍵來移動人物圖片,最主要就是看它與磚塊圖片碰撞的地方,會比矩形偵測來的準確。

程式範例:


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;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Per_Pixel_Collision
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D block;
        Texture2D person;
        Vector2 blockpos;
        Vector2 personpos;
        Color[] blockTextureData;
        Color[] personTextureData;
        int movespeed = 3;
        bool personHit = false;

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

        protected override void Initialize()
        {
            blockpos = new Vector2(0, 0);
            personpos = new Vector2(50, 0);
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteBatch = new SpriteBatch(GraphicsDevice);
            block = Content.Load<Texture2D>("Block");
            blockTextureData = new Color[block.Width * block.Height];
            block.GetData(blockTextureData);
            person = Content.Load<Texture2D>("Person");
            personTextureData = new Color[person.Width * person.Height];
            person.GetData(personTextureData);
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            KeyboardState keyboard = Keyboard.GetState();
            if (keyboard.IsKeyDown(Keys.Up))
            {
                personpos = new Vector2(personpos.X, personpos.Y - movespeed);
            }
            if (keyboard.IsKeyDown(Keys.Down))
            {
                personpos = new Vector2(personpos.X, personpos.Y + movespeed);
            }
            if (keyboard.IsKeyDown(Keys.Left))
            {
                personpos = new Vector2(personpos.X - movespeed, personpos.Y);
            }
            if (keyboard.IsKeyDown(Keys.Right))
            {
                personpos = new Vector2(personpos.X + movespeed, personpos.Y);
            }
            Rectangle personRectangle = new Rectangle((int)personpos.X, (int)personpos.Y, person.Width, person.Height);
            Rectangle blockRectangle = new Rectangle((int)blockpos.X, (int)blockpos.Y, block.Width, block.Height);
            if (IntersectPixels(personRectangle,personTextureData,blockRectangle, blockTextureData ))
            {
                personHit = true;
            }
            else
            {
                personHit = false;
            }
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            if (personHit == true)
                GraphicsDevice.Clear(Color.Red);
            else
                GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            spriteBatch.Draw(block, blockpos, Color.White);
            spriteBatch.Draw(person, personpos, Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        }

        public bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)
        {
            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);

            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    Color colorA = dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width];
                    Color colorB = dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width];
                    if ((colorA.A != 0 ) && (colorB.A != 0))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}

    24~25行宣告的變數是用來存放兩圖片RGBA的資訊。

    47~48行和50~51行運用讀取圖片完的寬和高來決定存放該圖所需要的陣列大小,並且取出兩圖片RGBA的資訊。

    79行判斷是否碰撞到的函數IntersectPixels需要傳入的參數為兩圖片的Rectange型態和兩圖片Color型態的資訊。

    105~108行則是找出兩圖片交集的矩形範圍是多少乘多少。

    110行迴圈最主要檢查交集圖片中每個點是否有碰撞到。

    114~115行得到交集圖片各點的RGBA的資料。

    116行運用圖片的alpha值來判斷是否有碰撞到,如果交集圖片的某點alpha值都不是0那就代表碰到了。

1

這樣表示沒有碰撞到

這樣才表示有碰撞到

    122行交集圖片沒有碰撞在一起的點,所以回傳false。

最後附上專案檔Per-Pixel Collision.rar