1

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

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

4

3 回答 3

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

于 2009-05-02T06:27:41.687 回答
1

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

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

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

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

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

于 2009-04-27T11:50:41.390 回答
0

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

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

于 2009-04-27T11:54:21.130 回答