C#中的每像素碰撞问题

时间:2009-04-27 11:24:29

标签: c# collision-detection pixel

我正在用C#编写一个小的2d游戏引擎用于我自己的目的,除了精灵碰撞检测之外它工作正常。我决定让它成为每像素检测(对我来说最容易实现),但它没有像预期的那样工作。代码在发生之前很久就会检测到它。我已经检查了检测的每个组成部分,但我找不到问题。

碰撞检测方法:

public static bool CheckForCollision(Sprite s1, Sprite s2, bool perpixel) {
    if(!perpixel) {
        return s1.CollisionBox.IntersectsWith(s2.CollisionBox);
    }
    else {
        Rectangle rect;
        Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
        int posx1 = rect.X;
        int posy1 = rect.Y;

        Image img2 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s2.Image, s2.Position, s2.Origin, s2.Rotation, out rect), s2.Scale);
        int posx2 = rect.X;
        int posy2 = rect.Y;

        Rectangle abounds = new Rectangle(posx1, posy1, (int)img1.Width, (int)img1.Height);
        Rectangle bbounds = new Rectangle(posx2, posy2, (int)img2.Width, (int)img2.Height);

        if(Utilities.RectangleIntersects(abounds, bbounds)) {

            uint[] bitsA = s1.GetPixelData(false);

            uint[] bitsB = s2.GetPixelData(false);

            int x1 = Math.Max(abounds.X, bbounds.X);
            int x2 = Math.Min(abounds.X + abounds.Width, bbounds.X + bbounds.Width);

            int y1 = Math.Max(abounds.Y, bbounds.Y);
            int y2 = Math.Min(abounds.Y + abounds.Height, bbounds.Y + bbounds.Height);

            for(int y = y1; y < y2; ++y) {
                for(int x = x1; x < x2; ++x) {
                    if(((bitsA[(x - abounds.X) + (y - abounds.Y) * abounds.Width] & 0xFF000000) >> 24) > 20 &&
                        ((bitsB[(x - bbounds.X) + (y - bbounds.Y) * bbounds.Width] & 0xFF000000) >> 24) > 20)
                        return true;
                }
            }
        }
        return false;
    }
}

图像旋转方法:

internal static Image RotateImagePoint(Image img, Vector pos, Vector orig, double rotation, out Rectangle sz) {
    if(!(new Rectangle(new Point(0), img.Size).Contains(new Point((int)orig.X, (int)orig.Y))))
        Console.WriteLine("Origin point is not present in image bound; unwanted cropping might occur");
    rotation = (double)ra_de((double)rotation);
    sz = GetRotateDimensions((int)pos.X, (int)pos.Y, img.Width, img.Height, rotation, false);
    Bitmap bmp = new Bitmap(sz.Width, sz.Height);
    Graphics g = Graphics.FromImage(bmp);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
    g.RotateTransform((float)rotation);
    g.TranslateTransform(sz.Width / 2, sz.Height / 2, MatrixOrder.Append);
    g.DrawImage(img, (float)-orig.X, (float)-orig.Y);
    g.Dispose();
    return bmp;
}       
internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
    Rectangle sz = new Rectangle();
    if (Crop == true) {
        // absolute trig values goes for all angles
        double dera = de_ra(rotation);
        double sin = Math.Abs(Math.Sin(dera));
        double cos = Math.Abs(Math.Cos(dera));
        // general trig rules:
        // length(adjacent) = cos(theta) * length(hypotenuse)
        // length(opposite) = sin(theta) * length(hypotenuse)
        // applied width = lo(img height) + la(img width)
        sz.Width = (int)(sin * imgheight + cos * imgwidth);
        // applied height = lo(img width) + la(img height)
        sz.Height = (int)(sin * imgwidth + cos * imgheight);
    }
    else {
        // get image diagonal to fit any rotation (w & h =diagonal)
        sz.X = imgx - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
        sz.Y = imgy - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
        sz.Width = (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0)) * 2;
        sz.Height = sz.Width;

    }
    return sz;
}

