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); }
问题分析与修复方案
核心问题点
particleRect未随粒子移动更新:仅在添加新粒子时初始化一次,后续粒子下落时碰撞检测仍使用初始位置的矩形,导致位置判断错误。- 纹理数据未绑定到单个粒子:全局
tTextureData只会被最后添加的粒子覆盖,无法检测所有粒子与静态碰撞体的碰撞。 - 碰撞检测未遍历所有粒子:仅检查了单个粒子的碰撞状态,忽略了其他粒子的碰撞情况。
- 静态碰撞体尺寸不匹配:绘制静态精灵时用了
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
相关产品推荐
相关产品推荐

