You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XNA逐像素碰撞检测实现失败求助:碰撞判定时机异常

逐像素碰撞检测功能失效问题

我正尝试在程序中实现逐像素碰撞检测,但功能无法正常运行。当前运行代码时,点击鼠标任意位置都会绘制一个方形精灵,它们会受重力影响下落。屏幕中还有一个静态黑色方形用于碰撞测试,同时会显示一个红色方形来标记碰撞判定为真的状态(仅用于测试),但该功能完全失效,红色方形总是在错误的时机出现。

private SpriteBatch _spriteBatch;
public Vector2 Origin;
public float Rotation;

Texture2D staticSprite; //static sprite used to check collision with moving particle("white circle")

//white circle gets generated and added to particle list whenever mouse is clicked
List<Particle> particleList = new List<Particle>();

public bool mReleased = true; //mouse button released
public Vector2 clickedMousePos = Vector2.Zero; //default mouse click position
bool collision = false; // no collision by default

Rectangle tTextureRectangle = new Rectangle(200, 200, 100, 100);// rectangle placed for 
Rectangle particleRect;

//public int particleIndex = 0; **code adding later**

MouseState mState = Mouse.GetState();

//PerPixelCollision
Color[] stiffSpriteData;
Color[] tTextureData;      

// intersect method takes two rectangles and color data to check for collision
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;
}

public Game1()
{
    _graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
    IsMouseVisible = true;
}

protected override void Initialize()
{
    _graphics.PreferredBackBufferWidth = 1280;
    _graphics.PreferredBackBufferHeight = 720;
    _graphics.ApplyChanges();

    base.Initialize();
}

protected override void LoadContent()
{
    _spriteBatch = new SpriteBatch(GraphicsDevice);

    staticSprite = Content.Load<Texture2D>("whiteCircle");

    //PerPixelCollision
    stiffSpriteData = new Color[staticSprite.Width * staticSprite.Height];
    staticSprite.GetData<Color>(stiffSpriteData);
}

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
        Exit();

    collision = false;      

    mState = Mouse.GetState();

    clickedMousePos = new Vector2 (mState.Position.X, mState.Position.Y); // updated mouse position when clicked

    //draws a particle ("white circle") with garvity and adds to particleList
    if (mState.LeftButton == ButtonState.Pressed && mReleased == true)
    {
        particleList.Add(new Particle(Content.Load<Texture2D>("whiteCircle"), new Vector2(0,1), new Vector2(clickedMousePos.X, clickedMousePos.Y), 1f));

        var listSize = particleList.Count - 1;

        //PerPixelCollision
        tTextureData = new Color[particleList[listSize].texture.Width * particleList[listSize].texture.Height];
        particleList[listSize].texture.GetData<Color>(tTextureData);

        //Rectangle generates for first moving particle in list for now
        particleRect = new Rectangle((int)particleList[listSize].Position.X, (int)particleList[listSize].Position.Y, particleList[0].texture.Width, particleList[0].texture.Height);

        mReleased = false; 
    }

    if (mState.LeftButton == ButtonState.Released)
    {
        mReleased = true;
    }

    foreach (Particle particle in particleList)
    {
        particle.Update(gameTime);
    }

    // Check collision with person
    if (IntersectPixels(tTextureRectangle, tTextureData, particleRect, stiffSpriteData))
    {
        collision = true;
    }

    base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    _spriteBatch.Begin();

    _spriteBatch.Draw(staticSprite, new Vector2(200, 200), null, Color.Black, Rotation, Origin, 0.1f, SpriteEffects.None, 0);

    foreach (Particle particle in particleList)
    {
         particle.Draw(_spriteBatch);     
    }         

    // this is to test wether a collision was actually detected
    if (collision == true)
    {
        _spriteBatch.Draw(staticSprite, new Vector2(500, 500), null, Color.Red, Rotation, Origin, 0.1f, SpriteEffects.None, 0);
    }

    _spriteBatch.End();

    base.Draw(gameTime);
}

问题分析与修复方案

核心问题点

  1. particleRect未随粒子移动更新:仅在添加新粒子时初始化一次,后续粒子下落时碰撞检测仍使用初始位置的矩形,导致位置判断错误。
  2. 纹理数据未绑定到单个粒子:全局tTextureData只会被最后添加的粒子覆盖,无法检测所有粒子与静态碰撞体的碰撞。
  3. 碰撞检测未遍历所有粒子:仅检查了单个粒子的碰撞状态,忽略了其他粒子的碰撞情况。
  4. 静态碰撞体尺寸不匹配:绘制静态精灵时用了0.1f缩放,但tTextureRectangle设置为100x100,实际碰撞区域与视觉显示不符。

修复步骤

1. 改造Particle类,存储自身碰撞数据

给每个粒子添加纹理数据和碰撞矩形属性,在更新时同步位置:

public class Particle
{
    public Texture2D texture;
    public Vector2 Position;
    public Vector2 Velocity;
    public float Scale;
    public Color[] TextureData;
    public Rectangle CollisionRect;

    public Particle(Texture2D tex, Vector2 vel, Vector2 pos, float scale)
    {
        texture = tex;
        Velocity = vel;
        Position = pos;
        Scale = scale;

        // 初始化自身纹理数据
        TextureData = new Color[tex.Width * tex.Height];
        tex.GetData(TextureData);
    }

    public void Update(GameTime gameTime)
    {
        // 重力逻辑
        Position += Velocity;
        Velocity.Y += 0.1f;

        // 实时更新碰撞矩形(匹配缩放比例)
        CollisionRect = new Rectangle(
            (int)Position.X,
            (int)Position.Y,
            (int)(texture.Width * Scale),
            (int)(texture.Height * Scale)
        );
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(texture, Position, null, Color.White, 0f, Origin, Scale, SpriteEffects.None, 0f);
    }
}

2. 修改碰撞检测逻辑,遍历所有粒子

在Update方法中,逐个检查每个粒子与静态碰撞体的碰撞:

protected override void Update(GameTime gameTime)
{
    // ...原有退出逻辑...

    collision = false;
    // 静态碰撞体的实际碰撞矩形(匹配绘制缩放)
    Rectangle staticCollisionRect = new Rectangle(200, 200, (int)(staticSprite.Width * 0.1f), (int)(staticSprite.Height * 0.1f));

    foreach (Particle particle in particleList)
    {
        particle.Update(gameTime);

        // 检查当前粒子与静态碰撞体的逐像素碰撞
        if (IntersectPixels(staticCollisionRect, stiffSpriteData, particle.CollisionRect, particle.TextureData))
        {
            collision = true;
            // 可选:处理碰撞后的逻辑,比如停止下落
            particle.Velocity.Y = 0;
        }
    }

    // ...原有鼠标创建粒子逻辑(简化)...
    if (mState.LeftButton == ButtonState.Pressed && mReleased == true)
    {
        particleList.Add(new Particle(Content.Load<Texture2D>("whiteCircle"), new Vector2(0,1), clickedMousePos, 0.1f));
        mReleased = false; 
    }

    base.Update(gameTime);
}

3. 移除冗余全局变量

删除不再需要的全局tTextureData和particleRect变量,避免混淆。


内容的提问来源于stack exchange,提问作者Julian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:55:21