像素获取方法:

public uint[] GetPixelData(bool useBaseImage) {
    Rectangle rect;
    Image image;
    if (useBaseImage)
        image = Image;
    else
        image = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(Image, Position, Origin, Rotation, out rect), Scale);

    BitmapData data;
    try {
        data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    }
    catch (ArgumentException) {
        data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
    }

    byte[] rawdata = new byte[data.Stride * image.Height];
    Marshal.Copy(data.Scan0, rawdata, 0, data.Stride * image.Height);
    ((Bitmap)image).UnlockBits(data);
    int pixelsize = 4;
    if (data.PixelFormat == PixelFormat.Format24bppRgb)
        pixelsize = 3;
    else if (data.PixelFormat == PixelFormat.Format32bppArgb || data.PixelFormat == PixelFormat.Format32bppRgb)
        pixelsize = 4;

    double intdatasize = Math.Ceiling((double)rawdata.Length / pixelsize);
    uint[] intdata = new uint[(int)intdatasize];

    Buffer.BlockCopy(rawdata, 0, intdata, 0, rawdata.Length);

    return intdata;
} 

像素检索方法有效,旋转方法也可以,因此代码可能出错的唯一地方是碰撞检测代码,但我真的不知道问题可能在哪里。

3 个答案:

答案 0 :(得分:1)

我不认为这里有很多人会费心仔细检查你的代码以找出究竟是什么问题。但我可以提供一些提示,告诉你如何找到问题。

如果碰撞发生很久之前,我建议您的边界框检查无法正常工作。

我会更改代码以在碰撞时转储有关矩形的所有数据。因此,您可以创建一些代码来显示碰撞时的情况。这可能比查看数字更容易。

除此之外,我怀疑每像素碰撞检测更容易实现。当您允许旋转和缩放时,很快就会变得难以正确。我会做基于多边形的碰撞检测。

我制作了自己的2D引擎,但我使用基于多边形的碰撞检测,效果很好。

答案 1 :(得分:1)

我想我找到了你的问题。

internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
    Rectangle sz = new Rectangle(); // <-- Default constructed rect here.
    if (Crop == true) {
            // absolute trig values goes for all angles
            double dera = de_ra(rotation);
            double sin = Math.Abs(Math.Sin(dera));
            double cos = Math.Abs(Math.Cos(dera));
            // general trig rules:
            // length(adjacent) = cos(theta) * length(hypotenuse)
            // length(opposite) = sin(theta) * length(hypotenuse)
            // applied width = lo(img height) + la(img width)
            sz.Width = (int)(sin * imgheight + cos * imgwidth);
            // applied height = lo(img width) + la(img height)
            sz.Height = (int)(sin * imgwidth + cos * imgheight);

            // <-- Never gets the X & Y assigned !!!
    }

由于您从未将imgx和imgy分配给Rectangle的X和Y坐标,因此每次调用GetRotateDimensions都会生成一个具有相同位置的Rectangle。它们可能具有不同的大小,但它们始终处于默认的X,Y位置。这会导致你所看到的真正的早期碰撞,因为每当你试图检测两个精灵上的碰撞时,GetRotateDimensions会将它们的边界置于相同的位置而不管它们实际位于何处。

纠正该问题后,您可能会遇到另一个错误:

Rectangle rect;
Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
// <-- Should resize rect here.
int posx1 = rect.X;
int posy1 = rect.Y;

您可以从RotateImagePoint函数获取边界矩形,但随后调整图像大小。 rect中的X和Y可能与图像的调整大小的边界的X和Y不完全相同。我猜你的意思是图像的中心保持在原位,而所有点在调整大小时向中心收缩或从中心扩展。如果是这种情况,那么您需要调整rect和图像的大小以获得正确的位置。

答案 2 :(得分:0)

我怀疑这是实际问题,但LockBits并不能保证位数据与图像的宽度对齐。

即,可能有一些填充。您需要使用data[x + y * stride]而不是data[x + y * width]来访问该图片。 Stride也是BitmapData的一部分。