Search code examples
xna2dcollision-detectionpixelcollision

Xna Pixel Collision and multi-layers. Early Collision, wrong detection?


we are currently working on a Xna game for college. We are testing something first before we put in our own textures etc.

So we watch a lot tutorials and went through a lot of code. Right now we have this little Program running: http://www.konter.at/Konter_Game.rar

So this the GameScreen, where we load the player and the Background and everything we want to use.

namespace Casual_Game
{
    public class GameplayScreen : GameScreen
    {
        Player player;
        Camera camera;
        SpriteFont font;
        private List<Layer> layers;
        private SpriteBatch _spriteBatch;

    Rectangle recPlayer, recGround;
    bool colis = false;
    Collision collision;

    public override void LoadContent(ContentManager content, InputManager input)
    {
        camera = new Camera(Game1.reference.GraphicsDevice.Viewport) { Limits = new Rectangle(0, 0, 4800, 720) };

        collision = new Collision();
        _spriteBatch = new SpriteBatch(Game1.reference.GraphicsDevice);
        base.LoadContent(content, input);
        player = new Player();
        player.LoadContent(content, input);
        font = content.Load<SpriteFont>("Font1");

        layers = new List<Layer>
        {
            new Layer(camera) { Parallax = new Vector2(0.0f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.1f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.2f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.3f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.4f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.5f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.6f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(0.8f, 1.0f) },
            new Layer(camera) { Parallax = new Vector2(1.0f, 1.0f) }
        };

        // Add one sprite to each layer });
        layers[1].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer2") });
        layers[2].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer3") });
        layers[3].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer4") });
        layers[4].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer5") });
        layers[7].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer8") });
        layers[8].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer9") });

        // Add a few duplicates in different positions
        layers[7].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer8"), position = new Vector2(900, 0) });
        layers[7].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer8"), position = new Vector2(1400, 0) });
        layers[7].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer8"), position = new Vector2(2700, 0) });
        layers[8].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer9"), position = new Vector2(1600, 0) });
        layers[8].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer9"), position = new Vector2(3200, 0) });
        layers[8].Sprites.Add(new Sprite { texture = content.Load<Texture2D>("Textures/Layer9"), position = new Vector2(4800, 0) });

    }
    public override void UnloadContent()
    {
        base.UnloadContent();
    }
    public override void Update(GameTime gameTime)
    {
        player.Update(gameTime);


        recGround = new Rectangle((int)layers[8].Sprites[0].Position.X, (int)layers[8].Sprites[0].Position.Y, layers[8].Sprites[0].Texture.Width, layers[8].Sprites[0].Texture.Height);
        recPlayer = new Rectangle((int)player.Position.X, (int)player.Position.Y, player.Image.Width, player.Image.Height);
        float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
        KeyboardState keyboardState = Keyboard.GetState();

        if (keyboardState.IsKeyDown(Keys.Right))
            camera.Move(new Vector2(200.0f * (float)gameTime.ElapsedGameTime.TotalSeconds, 0.0f), true);

        if (keyboardState.IsKeyDown(Keys.Left))
            camera.Move(new Vector2(-200.0f * (float)gameTime.ElapsedGameTime.TotalSeconds, 0.0f), true);

        if (keyboardState.IsKeyDown(Keys.Down))
            camera.Move(new Vector2(0.0f, 400.0f * elapsedTime), true);

        if (keyboardState.IsKeyDown(Keys.Up))
            camera.Move(new Vector2(0.0f, -400.0f * elapsedTime), true);
        if (recPlayer.Intersects(recGround))
        {
            if (collision.PixelCollision(player.Image, layers[8].Sprites[0].Texture, recPlayer, recGround))
            {
                player.Collision = true;
                colis = true;
            }
            else
            {
                player.Collision = false;
                colis = false;
            }
        }
        else
        {
            player.Collision = false;
            colis = false;
        }

        camera.LookAt(player.Position);

        base.Update(gameTime);
    }
    public override void Draw(SpriteBatch spriteBatch)
    {
       Vector2 parallax = new Vector2(1.0f);
        _spriteBatch.Begin(SpriteSortMode.Deferred,null , null, null, null, null, camera.GetViewMatrix(parallax));
        foreach (Layer layer in layers)
            layer.Draw(_spriteBatch);
        player.Draw(_spriteBatch);
        spriteBatch.DrawString(font, "Player X : " + player.Position.X.ToString(),new Vector2(10, 10), Color.Black);
        spriteBatch.DrawString(font, "CameraX :" + camera._position.X.ToString(), new Vector2(10, 25), Color.Black);
        spriteBatch.DrawString(font, "CameraY :" + camera._position.Y.ToString(), new Vector2(10, 40), Color.Black);
        spriteBatch.DrawString(font, "Col : " + colis.ToString(), new Vector2(10, 55), Color.Black);
        _spriteBatch.End();

    }
}

You see it is a Parallax Background with different Layers. On Layer[8] is the "Ground" where our player would run on later.

The Problem now is that the Collision Detection is not working correctly. It looks like the detection is loading to slow or something.

We have in mind, that the player will hit the ground with the detection. So he has no value where he stops on the ground. So it has to be exate.

This is the Collision Class:

