bit masks for collision etc. design/use

I am thinking of using a bit mask to make collision design a bit easier. Here is an example, but this could be anything say different types of enemies and only certain ones may collide and hurt the player.

        [Flags]
        public enum BitMask
        {
            None = 0,
            Ground = 1,
            Sand = 2,
            Water = 4,
            // etc. above could be say zombie, dragon etc for damage collision.
        }

Is an enum the right way to go about this? I imagine say my player to have something like the following

class player
{

int collidables = 6; // this is represented as 0110, so can collide with ground, sand but not water, none.

}

The idea then is later in collision checking I can look at all surrounding blocks and they will have their type, say ground so I can do something like:

if (block.Type | player.collidables) return true;

Does anyone have any guidance or resources to use a pattern like this?

Of course I stumble across something 5 minutes after posting. So maybe more like?


// public enum as above, named Collidables

class Player
{

Collidables collidables = Collidables.Ground | Collidables.Sand;

// other stuff then physics calc

if (player.Collidables | block.Type) return true; 

}

From looking at your post I take it you have some sort of tile map and the player is a unit on that map and you want to check if he can move in to the adjoining location before they move - maybe with a mouse hover to give it a green/red border?

Yes, I don’t see a problem with using the flags as you say - so each tile on your map would have a “TerrainType” flag and you would then have the units flag set to say what terrain they can go over.

HoverCraft = Water | Sand
Tank = Ground | Sand
Infantry = Ground | Sand & Water
etc.
As for putting enemies into the same flags list - possibly not as these would be more of a game object and could possibly move - but does depend on your game.

As well as the FlagsHelper on that stackoverflow page, I have an extension that might be of use, below is the code.

Basically you can put a description attribute on your enums and then use this rather than the “tostring” - I find it useful for things like tool tips etc.

enum TerrainType
{
  [Description("Lovely golden sandy beach")]
  Sand,
  [Description("Icy cold water")]
  Water,
}

static public class Extension_Enum
	{
		public static string ToDescription(this Enum @this)
		{
			DescriptionAttribute[] descriptionAttribute = (DescriptionAttribute[])(@this.GetType().GetField(@this.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false);
			return descriptionAttribute.Length > 0 ? descriptionAttribute[0].Description : @this.ToString();
		}

		/// <summary>
		/// Multiple ways to check if an enum has description
		/// </summary>
		/// <param name="this"></param>
		/// <returns></returns>
		/// <example>
		///  if (someColor.HasDescription())
		///  {
		///    if (someColor.HasDescription("indicates stop", StringComparison.OrdinalIgnoreCase))
		///    {
		///      Console.WriteLine("Works");
		///    }
		///  }
		/// </example>
		public static bool HasDescription(this Enum @this)
		{
			return (string.IsNullOrWhiteSpace(@this.ToDescription()) == false);
		}

		/// <summary>
		/// Multiple ways to check if an enum has description
		/// </summary>
		/// <param name="this"></param>
		/// <param name="expectedDescription"></param>
		/// <returns></returns>
		/// <example>
		///  if (someColor.HasDescription())
		///  {
		///    if (someColor.HasDescription("indicates stop", StringComparison.OrdinalIgnoreCase))
		///    {
		///      Console.WriteLine("Works");
		///    }
		///  }
		/// </example>
		public static bool HasDescription(this Enum @this, string expectedDescription)
		{
			return @this.ToDescription().Equals(expectedDescription);
		}

		/// <summary>
		/// Multiple ways to check if an enum has description
		/// </summary>
		/// <param name="this"></param>
		/// <param name="expectedDescription"></param>
		/// <param name="comparisionType"></param>
		/// <returns></returns>
		/// <example>
		///  if (someColor.HasDescription())
		///  {
		///    if (someColor.HasDescription("indicates stop", StringComparison.OrdinalIgnoreCase))
		///    {
		///      Console.WriteLine("Works");
		///    }
		///  }
		/// </example>
		public static bool HasDescription(this Enum @this, string expectedDescription, StringComparison comparisionType)
		{
			return @this.ToDescription().Equals(expectedDescription, comparisionType);
		}
	}

I think this needs to be an & - not done any bit stuff for a while as I use a helper class as in the stackoverflow post - Also note if you are using .net 4.0+ you can use this:

if (player.Collidables & block.Type) return true;

becomes:
return player.Collidables.HasFlag(block.Type);

Thanks, I should have said its mainly thinking about 2D platformer games.

Your little unit example did give me some insight about if it were a 2D Dune 2/CC style game :slightly_smiling:

I might still try and go about it, then for example say a player can become a “ghost” for a while, I can set a new mask that allows them to no longer collide with certain walls. Things like that I had in mind for this.

I might look at a hitbox/hurtbox implementation for the enemies collision after some reading.

Yes & would be correct,

if (player.Collidables | block.Type) will return true so long as either one has at least one bit set lol.

That works in C++, but in C# you can’t treat an integer result as a boolean. You need to be more explicit.

if ((player.Collidables & block.Type) != 0)

if you want to test that the player can collide with one or more of the types set in block.Type.

if ((player.Collidables & block.Type) == block.Type)

if you want to test that the player must collide with all the types set in block.Type.

@lozzajp You can also use bit shifting when writing out the enumeration. The bit shifting here is done at compile time.

[Flags]
public enum BitMask
{
    None   = 0,
    Ground = 1 << 0,  // 2^0 = 1
    Sand = 1 << 1,    // 2^1 = 2
    Water  = 1 << 2,  // 2^2 = 4
    Grass  = 1 << 3,  // 2^3 = 8
    ...
}

Interesting @LithiumToast, what is the difference between that and writing the bit itself?

It’s just a bit clearer :slightly_smiling:

If I were to do this, I’d also use polymorphism to some degree to handle collisions. At least for enemies anyway, so you don’t get a huge if/else if block in your collision code and it’s all a bit more manageable :slight_smile:

Nothing but readability, which arguably is pretty important.

1 Like

This is exactly the way we deal with terrain-constraints like movement or building on specific tiles in that level in ThrobaxTD.

Glad to hear I stumbled upon a design that is actually used :slightly_smiling:

I didn’t want to say that it’s glorious, just that, good or bad, we use it as well :wink:
Actually we’ve found out the hard way that those maps or masks get convoluted and cluttered pretty fast.
1<<15 shifts the columns in the string-map representation quite a few characters to the left :wink:

For the decal-system for example (display a tree there and a stone here) where we have many different objects (which would require many different enum-values) we changed the approach to a XML-array of object-descriptors. Not as easy to read but tidier.

The ‘hero may run here’ or ‘may build here’ or ‘enemy cannot walk here’ is still done in the ‘old’ way like this:

19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19
19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 02 02 01 01 01 01 01 01 01 01 01 01 01 01 01 00 00 19 19
19 19 00 00 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 19 19
19 19 00 00 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 02 02 01 00 00 19 19
19 19 00 00 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 19 19
19 19 00 00 01 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 19 19
19 19 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 19
19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19
19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19

which is pretty easy to understand from a level-design point of perspective…