C# 中的每像素碰撞问题

发布于 2024-07-17 22:43:21 字数 5377 浏览 4 评论 0原文

我正在为自己的目的用 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;
} 

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

I am writing a small 2d game engine in C# for my own purposes, and it works fine except for the sprite collision detection. I've decided to make it a per-pixel detection (easiest for me to implement), but it is not working the way it's supposed to. The code detects a collision long before it happens. I've examined every component of the detection, but I can't find the problem.

The collision detection method:

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;
    }
}

The image rotation method:

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;
}

Pixel getting method:

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;
} 

The pixel retrieval method works, and the rotation method works as well, so the only place that the code might be wrong is the collision detection code, but I really have no idea where the problem might be.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

淡莣 2024-07-24 22:43:21

我认为这里的很多人都不会费心去检查你的代码来找出到底出了什么问题。 但我可以提供一些提示,告诉您如何找到问题。

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

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

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

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

I don't think many people here will bother to scrutinize your code to figure out what exactly is wrong. But I can come with some hints to how you can find the problem.

If collision happens long before it is supposed to I suggest your bounding box check isn't working properly.

I would change the code to dump out all the data about rectangles at collision. So you can create some code that will display the situation at collision. That might be easier than looking over the numbers.

Apart from that I doubt that per pixel collision detection easier for you to implement. When you allow for rotation and scaling that quickly becomes difficult to get right. I would do polygon based collision detection instead.

I have made my own 2D engine like you but I used polygon based collision detection and that worked fine.

心碎的声音 2024-07-24 22:43:21

我想我已经找到你的问题了。

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 分配给矩形的 X 和 Y 坐标,因此每次调用 GetRotateDimensions 都会生成一个具有相同位置的矩形。 它们的尺寸可能不同,但它们将始终处于默认的 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 函数获取边界矩形,但随后调整图像大小。 矩形的 X 和 Y 可能与调整后的图像边界的 X 和 Y 不完全相同。 我猜你的意思是图像的中心保持在原位,而所有点在调整大小时向中心收缩或从中心扩展。 如果是这种情况,那么您需要调整矩形和图像的大小以获得正确的位置。

I think I've found your problem.

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 !!!
    }

Since you never assigned imgx and imgy to the X and Y coordinates of the Rectangle, every call of GetRotateDimensions will produce a Rectangle with the same location. They may be of differing sizes, but they will always be in the default X,Y position. This would cause the really early collisions that you are seeing because any time you tried to detect collisions on two sprites, GetRotateDimensions would put their bounds in the same position regardless of where they actually are.

Once you have corrected that problem, you may run into another error:

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;

You get your boundary rect from the RotateImagePoint function, but you then resize the image. The X and Y from the rect are probably not exactly the same as that of the resized boundaries of the image. I'm guessing that you mean for the center of the image to remain in place while all points contract toward or expand from the center in the resize. If this is the case, then you need to resize rect as well as the image in order to get the correct position.

去了角落 2024-07-24 22:43:21

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

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

I doubt this is the actual problem, but LockBits doesn't guarantee that the bits data is aligned to the image's Width.

I.e., there may be some padding. You need to access the image using data[x + y * stride] and not data[x + y * width]. The Stride is also part of the BitmapData.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文