        public bool PixelCollision(Texture2D sprite1, Texture2D sprite2, Rectangle player, Rectangle enemy)
    {
        Color[] colorData1 = new Color[sprite1.Width * sprite1.Height];
        Color[] colorData2 = new Color[sprite2.Width * sprite2.Height];
        sprite1.GetData<Color>(colorData1);
        sprite2.GetData<Color>(colorData2);

        int top, bottom, left, right;

        top = Math.Max(player.Top, enemy.Top);
        bottom = Math.Min(player.Bottom, enemy.Bottom);
        left = Math.Max(player.Left, enemy.Left);
        right = Math.Min(player.Right, enemy.Right);

        for (int y = top; y < bottom; y++)
        {
            for (int x = left; x < right; x++)
            {
                Color A = colorData1[(y - player.Top) * (player.Width) + (x - player.Left)];
                Color B = colorData2[(y - enemy.Top) * (enemy.Width) + (x - enemy.Left)];

                if (A.A != 0 && B.A != 0)
                    return true;
            }
        }
        return false;
    }
}

We would be very happy if someone could help us figure that out. We are not that much of programmers. So we thought asking doesnt cost anything. :)

K.

Edit: Player.Update and Draw:

public override void Update(GameTime gameTime)
    {
        moveAnimation.IsActive = true;
        inputManager.Update();


        if (inputManager.KeyDown(Keys.Right, Keys.A))
        {
            moveAnimation.currentFrame = new Vector2(moveAnimation.CurrentFrame.X, 2);
            velocity.X = moveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (inputManager.KeyDown(Keys.Left, Keys.A))
        {
            moveAnimation.currentFrame = new Vector2(moveAnimation.CurrentFrame.X, 1);
            velocity.X =  -moveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else
        {
            velocity.X = 0;
            moveAnimation.IsActive = false;
        }

        if (inputManager.KeyDown(Keys.Up, Keys.W) && jump)
        {
            velocity.Y = -jumpSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; //Position += theDirection * theSpeed * (float)theGameTime.ElapsedGameTime.TotalSeconds;
            jump = false;
        }

        if (!jump)
            velocity.Y += gravity * (float)gameTime.ElapsedGameTime.TotalSeconds;
        else
            velocity.Y = 0;


        moveAnimation.Position = position += velocity;

        jump = moveAnimation.Position.Y >= 480;

        if (jump)
            position.Y = 480;


        base.Update(gameTime);
        moveAnimation.Update(gameTime);
    }
    public override void Draw(SpriteBatch spriteBatch)
    {
        moveAnimation.Draw(spriteBatch);
    }

Layer.Draw:

public class Layer
{
    SpriteBatch spr1teBatch;
    public Layer(Camera camera)
    {
        _camera = camera;
        Parallax = Vector2.One;
        Sprites = new List<Sprite>();
        spr1teBatch = new SpriteBatch(Game1.reference.GraphicsDevice );
    }

    public Vector2 Parallax { get; set; }

    public List<Sprite> Sprites { get; private set; }



    public void Draw(SpriteBatch spriteBatch)
    {
        spr1teBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, _camera.GetViewMatrix(Parallax));

        foreach(Sprite sprite in Sprites)
            sprite.Draw(spr1teBatch);

        spr1teBatch.End();
    }

    public Vector2 WorldToScreen(Vector2 worldPosition)
    {
        return Vector2.Transform(worldPosition, _camera.GetViewMatrix(Parallax));
    }

    public Vector2 ScreenToWorld(Vector2 screenPosition)
    {
        return Vector2.Transform(screenPosition, Matrix.Invert(_camera.GetViewMatrix(Parallax)));
    }

    private readonly Camera _camera;
}

Solution

  • Let's visualize what you are drawing:

    Visualization

    You draw the player at recPlayer.x, recPlayer.y on the screen. And you draw the layer at recGround.x, recGround.y. But this position is transformed by the view matrix.

    What you now do is to iterate all intersecting pixels of the two rectangles. The resulting x and y values are in screen space. I have highlighted an example position for x and y. You now need to retrieve the colors of bot textures at this very position.

    The color of the player can be retrieved pretty easily, because it already is in screen space. The positions in the texture is xTex = x - recPlayer.x and yTex = y - recPlayer.y. The index in the color array can be computed by index = yTex * width + xTex. You have already done this to access the color.

    Retrieving the color of the ground is a bit trickier, because it is not in screen space like the player. It is probably translated and a bit scaled. You have not posted the code, how the layer's sprites are actually drawn. I assume, it is drawn at the position recGround.x, recGround.y (and then transformed). What we need to do is calculating the position of x/y in the ground's world space. So we use the method you already have:

    var screenPosition = new Vector2(x, y);
    var groundPosition = ScreenToWorldPos(screenPos);
    

    This will basically invert the layer's transform. If it is translated by 20 px to the right, the new groundPosition will be 20 px left of the original position (because that's the position of the pixel that is actually at this position).

    You can access the color just like you did for the player with xTex = x - recGround.x and so on.

    Another problem I just realized is the computation of your intersecting rectangle. Of course, you have to use the transformed rectangle to get the real position.


    Old post:

    The problem is that the player and the layers are not drawn in the same coordinate system.

    The player is drawn in the "default" system. The layers are drawn transformed with the according view matrix and are therefore translated, scaled and whatever.

    In PixelCollision you iterate over all screen pixels that might intersect. These pixels are in the "default" system like the player. Due to this you can calculate the texture positions with:

    texX = x - player.x
    texY = y - player.y
    

    However, this does not work for the layers. You have to transform x and y from screen space into the layer's world space.

    layerPos = ScreenToWorld(new Vector2(x, y));
    

    And then access the colors at layerPos.x - enemy.x and layerPos.y - enemy.y. This works, if you draw the layers at enemy.x, enemy.y in their systems. If you have an additional transform or draw them somewhere else, you need to consider this.