Unityengine



We already discussedGame Objects andComponents as twoof the fundamental building blocks of the Unity Engine. Today, we’ll discusstheir programmatic representation.

This is Unity for Software Engineers,a series for folks familiar with software development best practices seeking anaccelerated introduction to Unity as an engine and editor. More is coming overthe next few weeks, so consider subscribing forupdates.

Unity

The Unity Engine runtime is primarily written in C++, and much of the Engine’sprimitives (such as the game objects and their components) live in C++ land.You’ll also know that the Unity Engine API is in C#. The API gives you accessto all of Unity’s native objects in a way that—save for a few pitfallswe’ll discuss today—feels like intuitive, idiomatic C#.

Unity is the ultimate game development platform. Use Unity to build high-quality 3D and 2D games, deploy them across mobile, desktop, VR/AR, consoles or the Web, and connect with loyal and enthusiastic players and customers. Use the GameSense™ Client for Unity Engine from SteelSeries Corporation America on your next project. Find this integration tool & more on the Unity Asset Store. Sprint into Spring Sale is on: get 50% off top assets and score extra savings with coupon code SPRING2021.

UnityEngine.Object

At the top of the Unity Object hierarchy sits UnityEngine.Object. For themost part provides a name string, an int GetInstanceID() method, and a bunchof equality comparers.

The class also provides a static void Destroy(Object obj) method (and someoverloads) that destroys a UnityEngine.Object and any of its subclasses. Whenan Object is destroyed, the native part of the object is freed from memory,and the smaller managed part will be garbage collected at some point afterthere are no more references to it.

Because your valid reference to a UnityEngine.Object can point to a destroyednative object, UnityEngine.Object overrides C#‘s operator and operator!=to make a destroyed Object appear null. Simply accessing methods on adestroyed object will return NullReferenceException, albeit with a friendliererror message that tells you which object you were trying to access.

GameObject

A GameObject derives from Object and represents anything in your scene.

Let’s start at a high-level: A GameObject inherits a name and instance ID fromits parent. Otherwise, conceptually, a GameObject

  • has a list of Components on it,
  • has a tag string for organizational purposes, and
  • belongs to a layer.

A GameObject’s state

  • is the product of all of its Components’ state, and
  • whether an object is active or not.

Let’s dig a bit deeper. When starting, most of the interesting stuff in aGameObject is in its Components. A GameObject has at least one Component:its Transform. ATransform describes the position and rotation of the GameObject. A Transformincludes helper properties that show an object’s absolute world position androtation, as well as the position and rotation relative to its parent. In theEditor, the Transform position and rotation are set from the parent relativevariants.

Since every GameObject has a Transform (and also, given that a Transform isfrequently needed/accessed), the GameObject directly exposes aTransform transform public property.

You can access individual components from T GetComponent<T>(), or lists ofcomponents from T[] GetComponents<T>(), etc. These methods search through allcomponents on a GameObject and return ones with a compatible type (or null, ifnone exist in the singular case). Since these methods search through componentsand check type compatibility, it is often recommended to cache this lookup.

If you are building/extending a GameObject by hand, you can always useT AddComponent<T>(). In most cases, however, you’re better off using theEditor.

Individual Objects (a Component or ScriptableObject) might refer to otherGameObjects in a few ways:

  • By reference. By exposing a GameObjectserialized field that youthen set from the inspector.

    We have discussed serialization extensively throughout the series:as a fundamental conceptand in our tour of the Editor, whendescribing the Inspector,and the practice of usingthe Inspector as an injection framework.

  • Using tags. Every Game Object can have a tag string. You can findobjects in the scene using that tag through the static functionsGameObject.FindGameObjectsWithTag and GameObject.FindGameObjectWithTag.A GameObject also exposes a public bool CompareTag(string tag) method.

    This is a quick-and-dirty way to get the job done, but is still a popularway. A common use of this in the wild is to have a 'Player' tag to findthe Player. Ideally, these methods should not be called every frame, so ifyou have to use them, consider caching the result.

  • Using layers. A layer is an int between 0 and 31. Every Game Object isin exactly one layer.

    While you can’t directly look up all objects in a layer, if you already havea reference to a GameObject (e.g., in a collision event), you can check aGameObject against aLayerMask. ALayerMask is typically used in functions like Physics.Raycast(). Thisallows you to find objects with colliders intersecting with a given ray.Passing a LayerMask to Physics.Raycast() will only return objects withinthe specified set of layers.

    Inside the Unity Engine, Cameras make heavy use of layers. E.g., you canhave one camera that renders “everything but UI”, and overlay another camerafor an in-game HUD, etc.

  • Using indirect references. There are many reasons why the methods abovemight be insufficient: you might not want to use tags to avoid depending oncopy-pasted strings, and layers might not fit your use case. If referencinga fellow object in-scene is not an option (e.g., you’re dealing with adynamic set of objects or don’t have access to the current scene objects inthe context you need this reference, etc.), then you might want to lookfurther.

    For this, an increasingly popular concept is runtime sets ScriptableObjects. You can read more about this in Unity’s how-to article onarchitecting your game with ScriptableObjects,based on the talk by RyanHipple. If you have an hour to spare, you might want to watch the wholething.

A GameObject also exposes a BroadcastMessage and SendMessage functions thatpropagate messages (described in the Component section) toall components in or under it.

Component

Unity defines the behaviors of game objects through the composition of theseComponent classes. This is a core tenet of what game engines refer to as anEntity Component System (ECS). TheRipple blog has an applied overview ofECS, and Robert Nystrom’s Game Programming Patternsdescribes entity component systems in detail.Confusingly, Unity refers to their next-generation high-performance gameprogramming paradigm asECS, which isalso ECS-based but takes things to the next level with data-oriented design.

