Friday, October 30, 2020

Weeklly Game Jam 173 - Secret Door

There is another weekly game jam that started today with a theme I can totally get behind, Secret Door. So, I'm doing a Rogue-like with secret doors. Hopefully I come up with some original game ideas over the next few days. 

I don't have a map rendering in MonoGame yet but I have a map being created. I just need to convert it to a tile map and all will be good. I should have that up and running tonight, tomorrow for sure. I will post a video tomorrow of my progress. If had something visual I would tonight.

Thursday, October 29, 2020

Second Collision Detection Tutorial Is Live

I have just published the second tutorial in my collision detection mini-series aimed at beginners. This tutorial covers doing collision detection with multiple bounding boxes and with bounding circles. You can find the tutorials on the MonoGame Tutorials page of the site. You can also use this direct link.

Wednesday, October 28, 2020

Beginner Collision Detect Tutorial

I have added a general tutorial on collision detection in MonoGame. It starts with the basics of testing for bounding box collision detection. It then moves to pixel based collision detection followed by collision detection of rotated objects. I will be doing another tutorial that covers alternate collision detection like multiple bounding boxes, bounding circles and a full explanation of per pixel collision detection on rotated animated sprites. You can find the link to the tutorial on the MonoGame Tutorials page of the site or you can use this direct link.

Tuesday, October 27, 2020

Per Pixel Collision Detection on Rotated and Scaled Sprites

This post about detecting collision between two rotated and/or scaled sprites where the sprites are part of a sprite sheet. I found lots of examples of detecting entire sprites but not so much for sprite sheets. This is an extension of my previous post about detecting if two sprites in sprite sheets collide.

For this to work you need to create a transformation matrix for each sprite. It is calculated by using the following formula: Matrix.CreateTranslation(new Vector3(-Origin, 0)) * Matrix.CreateScale(Scale) * Matrix.CreateRotationZ(Rotation) * Matrix.CreateTranslation(new Vector3(Position, 0)). Origin is calculated by (SourceRectangle.Width / 2, SourceRectangle.Height / 2).

The collision method takes six parameters: the source rectangle of sprite A, the transformation matrix of sprite A, the sprite sheet of sprite A, the source rectangle of sprite B, the transformation matrix of sprite B and the sprite sheet of sprite B. If a source rectangle is null the entire sprite sheet will be used.

It calls a second method that does the actual collision detection. It takes as parameters the source rectangle of sprite A, the transformation matrix of sprite A, the color data of sprite A, the source rectangle of sprite B, the transformation matrix of sprite B and the color data of sprite B.



/// <summary>
/// Checks if two sprites in a sprite sheet collide using per pixel collision detection
/// </summary>
/// <param name="sourceA">Nullable source rectangle of sprite A in the sprite sheet</param>
/// <param name="transformA">Transformation matrix for sprite A</param>
/// <param name="textureA">Texture for sprite sheet A</param>
/// <param name="transformB">Transformation matirx for sprite B</param>
/// <param name="sourceB">Nullable source rectangle of sprite B in the sprite sheet</param>
/// <param name="textureB">Texture for sprite sheet B</param>
/// <returns></returns>
public static bool SpriteSheetCollision(
    Rectangle? sourceA,
    Matrix transformA,
    Texture2D textureA,
    Rectangle? sourceB,
    Matrix transformB,
    Texture2D textureB)
{
    // If the sourceA is null use entire texture
    if (sourceA == null)
    {
        sourceA = new Rectangle(0, 0, textureA.Width, textureA.Height);
    }

    // Grab the texture data for checking if the pixels collide in the source rectangle
    Color[] textureDataA = new Color[sourceA.Value.Width * sourceA.Value.Height];
    textureA.GetData(0, sourceA, textureDataA, 0, textureDataA.Length);

    // If the sourceB is null use the entire texture as the source rectangle
    if (sourceB == null)
    {
        sourceB = new Rectangle(0, 0, textureB.Width, textureB.Height);
    }

    // Grab the texture data for checking if the pixels collide in the source rectangle
    Color[] textureDataB = new Color[sourceB.Value.Width * sourceB.Value.Height];
    textureB.GetData(0, sourceB, textureDataB, 0, textureDataB.Length);

    // Call the per pixel collision detection code
    return ColorDataCollides(sourceA.Value, transformA, textureDataA, sourceB.Value, transformB, textureDataB);
}

