Skip to main content

Categories

A category represents one or more categories that an entity can belong to. Categories are primarily used with the physics engine to determine which entities can collide with each other.

tip

While you can use categories for general purpose tagging, there is a limit of up to 32 categories and so it is best to save them for physics-related uses.

Declaration

A category is declared using the category keyword followed by an identifier. The identifier must begin with an uppercase letter.

Categories can be declared as public using the pub keyword, which allows them to be accessed from all files in the program. Otherwise they are private and can only be accessed from within the same file. If two private categories have the same name but are in different files, they are not the same category and will not interfere with each other.

pub category Category:Hero
category Category:AffectedByLava

Prefix the category with the keyword tangible to make a category tangible, meaning it will collide with other tangible categories by default. By default, categories are not tangible.

pub tangible category Category:Hero
category Category:AffectedByLava

Assigning categories

The first PolygonCollider decides the Category of the entity.

Spawn hero {
use body=hero
use shape=Circle(radius=1)
Body(pos=@(0,0))
PolygonCollider(category=Category:Hero)
PolygonSprite(color=#00ff00)
}

See the Collider vs Entity categories section for some caveats about how the entity's Category property is set.

Manipulating categories

Categories can be combined using the | operator.

PolygonCollider(category = Category:Hero | Category:AffectedByLava)

The exclusive-OR (XOR) operator ^ can be used to toggle categories, meaning it will remove the category if it is already present, or add the category if it is not already present.

PolygonCollider(category = Category:Hero, collideWith = Category:Tangible ^ Category:Shield)

Category constants

There are a number of Category constants available for you to use:

  • Category:All: All categories
  • Category:Tangible: All tangible categories
  • Category:None: No categories

For example, let's say these are all your categories:

pub tangible category Category:Hero
pub tangible category Category:Projectile
pub category Category:AffectedByLava
  • Category:All is equivalent to Category:Hero | Category:Projectile | Category:AffectedByLava
  • Category:Tangible is equivalent to Category:Hero | Category:Projectile
  • Category:None has no categories
tip

You will most commonly want to start with Category:Tangible and add or remove one category. For example:

  • PolygonCollider(..., collideWith = Category:Tangible ^ Category:Shield) means collide with all tangible things except for shields.
  • PolygonCollider(..., collideWith = Category:Tangible | Category:Grabbable) means collide with tangible things and grabbable things.

Checking for categories

An entity can be in more than one category, and so normally you will want to use the Overlaps to determine if there is any overlap between one set of categories and another.

on BeforeCollide that {
if this.Category.Overlaps(Category:Hero | Category:AffectedByLava) {
Print { "This entity is either a hero or affected by lava!" }
}
}

Alternatively, you can call Contains to determine if one set of categories is a superset of another.

Querying by category

Functions like Query and QueryNearest allow you to limit the search to entities that belong to certain categories by using the filter parameter.

let projectile = QueryNearest(filter = Category:Projectile)

Category limit

You can only have up to 32 categories in your entire game. This limit is necessary because categories are part of the physics engine, and this limit allows for the categories to be stored and processed efficiently as a 32-bit bitmask. For this reason, it is best to save your categories for physics-related purposes only, so you don't run out.

Alternatives to categories

Boolean fields

Instead of creating a category like pub category Category:Guitar, create a field pub field this.IsGuitar. You can have an unlimited number of fields.

pub field this.IsGuitar
Spawn item {
IsGuitar = true
}
on BeforeCollide that {
if that.IsGuitar {
Print { "We hit a guitar!" }
}
}

Collectors

If you want to use categories because you want to be able to Query for all entities of a certain category, consider using a collector instead.

pub collect World.AllGuitars
Spawn item {
give AllGuitars(this)
}
with AllGuitars {
Transmission { "There are " + AllGuitars.Length + " guitars in the world!" }
}

Collectors are generally better than using categories for this purpose:

  • Collectors are more performant than querying because they maintain an up-to-date list of entities that only gets incrementally updated as needed.
  • Collectors signal when they change, so you can use await or on to respond to when they change.
  • Collectors are generally more flexible. For example, you can wait for an entity to complete its initialization animation before you give it to a collector.

Collider vs Entity categories

The category is actually stored separately in two different places:

Normally, you can treat these two as being one and the same, because creating a PolygonCollider will automatically set the entity's Category property to the same value.

tip

You should not normally need to set the Category property manually, as it is set automatically from your first PolygonCollider.

PolygonCollider only sets Category if not already set

Adding a PolygonCollider to an entity will automatically set the entity's Category property, but only if the entity's Category property has not already been set.

That means, adding a second (or subsequent) PolygonCollider to the same entity will not change the entity's Category property. Removing a PolygonCollider will also not change the entity's Category property. This is intentional and is normally the desired behavior. Here are some reasons why this behavior is useful:

  • Imagine you have a proximity mine entity with a collider in the Category:Mine category. You add a second sensor collider to detect when enemies are nearby so that it can start moving towards them. This sensor collider is in the Category:ProximitySensor category. You would not want the entity's Category to change to Category:ProximitySensor because the entity is still a mine.
  • Imagine if your have a hero entity with a collider in the Category:Hero category. The hero temporarily uses a phase shifting ability that removes its collider. The entity is still a hero, even though it has no collider at the moment.

If this behavior does not match your needs, you can manually set the entity's Category property if you need to.