From the outside looking in, both Unity’s MonoBehaviour-based paradigm andUnity ECS are entity component systems, though how you use them differsubstantially.

Every behavior on a GameObject is driven through its Components.User-implemented Components will usually extend the MonoBehaviour subclass(more on that later).

A Component inherits a name and instance ID from its parent. Otherwise,conceptually, a Component

  • always belongs to a single GameObject, exposed as a publicGameObject gameObject property, and
  • can receive messages, driving much of its behavior.

The state of a component on an active GameObject lies entirely in itsimplementation.

In addition to its GameObject, a component exposes shorthand properties andmethods such as Transform transform, T GetComponent<T>(), etc. These aresimply convenience shorthands for accessing those same methods on thecorresponding gameObject.

The most important functionality of a Component is driven through UnityMessages (also sometimes called Unity Event Functions when referring tobuilt-in messages). These are effectively callbacks functions triggered by theEngine in certain situations. Every Component will receive a Awake(),Start(), Update() and other messages, for example. The Unity Docs on theOrder of Execution ofthese messages is a convenient resource.

To have your component receive a particular message, simply add aprivate void method with the appropriate message name. The runtime will usereflection to call these messages, when applicable. This is why you don’t see anoverride directive on these messages. Messages like Update, LateUpdate,and FixedUpdate are inspected once per type, so don’t worry about reflectionbeing used in every frame. See more details in the”10000 Update() calls”Unity blog post for more information.

A Behaviour is a type of component that can be enabled or disabled. Whena Behaviour is disabled, Start, Update, FixedUpdate, LateUpdate,OnEnable, and OnDisable messages are not called.

A MonoBehaviour is a Behaviour that also enables usingCoroutines.

Unityengineinternal

A Note on Inactive Objects and Disabled Components

A GameObject in a loaded scene will exist in memory until the object isDestroyed explicitly or the scene is unloaded. A GameObject can be set toinactive, which will cause it to stop receiving Update (and related) events.

When an object is created, the messages called on a component depend onif: (1) the GameObject is active, and (2) the component is enabled:

GameObject is activeGameObject is inactive
Component is EnabledAwake, OnEnable, StartComponent implicitly disabled
Component is DisabledAwakeAwake

When an object is set to active or a Behaviour is set to enabled:

  • OnEnable will be called.
  • If Start has never been called on this Behaviour, it will be calledexactly once.

Takeaways

Some takeaways of all this:

  1. A Unity object might appear to become null when destroyed. nullchecking does more than you think.
  2. As a result, null-coalescing operators (??, ??=) and null-conditionaloperators (?., ?[]) don’t work as expected.
  3. Yes, your Unity Messages can be private!
  4. Don’t create abstract classes that unnecessarily declare Update or othermessages to make overriding easier; that’ll result in the engine alwayscalling these events.
  5. Disabling an Object or Component is a great way to limit its game logic orsave on CPU-bound effort, but these objects still have a memory overhead.
-->

Every Mixed Reality app gets a HolographicSpace before it starts receiving camera data and rendering frames. In Unity, the engine takes care of those steps for you, handling Holographic objects and internally updating as part of its render loop.

However, in advanced scenarios you may need to get access to the underlying native objects, such as the HolographicCamera and current HolographicFrame.

WindowsMixedRealityUtilities

Namespace:Microsoft.MixedReality.Toolkit.WindowsMixedReality
Type:WindowsMixedRealityUtilities

MRTK provides already-marshalled types across both legacy WSA and XR SDK through the WindowsMixedRealityUtilities class.

WindowsMREnvironment

Namespace:UnityEngine.XR.WindowsMR
Type:WindowsMREnvironment

The static WindowsMREnvironment class provides access to several native pointers.

XRDevice

Namespace:UnityEngine.XR
Type:XRDevice

The XRDevice type allows you to get access to underlying native objects using the GetNativePtr method. What GetNativePtr returns varies between different platforms. On the Universal Windows Platform when targeting Windows Mixed Reality, XRDevice.GetNativePtr returns a pointer (IntPtr) to the following structure:

Unityengine.scenemanagement

You can convert it to HolographicFrameNativeData using Marshal.PtrToStructure method:

IHolographicCameraPtr is an array of IntPtr marshaled as UnmanagedType.ByValArray with a length equal to MaxNumberOfCameras

Unmarshaling native pointers

After obtaining the IntPtr from one of the methods above (not needed for MRTK), use the following code snippets to marshal them to managed objects.

If you are using Microsoft.Windows.MixedReality.DotNetWinRT, you can construct a managed object from a native pointer using the FromNativePtr() method:

Otherwise, use Marshal.GetObjectForIUnknown() and cast to the type you want:

Converting between coordinate systems

Unity Engine Apk

Unity uses a left-handed coordinate system, while the Windows Perception APIs use right-handed coordinate systems. To convert between these two conventions, you can use the following helpers:

Using HolographicFrame native data

Unityengine Random

Note

Changing the state of the native objects received via HolographicFrameNativeData may cause unpredictable behavior and rendering artifacts, especially if Unity also reasons about that same state. For example, you should not call HolographicFrame.UpdateCurrentPrediction, or else the pose prediction that Unity renders with that frame will be out of sync with the pose that Windows is expecting, which will reduce hologram stability.

Games Made With Unity Engine

If you need access to native interfaces for rendering or debugging purposes, use data from HolographicFrameNativeData in your native plugins or C# code.

Here's an example of how you can use HolographicFrameNativeData to get the current frame's prediction for photon time using the XR SDK extensions.

See Also