Skip to content

Comparing Game Objects

Game objects in bundle code are small wrappers handed to you by the script host. When you ask the game for something (a player, a vehicle, a system), you get back one of these wrappers, and the wrapper tells the game which object you mean.

There are two kinds, and they compare differently. Knowing which one you are holding saves you from a class of bugs that are hard to spot, because the code looks correct and the result is simply wrong.

Objects and structs

Objects are things that exist on their own and have an identity. Entities, widgets, game systems, controllers. Each one is a distinct thing in the world, and asking for it twice gives you two wrappers around the same thing. They inherit from InternalClass inside our C# SDK.

Structs are plain data. A colour, a transform or an item id. They do not have an identity of their own, they only have contents. They inherit from InternalStruct inside our C# SDK.

Comparing objects

Two objects are equal when they refer to the same thing in the game. This works even though each read produces a separate wrapper:

var a = someEntity.GetOtherEntity();
var b = someEntity.GetOtherEntity();

// true: both wrappers point at the same entity
if (a == b)
{
    // ...
}

You do not have to do anything special for this. ==, != and Equals all compare what the wrappers point at, so a value you read a moment ago still matches a value you read now.

Comparing structs

Two structs are equal when their contents are the same:

var margin = widget.GetMargin();
var other = widget.GetMargin();

// true: same contents
if (margin == other)
{
    // ...
}

The comparison looks at the contents, not at where the values came from. If the bytes on the host match exactly, they are considered equal.

One thing to keep in mind

This is a comparison of the data itself. A struct that holds a reference to something else compares on that reference, so two structs can look the same to you and still compare unequal if they point at different things

Tl;dr: If the struct in question is more complex than just a few numbers, ensure that your comparison logic makes sense. Otherwise, compare the actual fields you are interested in

Do not use objects as dictionary keys

Objects work as HashSet or Dictionary keys only within a single read. Two wrappers for the same object that were read separately do not produce the same key, so a set or dictionary will treat them as two different entries:

var seen = new HashSet<Entity>();

// Reading the same entity twice adds two entries.
seen.Add(someEntity.GetTarget());
seen.Add(someEntity.GetTarget());

Compare objects directly instead, or key the collection on the entity's own id, which is stable and belongs to the game rather than to the wrapper:

var seen = new HashSet<ulong>();

seen.Add(someEntity.GetTarget().GetEntityID().id);

The same applies to structs that hold references.

Comparing across types

Comparison is defined between game objects, so comparing an entity against a game system compiles and simply returns false. It is not a type test. To ask what something actually is, use a cast, which returns null when the object is not of that type:

var puppet = someEntity.Cast<PlayerPuppet>();
if (puppet is not null)
{
    // someEntity is a PlayerPuppet
}

Summary

You have == compares
Objects (entities, widgets, systems. Anything derived from InternalClass) The thing they point at
Structs (transforms, margins, item ids. Anything derived from InternalStruct) Their contents

Tip

If you need a stable identity for something across many reads, use the id the game assigns to it, such as ent.EntityID or re.EntityId, rather than the wrapper.