/// <summary>
/// Checks if two sprites collide using pixel perfect collision detection
/// where the sprites are rotated and/or scaled.
/// </summary>
/// <param name="sourceA">Source rectangle of sprite A in the sprite sheet</param>
/// <param name="transformA">The transformation matrix of sprite A</param>
/// <param name="textureDataA">The color data of sprite A</param>
/// <param name="sourceB">Source rectangle of sprite B in the sprite sheet</param>
/// <param name="transformB">The transformation matrix of sprite B</param>
/// <param name="textureDataB">The color data of sprite B</param>
/// <returns></returns>
private static bool ColorDataCollides(Rectangle sourceA, Matrix transformA, Color[] textureDataA, Rectangle sourceB, Matrix transformB, Color[] textureDataB)
{
    // Transformation of sprite A to sprite B
    Matrix mat1to2 = transformA * Matrix.Invert(transformB);

    // Loop over the source rectangle of sprite A
    for (int x1 = 0; x1 < sourceA.Width; x1++)
    {
        for (int y1 = 0; y1 < sourceA.Height; y1++)
        {
        
            // Calculate the position of the pixel in sprite A in sprite B
            Vector2 pos1 = new Vector2(x1, y1);
            Vector2 pos2 = Vector2.Transform(pos1, mat1to2);

            // Round to the nearest pixel
            int x2 = (int)Math.Round(pos2.X);
            int y2 = (int)Math.Round(pos2.Y);

            // Check to see if the pixel in sprite A is in the bounds of sprite B
            if ((x2 >= 0) && (x2 < sourceB.Width))
            {
                if ((y2 >= 0) && (y2 < sourceB.Height))
                {
                    // If the alpha channel of sprite A and sprite B is greater
                    // than zero we have a collision
                    if (textureDataA[x1 + y1 * sourceA.Width].A > 0)
                    {
                        if (textureDataB[x2 + y2 * sourceB.Width].A > 0)
                        {
                            return true;
                 }
                    }
                }
            }
        }
    }

    // No collision occurred
    return false;
}

The first method is similar to the previous method I posted the other day. It takes transformation matrices instead of destination rectangles. It uses the GetData method overload that takes a source rectangle. For starting position you need to use 0. If your texture has multiple levels you will want to repeat the testing for the different levels.

The second method does the actual collision detection. It calculates a matrix that is used to map the pixels in sprite A to the pixels in sprite B. That is done by multiplying the transformation matrix of sprite A by the inverse transformation of sprite B. I suggest reading up on matrix transformation if you're unclear about what is going on.

Next we loop over all of the pixels in sprite A and test if the collide with a pixel in sprite B. Inside the loops I create a vector for the current pixel in sprite A, or 1 in the code. I transform that vector next using the matrix we calculated earlier. I round the points to integers.

The next step is to check if the transformed pixel is inside the bounds of sprite B. If it is I compare the alpha channel of the two pixels. If their alpha channel are both greater than zero there is a collision and I return true. If a collision is not found I return false.

If you have questions leave a comment and I will answer them. I hope that you find the methods useful.

Monday, October 26, 2020

Space Raptor Tutorial 3 Is Live

 I have just posted tutorial. 3 in the Space Raptor tutorial series to the site. This tutorial adds in some backgrounds and frames per second counter. It also adds in a shield for the player that drains over time and regenerates slowly. I also add in a power up that gives the player three way shot for a limited time. You can find the tutorial on the MonoGame tutorial page of the site or you can download it from this direct link.

Per Pixel Sprite Sheet Collision Detection

 I've seen this question asked a few times but no public answers. How do you detect if two sprites in a sprite sheet collide using per pixel collision detection? The answer for entire sprites is available easily. I made two static methods that can be added to a project to check if two parts of a sprite sheet collide. Soon I will be posting code that handles rotated and scaled sprites as well.



public static bool SpriteSheetCollision(
    Rectangle destinationA, 
    Rectangle? sourceA, 
    Texture2D textureA,
    Rectangle destinationB,
    Rectangle? sourceB,
    Texture2D textureB)
{
    // If the sourceA is null use entire texture
    if (sourceA == null)
    {
        sourceA = new Rectangle(0, 0, textureA.Width, textureA.Height);
    }

    // Grab the texture data for checking if the pixels collide in the source rectangle
    Color[] textureDataA = new Color[sourceA.Value.Width * sourceA.Value.Height];
    textureA.GetData(0, sourceA, textureDataA, 0, textureDataA.Length);

    // If the sourceB is null use the entire texture as the source rectangle
    if (sourceB == null)
    {
        sourceB = new Rectangle(0, 0, textureB.Width, textureB.Height);
    }

    // Grab the texture data for checking if the pixels collide in the source rectangle
    Color[] textureDataB = new Color[sourceB.Value.Width * sourceB.Value.Height];
    textureB.GetData(0, sourceB, textureDataB, 0, textureDataB.Length);

    // Call the per pixel collision detection code
    return ColorDataCollides(destinationA, textureDataA, destinationB, textureDataB);
}

public static bool ColorDataCollides(Rectangle a, Color[] textureDataA, Rectangle b, Color[] textureDataB)
{
    // Find the bounds of the rectangle intersection
    int top = Math.Max(a.Top, b.Top);
    int bottom = Math.Min(a.Bottom, b.Bottom);
    int left = Math.Max(a.Left, b.Left);
    int right = Math.Min(a.Right, b.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 = textureDataA[(x - a.Left) +
                                    (y - a.Top) * a.Width];
            Color colorB = textureDataB[(x - b.Left) +
                                    (y - b.Top) * b.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;
}

Sunday, October 25, 2020

Frames Per Second Counter

So, I'm going to try something new. I'm not happy with the way it is rendering 100% but it should copy/paste fine. Most probably have one by now but I thought I'd share my frames per second component outside of a tutorial on its own. It renders the FPS into the title bar of the window and the output log in Visual Studio. I recommend using the base constructor and adding it to the list of game components in the constructor using Components.Add(new FramesPerSecond(this)).

UPDATE: I fixed the issues I had with code.


using System;
using Microsoft.Xna.Framework;

namespace Psilibrary
{
    public sealed class FramesPerSecond : DrawableGameComponent
    {
        private float _fps;
        private float _updateInterval = 1.0f;
        private float _timeSinceLastUpdate = 0.0f;
        private float _frameCount = 0;

        public FramesPerSecond(Game game)
            : this(game, false, false, game.TargetElapsedTime)
        {
        }

        public FramesPerSecond(
            Game game, 
            bool synchWithVerticalRetrace, 
            bool isFixedTimeStep, 
            TimeSpan targetElapsedTime)
            : base(game)
        {
            GraphicsDeviceManager graphics = 
                (GraphicsDeviceManager)Game.Services.GetService(
                    typeof(IGraphicsDeviceManager));

            graphics.SynchronizeWithVerticalRetrace = synchWithVerticalRetrace;
            Game.IsFixedTimeStep = isFixedTimeStep;
            Game.TargetElapsedTime = targetElapsedTime;
        }

        public sealed override void Draw(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
            _frameCount++;
            _timeSinceLastUpdate += elapsed;

            if (_timeSinceLastUpdate > _updateInterval)
            {
                _fps = _frameCount / _timeSinceLastUpdate;
                System.Diagnostics.Debug.WriteLine("FPS: " + _fps.ToString());
                Game.Window.Title = "FPS: " + ((int)_fps).ToString();
                _frameCount = 0;
                _timeSinceLastUpdate -= _updateInterval;
            }

            base.Draw(gameTime);
        }
    }
}