Skip to content

Instantly share code, notes, and snippets.

@mishazawa
Last active August 31, 2022 20:18
Show Gist options
  • Save mishazawa/3b2fcad8884dc3aa0855ce8abb2f8d97 to your computer and use it in GitHub Desktop.
Save mishazawa/3b2fcad8884dc3aa0855ce8abb2f8d97 to your computer and use it in GitHub Desktop.
Unity dev prepare

Plan

Design patterns 1

Design pattern is a general repeatable solution to a commonly occurring problem in software design. A design pattern isn't a finished design that can be transformed directly into code. It is a description or template for how to solve a problem that can be used in many different situations.

Creational design patterns

These design patterns are all about class instantiation. This pattern can be further divided into class-creation patterns and object-creational patterns. While class-creation patterns use inheritance effectively in the instantiation process, object-creation patterns use delegation effectively to get the job done.

Abstract Factory

The Abstract Factory provides you with an interface for creating objects from each class of the product family. As long as your code creates objects via this interface, you don’t have to worry about creating the wrong variant of a product which doesn’t match the products already created by your app.

Use the Abstract Factory when your code needs to work with various families of related products, but you don’t want it to depend on the concrete classes of those products — they might be unknown beforehand or you simply want to allow for future extensibility.

  • Consider implementing the Abstract Factory when you have a class with a set of Factory Methods that blur its primary responsibility.

  • In a well-designed program each class is responsible only for one thing. When a class deals with multiple product types, it may be worth extracting its factory methods into a stand-alone factory class or a full-blown Abstract Factory implementation.

Builder

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

The base builder interface defines all possible construction steps, and concrete builders implement these steps to construct particular representations of the product. Meanwhile, the director class guides the order of construction.

The Builder pattern lets you construct products step-by-step. You could defer execution of some steps without breaking the final product. You can even call steps recursively, which comes in handy when you need to build an object tree.

A builder doesn’t expose the unfinished product while running construction steps. This prevents the client code from fetching an incomplete result.

  • Use the Builder pattern to get rid of a “telescoping constructor”.

  • Use the Builder to construct Composite trees or other complex objects.

  • Use the Builder pattern when you want your code to be able to create different representations of some product (for example, stone and wooden houses).

Factory Method

The Factory Method separates product construction code from the code that actually uses the product. Therefore it’s easier to extend the product construction code independently from the rest of the code.

  • Use the Factory Method when you don’t know beforehand the exact types and dependencies of the objects your code should work with.

  • Use the Factory Method when you want to provide users of your library or framework with a way to extend its internal components.

  • Use the Factory Method when you want to save system resources by reusing existing objects instead of rebuilding them each time. (Object Pool)

Prototype

Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.

  • Use the Prototype pattern when your code shouldn’t depend on the concrete classes of objects that you need to copy.

  • Use the pattern when you want to reduce the number of subclasses that only differ in the way they initialize their respective objects.

  • The Prototype pattern is available in C# out of the box with a ICloneable interface.

  • The prototype can be easily recognized by a clone or copy methods, etc.

Singleton

Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.

  • Use the Singleton pattern when a class in your program should have just a single instance available to all clients; for example, a single database object shared by different parts of the program.

  • Use the Singleton pattern when you need stricter control over global variables (DB/Heavy objects(Flyweight)).

  • Violates the Single Responsibility Principle. The pattern solves two problems at the time (self lifecycle and other functionality).

  • The pattern requires special treatment in a multithreaded environment so that multiple threads won’t create a singleton object several times.

Structural design patterns

These design patterns are all about Class and Object composition. Structural class-creation patterns use inheritance to compose interfaces. Structural object-patterns define ways to compose objects to obtain new functionality.

Adapter

Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.

Adapter provides a different interface to the wrapped object, Proxy provides it with the same interface, and Decorator provides it with an enhanced interface.

  • Use the Adapter class when you want to use some existing class, but its interface isn’t compatible with the rest of your code.

  • Use the pattern when you want to reuse several existing subclasses that lack some common functionality that can’t be added to the superclass.

  • Single Responsibility Principle. You can separate the interface or data conversion code from the primary business logic of the program.

  • Open/Closed Principle. You can introduce new types of adapters into the program without breaking the existing client code, as long as they work with the adapters through the client interface.

Bridge

Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.

  • Use the Bridge pattern when you want to divide and organize a monolithic class that has several variants of some functionality (for example, if the class can work with various database servers).

  • Use the pattern when you need to extend a class in several orthogonal (independent) dimensions.

  • Use the Bridge if you need to be able to switch implementations at runtime.

  • You can create platform-independent classes and apps.

  • The client code works with high-level abstractions. It isn’t exposed to the platform details.

A pattern isn’t just a recipe for structuring your code in a specific way. It can also communicate to other developers the problem the pattern solves.

Composite (Object Tree)

Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.

The Composite pattern provides you with two basic element types that share a common interface: simple leaves and complex containers. A container can be composed of both leaves and other containers. This lets you construct a nested recursive object structure that resembles a tree.

  • Use the Composite pattern when you have to implement a tree-like object structure.

  • Use the pattern when you want the client code to treat both simple and complex elements uniformly.

  • You can work with complex tree structures more conveniently: use polymorphism and recursion to your advantage.

  • Open/Closed Principle. You can introduce new element types into the app without breaking the existing code, which now works with the object tree.

Decorator (Wrapper)

Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

The Decorator lets you structure your business logic into layers, create a decorator for each layer and compose objects with various combinations of this logic at runtime. The client code can treat all these objects in the same way, since they all follow a common interface.

  • Use the pattern when it’s awkward or not possible to extend an object’s behavior using inheritance.

  • Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects.

  • You can extend an object’s behavior without making a new subclass.

  • You can add or remove responsibilities from an object at runtime.

  • You can combine several behaviors by wrapping an object into multiple decorators.

  • Single Responsibility Principle. You can divide a monolithic class that implements many possible variants of behavior into several smaller classes.

  • It’s hard to remove a specific wrapper from the wrappers stack.

  • It’s hard to implement a decorator in such a way that its behavior doesn’t depend on the order in the decorators stack.

Facade

Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.

Often, subsystems get more complex over time. Even applying design patterns typically leads to creating more classes. A subsystem may become more flexible and easier to reuse in various contexts, but the amount of configuration and boilerplate code it demands from a client grows ever larger. The Facade attempts to fix this problem by providing a shortcut to the most-used features of the subsystem which fit most client requirements.

  • Use the Facade pattern when you need to have a limited but straightforward interface to a complex subsystem.

  • Use the Facade when you want to structure a subsystem into layers.

  • You can isolate your code from the complexity of a subsystem.

  • A facade can become a god object coupled to all classes of an app.

  • Facade defines a simplified interface to a subsystem of objects, but it doesn’t introduce any new functionality. The subsystem itself is unaware of the facade. Objects within the subsystem can communicate directly. (Facade and Mediator have similar jobs: they try to organize collaboration between lots of tightly coupled classes.)

Flyweight (Cache)

Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.

The benefit of applying the pattern depends heavily on how and where it’s used. It’s most useful when:

  • an application needs to spawn a huge number of similar objects
  • this drains all available RAM on a target device
  • the objects contain duplicate states which can be extracted and shared between multiple objects
  • Use the Flyweight pattern only when your program must support a huge number of objects which barely fit into available RAM.

  • The Singleton object can be mutable. Flyweight objects are immutable.

Proxy

Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.

  • Lazy initialization (virtual proxy). This is when you have a heavyweight service object that wastes system resources by being always up, even though you only need it from time to time.

  • Access control (protection proxy). This is when you want only specific clients to be able to use the service object; for instance, when your objects are crucial parts of an operating system and clients are various launched applications (including malicious ones).

  • Local execution of a remote service (remote proxy). This is when the service object is located on a remote server. In this case, the proxy passes the client request over the network, handling all of the nasty details of working with the network.

  • Logging requests (logging proxy). This is when you want to keep a history of requests to the service object.

  • Caching request results (caching proxy). This is when you need to cache results of client requests and manage the life cycle of this cache, especially if results are quite large.

  • Smart reference. This is when you need to be able to dismiss a heavyweight object once there are no clients that use it. The proxy can keep track of clients that obtained a reference to the service object or its results. From time to time, the proxy may go over the clients and check whether they are still active. If the client list gets empty, the proxy might dismiss the service object and free the underlying system resources.

Decorator and Proxy have similar structures, but very different intents. Both patterns are built on the composition principle, where one object is supposed to delegate some of the work to another. The difference is that a Proxy usually manages the life cycle of its service object on its own, whereas the composition of Decorators is always controlled by the client.

Behavioral design patterns

These design patterns are all about Class's objects communication. Behavioral patterns are those patterns that are most specifically concerned with communication between objects.

Chain of responsibility

Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.

  • Use the pattern when your program is expected to process different kinds of requests in various ways, but the exact types of requests and their sequences are unknown beforehand. The pattern lets you link several handlers into one chain and, upon receiving a request, “ask” each handler whether it can process it. This way all handlers get a chance to process the request.

  • Use the pattern when it’s essential to execute several handlers in a particular order. Since you can link the handlers in the chain in any order, all requests will get through the chain exactly as you planned.

  • Use the pattern when the set of handlers and their order are supposed to change at runtime. If you provide setters for a reference field inside the handler classes, you’ll be able to insert, remove or reorder handlers dynamically.

  • You can control the order of request handling.

  • Single Responsibility Principle. You can decouple classes that invoke operations from classes that perform operations.

  • Open/Closed Principle. You can introduce new handlers into the app without breaking the existing client code.

  • Some requests may end up unhandled.

Command (Action, Transaction)

Command is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method arguments, delay or queue a request’s execution, and support undoable operations.

  • Use the Command pattern when you want to parametrize objects with operations.

  • Use the Command pattern when you want to queue operations, schedule their execution, or execute them remotely.

  • Use the Command pattern when you want to implement reversible operations.

  • Single Responsibility Principle. You can decouple classes that invoke operations from classes that perform these operations.

  • Open/Closed Principle. You can introduce new commands into the app without breaking existing client code.

Iterator

Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).

The iterator encapsulates the details of working with a complex data structure, providing the client with several simple methods of accessing the collection elements. While this approach is very convenient for the client, it also protects the collection from careless or malicious actions which the client would be able to perform if working with the collection directly.

  • Use the Iterator pattern when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (either for convenience or security reasons).

  • Use the pattern to reduce duplication of the traversal code across your app.

  • Use the Iterator when you want your code to be able to traverse different data structures or when types of these structures are unknown beforehand.

  • You can iterate over the same collection in parallel because each iterator object contains its own iteration state.

  • For the same reason, you can delay an iteration and continue it when needed.

Mediator (Intermediary, Controller)

Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.

  • Use the Mediator pattern when it’s hard to change some of the classes because they are tightly coupled to a bunch of other classes. The pattern lets you extract all the relationships between classes into a separate class, isolating any changes to a specific component from the rest of the components.

  • Use the pattern when you can’t reuse a component in a different program because it’s too dependent on other components. After you apply the Mediator, individual components become unaware of the other components. They could still communicate with each other, albeit indirectly, through a mediator object. To reuse a component in a different app, you need to provide it with a new mediator class.

  • Use the Mediator when you find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts.

  • Over time a mediator can evolve into a God Object.

The primary goal of Mediator is to eliminate mutual dependencies among a set of system components. Instead, these components become dependent on a single mediator object. The goal of Observer is to establish dynamic one-way connections between objects, where some objects act as subordinates of others.

Memento (Snapshot)

Memento is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation.

The Memento pattern lets you make full copies of an object’s state, including private fields, and store them separately from the object. While most people remember this pattern thanks to the “undo” use case, it’s also indispensable when dealing with transactions (i.e., if you need to roll back an operation on error).

  • Use the Memento pattern when you want to produce snapshots of the object’s state to be able to restore a previous state of the object.

  • Use the pattern when direct access to the object’s fields/getters/setters violates its encapsulation.

Observer (Event-Subscriber, Listener)

Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

  • Use the Observer pattern when changes to the state of one object may require changing other objects, and the actual set of objects is unknown beforehand or changes dynamically.

State

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.

The pattern suggests that you extract all state-specific code into a set of distinct classes. As a result, you can add new states or change existing ones independently of each other, reducing the maintenance cost.

  • Use the State pattern when you have an object that behaves differently depending on its current state, the number of states is enormous, and the state-specific code changes frequently.

  • Use the pattern when you have a class polluted with massive conditionals that alter how the class behaves according to the current values of the class’s fields.

  • Use State when you have a lot of duplicate code across similar states and transitions of a condition-based state machine.

Strategy

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

  • Use the pattern when you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime. The pattern lets you indirectly alter the object’s behavior at runtime by associating it with different sub-objects which can perform specific sub-tasks in different ways.

  • Use the pattern when you have a lot of similar classes that only differ in the way they execute some behavior. The pattern lets you extract the varying behavior into a separate class hierarchy and combine the original classes into one, thereby reducing duplicate code.

  • Use the pattern to isolate the business logic of a class from the implementation details of algorithms that may not be as important in the context of that logic. The pattern lets you isolate the code, internal data, and dependencies of various algorithms from the rest of the code. Various clients get a simple interface to execute the algorithms and switch them at runtime.

  • Use the pattern when your class has a massive conditional statement that switches between different variants of the same algorithm. The pattern lets you do away with such a conditional by extracting all algorithms into separate classes, all of which implement the same interface. The original object delegates execution to one of these objects, instead of implementing all variants of the algorithm.

  • You can swap algorithms used inside an object at runtime.

  • You can isolate the implementation details of an algorithm from the code that uses it.

  • You can replace inheritance with composition.

  • Open/Closed Principle. You can introduce new strategies without having to change the context.

A lot of modern programming languages have functional type support that lets you implement different versions of an algorithm inside a set of anonymous functions. Then you could use these functions exactly as you’d have used the strategy objects, but without bloating your code with extra classes and interfaces.

Template method

Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.

  • Use the pattern when you want to let clients extend only particular steps of an algorithm, but not the whole algorithm or its structure.

  • Use the pattern when you have several classes that contain almost identical algorithms with some minor differences. As a result, you might need to modify all classes when the algorithm changes. When you turn such an algorithm into a template method, you can also pull up the steps with similar implementations into a superclass, eliminating code duplication. Code that varies between subclasses can remain in subclasses.

  • You might violate the Liskov Substitution Principle by suppressing a default step implementation via a subclass.

  • Factory Method is a specialization of Template Method. At the same time, a Factory Method may serve as a step in a large Template Method.*

Template Method is based on inheritance: it lets you alter parts of an algorithm by extending those parts in subclasses. Strategy is based on composition: you can alter parts of the object’s behavior by supplying it with different strategies that correspond to that behavior. Template Method works at the class level, so it’s static. Strategy works on the object level, letting you switch behaviors at runtime.

Visitor

Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.

  • Use the Visitor when you need to perform an operation on all elements of a complex object structure (for example, an object tree). The Visitor pattern lets you execute an operation over a set of objects with different classes by having a visitor object implement several variants of the same operation, which correspond to all target classes.

  • Use the Visitor to clean up the business logic of auxiliary behaviors. The pattern lets you make the primary classes of your app more focused on their main jobs by extracting all other behaviors into a set of visitor classes.

  • Use the pattern when a behavior makes sense only in some classes of a class hierarchy, but not in others. You can extract this behavior into a separate visitor class and implement only those visiting methods that accept objects of relevant classes, leaving the rest empty.

  • Open/Closed Principle. You can introduce a new behavior that can work with objects of different classes without changing these classes.

  • Single Responsibility Principle. You can move multiple versions of the same behavior into the same class.

  • A visitor object can accumulate some useful information while working with various objects. This might be handy when you want to traverse some complex object structure, such as an object tree, and apply the visitor to each object of this structure.

  • You need to update all visitors each time a class gets added to or removed from the element hierarchy.

  • Visitors might lack the necessary access to the private fields and methods of the elements that they’re supposed to work with.

You can treat Visitor as a powerful version of the Command pattern. Its objects can execute operations over various objects of different classes.

You can use Visitor to execute an operation over an entire Composite tree.

You can use Visitor along with Iterator to traverse a complex data structure and execute some operation over its elements, even if they all have different classes.

SOLID

SRP Single Responsibility Principle

  • Class do only 1 thing
  • Small classes/tiny components

OCP Open-Closed Principle

  • Structure should be closed for modification, but open for extensions.
  • Once class was written you should not change it. You should be able to expand it.

LSP Liskov Substitution Principle

  • Two types with the same base should be interchangeable.

Enables you to replace objects of a parent class with objects of a subclass without breaking the application. This requires all subclasses to behave in the same way as the parent class.

  • Don’t implement any stricter validation rules on input parameters than implemented by the parent class.
  • Apply at the least the same rules to all output parameters as applied by the parent class.

ISP Interface Segregation Principle

  • Keep small interfaces.
  • Implement many interfaces if needed.

DIP Dependency Inversion Principle

  • Use Interfaces or abstract classes

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.

  • Abstractions should not depend on details. Details should depend on abstractions.

Unity fundamentals

GameObject

GameObjects are the fundamental objects in Unity that represent characters, props and scenery. They do not accomplish much in themselves but they act as containers for Components, which implement the functionality.

The Transform is used to store a GameObject’s position, rotation, scale and parenting state and is thus very important. A GameObject will always have a Transform component attached - it is not possible to remove a Transform or to create a GameObject without one.

Parenting is one of the most important concepts to understand when using Unity. When a GameObject is a Parent of another GameObject, the Child GameObject will move, rotate, and scale exactly as its Parent does.

When parenting Transforms, it is useful to set the parent’s location to <0,0,0> before adding the child. This means that the local coordinates for the child will be the same as global coordinates making it easier to be sure you have the child in the right position.

The position, rotation and scale values of a Transform are measured relative to the Transform’s parent. If the Transform has no parent, the properties are measured in world space.

Importance of Scale

The scale of the Transform determines the difference between the size of a mesh in your modeling application and the size of that mesh in Unity. The mesh’s size in Unity (and therefore the Transform’s scale) is very important, especially during physics simulation. By default, the physics engine assumes that one unit in world space corresponds to one meter. If an object is very large, it can appear to fall in “slow motion”; the simulation is actually correct since effectively, you are watching a very large object falling a great distance.

Ideally, you should not adjust the Scale of your object in the Transform Component. The best option is to create your models at real-life scale so you won’t have to change your Transform’s scale. The next best option is to adjust the scale at which your mesh is imported in the Import Settings for your individual mesh. Certain optimizations occur based on the import size, and instantiating an object that has an adjusted scale value can decrease performance.

Limitations with Non-Uniform Scaling

Non-uniform scaling is when the Scale in a Transform has different values for x, y, and z; for example (2, 4, 2). In contrast, uniform scaling has the same value for x, y, and z; for example (3, 3, 3). Non-uniform scaling can be useful in a few specific cases but it introduces a few oddities that don’t occur with uniform scaling:

  • Certain components do not fully support non-uniform scaling. For example, some components have a circular or spherical element defined by a radius property, among them Sphere Collider, Capsule Collider, Light and Audio Source. In cases like this the circular shape will not become elliptical under non-uniform scaling as you would expect and will simply remain circular.
  • When a child object has a non-uniformly scaled parent and is rotated relative to that parent, it may appear skewed or “sheared”. There are components that support simple non-uniform scaling but don’t work correctly when skewed like this. For example, a skewed Box Collider will not match the shape of the rendered mesh accurately.
  • For performance reasons, a child object of a non-uniformly scaled parent will not have its scale automatically updated when it rotates. As a result, the child’s shape may appear to change abruptly when the scale eventually is updated, say if the child object is detached from the parent.

Deactivate GameObjects

To temporarily remove a GameObject from your scene, you can mark the GameObject as inactive. To deactivate a GameObject through script, use the SetActive method. To see if an object is active or inactive, check the activeSelf property.

If you deactivate a parent GameObject, you also deactivate all of its child GameObjects because the deactivation overrides the activeSelf setting on all child GameObjects. The child GameObjects return to their original state when you reactivate the parent.

To know if a child GameObject is active in your scene, use the activeInHierarchy property.

Note: The activeSelf property is not always accurate if you check a child GameObject because even if it is set to active, you might have set one of its parent GameObjects to inactive.

Static GameObjects

If a GameObject does not move at runtime, it is known as a static GameObject. If a GameObject moves at runtime, it is known as a dynamic GameObject. Many systems in Unity can precompute information about static GameObjects in the Editor. Because the GameObjects do not move, the results of these calculations are still valid at runtime. This means that Unity can save on runtime calculations, and potentially improve performance.

Static systems: Contribute GI, Occluder Static, Occludee Static, Batching Static, Navigation Static, Off Mesh Link Generation, Reflection Probe

Tags

A Tag is a reference word which you can assign to one or more GameObjects. Tags help you identify GameObjects for scripting purposes. They ensure you don’t need to manually add GameObjects to a script’s exposed properties using drag and drop, thereby saving time when you are using the same script code in multiple GameObjects.

Prefabs

Unity’s Prefab system allows you to create, configure, and store a GameObject complete with all its components, property values, and child GameObjects as a reusable Asset. The Prefab Asset acts as a template from which you can create new Prefab instances in the Scene.

Any edits that you make to a Prefab Asset are automatically reflected in the instances of that Prefab, allowing you to easily make broad changes across your whole Project without having to repeatedly make the same edit to every copy of the Asset.

You can nest Prefabs inside other Prefabs to create complex hierarchies of objects that are easy to edit at multiple levels.

However, this does not mean all Prefab instances have to be identical. You can override settings on individual prefab instances if you want some instances of a Prefab to differ from others. You can also create variants of Prefabs which allow you to group a set of overrides together into a meaningful variation of a Prefab.

You should also use Prefabs when you want to instantiate GameObjects at runtime that did not exist in your Scene at the start - for example, to make powerups, special effects, projectiles, or NPCs appear at the right moments during gameplay.

Instance overrides

Instance overrides allow you to create variations between Prefab instances, while still linking those instances to the same Prefab Asset.

There are four different types of instance override:

  • Overriding the value of a property
  • Adding a component
  • Removing a component
  • Adding a child GameObject

There are some limitations with Prefab instances: you cannot reparent a GameObject that is part of a Prefab, and you cannot remove a GameObject that is part of the Prefab. You can, however, deactivate a GameObject, which is a good substitute for removing a GameObject (this counts as a property override).

Overrides take precedence

An overridden property value on a Prefab instance always takes precedence over the value from the Prefab Asset. This means that if you change a property on a Prefab Asset, it doesn’t have any effect on instances where that property is overridden.

If you make a change to a Prefab Asset, and it does not update all instances as expected, you should check whether that property is overridden on the instance. It is best to only use instance overrides when strictly necessary, because if you have a large number of instance overrides throughout your Project, it can be difficult to tell whether your changes to the Prefab Asset will or won’t have an effect on all of the instances.

The alignment of a Prefab instance is a special case, and is handled differently to other properties. The alignment values are never carried across from the Prefab Asset to the Prefab instances. This means they can always differ from the Prefab Asset’s alignment without it being an explicit instance override. Specifically, the alignment means the Position and Rotation properties on the root Transform of the Prefab instance, and for a Rect Transform this also includes the Width, Height, Margins, Anchors and Pivot properties.

Prefab Variants

Prefab Variants are useful when you want to have a set of predefined variations of a Prefab. A Prefab Variant inherits the properties of another Prefab, called the base. Overrides made to the Prefab Variant take precedent over the base Prefab’s values. A Prefab Variant can have any other Prefab as its base, including Model Prefabs or other Prefab Variants.

As with any Prefab instance, you can use prefab overrides in a Prefab Variant, such as modified property values, added components, removed components, and added child GameObjects. There are also the same limitations: you cannot reparent GameObjects in the Prefab Variant which come from its base Prefab. You also cannot remove a GameObject from a Prefab Variant which exists in its base Prefab. You can, however, deactivate GameObjects (as a property override) to achieve the same effect as removing a GameObject.

The point of a Prefab Variant is to provide a convenient way to store a meaningful and reusable collection of overrides, which is why they should normally remain as overrides and not get applied to the base Prefab Asset.

Unpacking Prefab instances

You can unpack a Prefab instance by right-clicking on it in the Hierarchy and selecting Unpack Prefab. The resulting GameObject in the Scene no longer has any link to its former Prefab Asset. The Prefab Asset itself is not affected by this operation and there may still be other instances of it in your Project

Layers

Layers are a tool that allows you to separate GameObjects in your scenes.

You can render only the objects in a particular layer, or selection of layers, if you use the Camera’s culling mask. UI elements and screen space canvas children are exceptions to this and render regardless.

You can use layers to specify which GameObjects that a ray cast can intersect with. To make a ray cast ignore a GameObject, you can assign it to the Ignore Raycast layer, or pass a LayerMask to the ray cast API call. If you don’t pass a LayerMask to the ray cast API call, Unity uses Physics.DefaultRaycastLayers which matches every layer except Ignore Raycast.

var combinedLayerMask = (1 << layerMask1 | (1 << layerMask2);

/*
~ inverse
| or
& and
*/

// add to existing

combinedLayerMask |= (1 << layerMask3);

// remove from existing

combinedLayerMask &= ~(1 << layerMask3);

Constraints

A Constraint component links the position, rotation, or scale of a GameObject to another GameObject. A constrained GameObject moves, rotates, or scales like the GameObject it is linked to.

Unity supports the following types of Constraint components:

  • Aim: Rotate the constrained GameObject to face the linked GameObject.
  • Look At: Rotate the constrained GameObject to the linked GameObject (simplified Aim Constraint).
  • Parent: Move and rotate the constrained GameObject with the linked GameObject.
  • Position: Move the constrained GameObject like the linked GameObject.
  • Rotation: Rotate the constrained GameObject like the linked GameObject.
  • Scale: Scale the constrained GameObject like the linked GameObject.

Avoid creating a cycle of Constraints, because this causes unpredictable updates during gameplay.

There are two aspects to working with Constraints: activating and locking.

You activate a Constraint to allow it to evaluate the position, rotation, or scale of the constrained GameObject. Unity does not evaluate inactive Constraints.

You lock a Constraint to allow it to move, rotate, or scale the GameObject. A locked Constraint takes control of the relevant parts of the Transform of the GameObject. You cannot manually move, rotate, or scale a GameObject with a locked Constraint. You also cannot edit the Constraint Settings.

Rotation and orientation in Unity

In Unity, you can use both Euler angles and quaternions to represent rotations and orientation. These representations are equivalent but have different uses and limitations.

Typically, you rotate objects in your scene using the Transform component, which displays orientation as a Euler angle. However, Unity stores rotations and orientations internally as quaternions, which can be useful for more complex motions that might otherwise lead to gimbal lock.

Euler angles

In the Transform coordinate, Unity displays rotation with the vector property Transform.eulerAngles X, Y, and Z. Unlike a normal vector, these values actually represent the angle (in degrees) of rotation about the X, Y, and Z axes.

Euler angle rotations perform three separate rotations around the three axes. Unity performs the Euler rotations sequentially around the z-axis, the x-axis and then the y-axis. This method of rotation is extrinsic rotation; the original coordinate system doesn’t change while the rotations occur.

To rotate a GameObject, you can enter angle values of how far you want each axis to rotate into the Transform component. To rotate your GameObjects with script, use Transform.eulerAngles. If you convert to Euler angles to do calculations and rotations, you risk problems with gimbal lock.

Gimbal lock

When an object in 3D space loses a degree of freedom and can only rotate within two dimensions, it’s called gimbal lock. Gimbal lock can occur with Euler angles if two axes become parallel. If you don’t convert the rotational values to Euler angles in your script, the use of quaternions should prevent gimbal lock.

If you do have problems with gimbal lock, you can avoid Euler angles if you use Transform.RotateAround for rotations. You can also use Quaternion.AngleAxis on each axis and multiply them together (quaternion multiplication applies each rotation in turn).

Quaternions

Quaternions provide mathematical notation for unique representations of spatial orientation and rotation in 3D space. A quaternion uses four numbers to encode the direction and angle of rotation around unit axes in 3D. These four values are complex numbers rather than angles or degrees.

Unity converts rotational values to quaternions to store them because quaternion rotations are efficient and stable to compute. The Editor doesn’t display rotations as quaternions because a single quaternion can’t represent a rotation greater than 360 degrees about any axis.

Convert between Euler angles and quaternions
  • To convert from Euler angles to quaternions, you can use the Quaternion.Euler function.
  • To convert a quaternion to Euler angles, you can use the Quaternion.eulerAngles function.

Cross-Platform Considerations

Input

Keyboard and joypad

The Input.GetAxis function is convenient on desktop platforms to consolidate keyboard and joypad input. This function isn’t suitable for mobile platforms that rely on touchscreen input. The standard desktop keyboard input is only suitable for porting typed text to mobile devices.

Touches and clicks

The Input.GetMouseButtonXXX functions are designed to have an obvious interpretation on mobile devices. The screen reports a simple touch as a left click and the Input.mousePosition property gives the position of the touch, as long as the finger is touching the screen. Games with simple mouse interactions can often work transparently between the desktop and mobile platforms.

Memory, storage and CPU performance

Mobile devices have less storage, memory and CPU power available than desktop machines and so a game may be difficult to port simply because its performance isn’t acceptable on lower powered hardware. If you are pushing the limits of your desktop hardware, then the game probably isn’t a good candidate for porting to a mobile platform.

Storage requirements

Video, audio, and textures can use a lot of storage space. You need to manage storage effectively if you want to port your game. Storage space (which often also corresponds to download time) is usually not an issue on desktop machines, but it can be limited on mobile devices. Mobile app stores often impose a limit on the maximum size of a submitted product. It might require some planning to address these concerns during your game development.

For example, you may need to provide cut-down versions of assets for mobiles to save space. Another possibility is that the game may need to be designed so that large assets can be downloaded on demand rather than being part of the initial download of the application.

  • Unity re-codes imported Assets into its own internal formats, so the choice of source Asset type is not relevant. For example, if you have a multi-layer Photoshop Texture in the Project, it is flattened and compressed before building. Exporting the Texture as a .png file does not make any difference to build size, so you should stick to the format that is most convenient for you during development.

  • Unity strips most unused Assets during the build, so you don’t gain anything by manually removing Assets from the Project. The only Assets that are not removed are scripts (which are generally very small anyway) and Assets in the Resources folder (because Unity can’t determine which of these are needed and which are not). With this in mind, you should make sure that the only Assets in the Resources folder are the ones you need for the game. You might be able to replace Assets in the Resources folder with AssetBundles - this means that Unity loads Assets dynamically, thereby reducing the player size.

  • Mesh and Animation compression uses quantization, which means it takes less space, but the compression can introduce some inaccuracies.

  • Mesh compression only produces smaller data files, and does not use less memory at run time. Animation keyframe reduction produces smaller data files and uses less memory at run time; generally you should always have it enabled.

Automatic memory management

Unity automatically handles the recovery of unused memory from “dead” objects and often happens unnoticed on desktop machines. However, the lower memory and CPU power on mobile devices means that garbage collections can be more frequent, impacting performance and causing unwanted pauses in gameplay. Even if the game runs in the available memory, it might be necessary to optimize code to avoid garbage collection pauses

CPU power

A game that runs well on a desktop machine might experience poor framerate on a mobile device because the mobile CPU struggles with the game complexity. Pay attention to code efficiency when a project is ported to a mobile platform.

A script makes its connection with the internal workings of Unity by implementing a class which derives from the built-in class called MonoBehaviour.

An initialization of an object is not done using a constructor function. This is because the construction of objects is handled by the editor and does not take place at the start of gameplay as you might expect. If you attempt to define a constructor for a script component, it will interfere with the normal operation of Unity and can cause major problems with the project.

Variables and the Inspector

  • The way to see a variable in the Inspector is to declare it as public.
  • An alternative method is to use SerializeField.
  • HideInInspector to prevent a public variable from being displayed in the Inspector.

First Scene load

  • Awake: This function is always called before any Start functions and also just after a prefab is instantiated. (If a GameObject is inactive during start up Awake is not called until it is made active.)
  • OnEnable: (only called if the Object is active): This function is called just after the object is enabled. This happens when a MonoBehaviour instance is created, such as when a level is loaded or a GameObject with the script component is instantiated.

Note that for objects added to the scene, the Awake and OnEnable functions for all scripts will be called before Start, Update, etc are called for any of them. Naturally, this cannot be enforced when you instantiate an object during gameplay.

Editor

  • Reset: Reset is called to initialize the script’s properties when it is first attached to an object and also when the Reset command is used.

  • OnValidate: OnValidate is called whenever the script’s properties are set, including when an object is deserialized, which can occur at various times, such as when you open a scene in the Editor and after a domain reload.

Before the first frame update

  • Start: Is called before the first frame update only if the script instance is enabled. For objects that are part of a scene asset, the Start function is called on all scripts before Update, etc is called for any of them.

In between frames

  • OnApplicationPause: This is called at the end of the frame where the pause is detected, effectively between the normal frame updates. One extra frame will be issued after OnApplicationPause is called to allow the game to show graphics that indicate the paused state.

Update Order

The common pattern is to perform most tasks inside the Update function, but there are also other functions you can use.

  • FixedUpdate: FixedUpdate is often called more frequently than Update. It can be called multiple times per frame, if the frame rate is low and it may not be called between frames at all if the frame rate is high. All physics calculations and updates occur immediately after FixedUpdate. When applying movement calculations inside FixedUpdate, you do not need to multiply your values by Time.deltaTime. This is because FixedUpdate is called on a reliable timer, independent of the frame rate.

  • Update: Update is called once per frame. It is the main workhorse function for frame updates.

  • LateUpdate: LateUpdate is called once per frame, after Update has finished. Any calculations that are performed in Update will have completed when LateUpdate begins. A common use for LateUpdate would be a following third-person camera. If you make your character move and turn inside Update, you can perform all camera movement and rotation calculations in LateUpdate. This will ensure that the character has moved completely before the camera tracks its position.

You cannot specify the order in which an event function is called for different instances of the same MonoBehaviour subclass. For example, the Update function of one MonoBehaviour might be called before or after the Update function for the same MonoBehaviour on another GameObject — including its own parent or child GameObjects.

You can specify that the event functions of one MonoBehaviour subclass should be invoked before those of a different subclass (using the Script Execution Order panel of the Project Settings window).

Animation update loop

These functions and Profiler Markers are called when Unity evaluates the Animation system.

  • OnStateMachineEnter: During the State Machine Update step, this callback is called on the first update frame when a controller’s state machine makes a transition that flows through an Entry state. It is not called for a transition to a StateMachine sub-state.

This callback occurs only if there is a controller component (for example, AnimatorController or AnimatorOverrideController or AnimatorControllerPlayable) in the animation graph.

Note: Adding this callback to a StateMachineBehaviour component disables multithreaded state machine evaluation.

  • OnStateMachineExit: During the State Machine Update step, this callback is called on the last update frame when a controller’s state machine makes a transition that flows through an Exit state. It is not called for a transition to a StateMachine sub-state.

This callback occurs only if there is a controller component (for example, AnimatorController or AnimatorOverrideController or AnimatorControllerPlayable) in the animation graph.

Note: Adding this callback to a StateMachineBehaviour component disables multithreaded state machine evaluation.

  • Fire Animation Events: Calls all animation events from all clips sampled between the time of the last update and the time of the current update.

  • StateMachineBehaviour (OnStateEnter/OnStateUpdate/OnStateExit): A layer can have up to 3 active states: current state, interrupted state, and next state. This function is called for each active state with a StateMachineBehaviour component that defines the OnStateEnter, OnStateUpdate, or OnStateExit callback.

The function is called for the current state first, then the interrupted state, and finally the next state.

This step occurs only if there is a controller component (for example, AnimatorController or AnimatorOverrideController or AnimatorControllerPlayable) in the animation graph..

  • OnAnimatorMove: Every update frame, this is called once for each Animator component to modify the Root Motion.

  • StateMachineBehaviour(OnStateMove): This is called on each active state with a StateMachineBehaviour that defines this callback.

  • OnAnimatorIK: Sets up animation IK. This is called once for each Animator Controller layer with IK pass enabled. This event executes only if you are using a Humanoid rig.

  • StateMachineBehaviour(OnStateIK): This is called on each active state with a StateMachineBehaviour component that defines this callback on a layer with IK pass enabled.

  • WriteProperties: Writes all other animated properties to the Scene from the main thread.

Useful profile markers

Some of the animation functions shown in the Script Lifecycle Flowchart are not Event functions that you can call; they are internal functions called when Unity processes your animation.

These functions have Profiler Markers, so you can use the Profiler to see when in the frame Unity calls them. Knowing when Unity calls these functions can help you understand exactly when the Event functions you do call are executed.

  • State Machine Update: All state machines are evaluated at this step in the execution sequence. This step occurs only if there is a controller component (for example, AnimatorController or AnimatorOverrideController or AnimatorControllerPlayable) in the animation graph.

  • Note: State machine evaluation is normally multithreaded, but adding certain callbacks (for example OnStateMachineEnter and OnStateMachineExit) disables multithreading. See Animation update loop above for details.

  • ProcessGraph: Evaluates all animation graphs. This includes sampling all animation clips that need to be evaluated, and computing Root Motion.

  • ProcessAnimation: Blends the results of the animation graph.

  • WriteTransforms: Writes all animated transforms to the scene from a worker thread. A Humanoid rig with multiple layers that have IK pass enabled can have multiple WriteTransforms passes.

Rendering

Note: These callbacks only work with the Built-in Render Pipeline.

  • OnPreCull: Called before the camera culls the scene. Culling determines which objects are visible to the camera. OnPreCull is called just before culling takes place.

  • OnBecameVisible/OnBecameInvisible: Called when an object becomes visible/invisible to any camera.

  • OnWillRenderObject: Called once for each camera if the object is visible.

  • OnPreRender: Called before the camera starts rendering the scene.

  • OnRenderObject: Called after all regular scene rendering is done. You can use GL class or Graphics.DrawMeshNow to draw custom geometry at this point.

  • OnPostRender: Called after a camera finishes rendering the scene.

  • OnRenderImage: Called after scene rendering is complete to allow post-processing of the image, see Post-processing Effects.

  • OnGUI: Called multiple times per frame in response to GUI events. The Layout and Repaint events are processed first, followed by a Layout and keyboard/mouse event for each input event.

  • OnDrawGizmos Used for drawing Gizmos in the scene view for visualisation purposes.

Coroutines

Normal coroutine updates are run after the Update function returns. A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes. Different uses of Coroutines:

  • yield The coroutine will continue after all Update functions have been called on the next frame.

  • yield WaitForSeconds Continue after a specified time delay, after all Update functions have been called for the frame.

  • yield WaitForFixedUpdate Continue after all FixedUpdate has been called on all scripts. If the coroutine yielded before FixedUpdate, then it resumes after FixedUpdate in the current frame.

  • yield WWW Continue after a WWW download has completed.

  • yield StartCoroutine Chains the coroutine, and will wait for the MyFunc coroutine to complete first.

When the Object is destroyed

  • OnDestroy: This function is called after all frame updates for the last frame of the object’s existence (the object might be destroyed in response to Object.Destroy or at the closure of a scene).

When quitting

These functions get called on all the active objects in your scene:

  • OnApplicationQuit: This function is called on all game objects before the application is quit. In the editor it is called when the user stops playmode.
  • OnDisable: This function is called when the behaviour becomes disabled or inactive.

Event Functions

Unity passes control to a script intermittently by calling certain functions that are declared within it. Once a function has finished executing, control is passed back to Unity. These functions are known as event functions since they are activated by Unity in response to events that occur during gameplay. Unity uses a naming scheme to identify which function to call for a particular event.

Initialization Events

void Start(); The Start function is called before the first frame or physics update on an object.

void Awake(); The Awake function is called for each object in the scene at the time when the scene loads. Note that although the various objects’ Start and Awake functions are called in arbitrary order, all the Awakes will have finished before the first Start is called. This means that code in a Start function can make use of other initializations previously carried out in the Awake phase.

Regular Update Events

void Update();

A game is rather like an animation where the animation frames are generated on the fly. A key concept in games programming is that of making changes to position, state and behavior of objects in the game just before each frame is rendered. The Update function is the main place for this kind of code in Unity. Update is called before the frame is rendered and also before animations are calculated.

void FixedUpdate(); The physics engine also updates in discrete time steps in a similar way to the frame rendering. A separate event function called FixedUpdate is called just before each physics update. Since the physics updates and frame updates do not occur with the same frequency, you will get more accurate results from physics code if you place it in the FixedUpdate function rather than Update.

void LateUpdate(); It is also useful sometimes to be able to make additional changes at a point after the Update and FixedUpdate functions have been called for all objects in the scene and after all animations have been calculated. An example is where a camera should remain trained on a target object; the adjustment to the camera’s orientation must be made after the target object has moved. The LateUpdate function can be used for these kinds of situations.

Physics events

The physics engine will report collisions against an object by calling event functions on that object’s script. The OnCollisionEnter, OnCollisionStay and OnCollisionExit functions will be called as contact is made, held and broken. The corresponding OnTriggerEnter, OnTriggerStay and OnTriggerExit functions will be called when the object’s collider is configured as a Trigger (ie, a collider that simply detects when something enters it rather than reacting physically). These functions may be called several times in succession if more than one contact is detected during the physics update and so a parameter is passed to the function giving details of the collision (position, identity of the incoming object, etc).

GUI events

Unity has a system for rendering GUI controls over the main action in the scene and responding to clicks on these controls. This code is handled somewhat differently from the normal frame update and so it should be placed in the OnGUI function, which will be called periodically.

You can also detect mouse events that occur over a GameObject as it appears in the scene. This can be used for targeting weapons or displaying information about the character currently under the mouse pointer. A set of OnMouseXXX event functions (eg, OnMouseOver, OnMouseDown) is available to allow a script to react to user actions with the mouse. For example, if the mouse button is pressed while the pointer is over a particular object then an OnMouseDown function in that object’s script will be called if it exists.

Coroutines

A coroutine allows you to spread tasks across several frames. In Unity, a coroutine is a method that can pause execution and return control to Unity but then continue where it left off on the following frame.

In most situations, when you call a method, it runs to completion and then returns control to the calling method, plus any optional return values. This means that any action that takes place within a method must happen within a single frame update.

In situations where you would like to use a method call to contain a procedural animation or a sequence of events over time, you can use a coroutine.

However, it’s important to remember that coroutines aren’t threads. Synchronous operations that run within a coroutine still execute on the main thread. If you want to reduce the amount of CPU time spent on the main thread, it’s just as important to avoid blocking operations in coroutines as in any other script code. If you want to use multi-threaded code within Unity, consider the C# Job System.

It’s best to use coroutines if you need to deal with long asynchronous operations, such as waiting for HTTP transfers, asset loads, or file I/O to complete.

A coroutine is a method that you declare with an IEnumerator return type and with a yield return statement included somewhere in the body. The yield return null line is the point where execution pauses and resumes in the following frame.

Correct values over the lifetime of the coroutine, and any variable or parameter is preserved between yield statements.

IEnumerator Func() {
  yield return null;
}

StartCoroutine(Func());

Coroutine time delay

By default, Unity resumes a coroutine on the frame after a yield statement. If you want to introduce a time delay, use WaitForSeconds to spread an effect over a period of time, and you can use it as an alternative to including the tasks in the Update method. Unity calls the Update method several times per second, so if you don’t need a task to be repeated quite so often, you can put it in a coroutine to get a regular update but not every single frame. This reduces the number of checks that Unity carries out without any noticeable effect on gameplay.

Stopping coroutines

To stop a coroutine, use StopCoroutine and StopAllCoroutines. A coroutine also stops if you’ve set SetActive to false to disable the GameObject the coroutine is attached to. Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and Unity processes the coroutine, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame.

Note: If you’ve disabled a MonoBehaviour by setting enabled to false, Unity doesn’t stop coroutines.

Analyzing coroutines

It’s best practice to condense a series of operations down to the fewest number of individual coroutines possible. Nested coroutines are useful for code clarity and maintenance, but they impose a higher memory overhead because the coroutine tracks objects.

If a coroutine runs every frame and doesn’t yield on long-running operations, it’s more performant to replace it with an Update or LateUpdate callback. This is useful if you have long-running or infinitely looping coroutines.

All the initial code in a coroutine, from the start of the coroutine method until the first yield statement, appears in the trace whenever Unity starts a coroutine. The initial code most often appears whenever the StartCoroutine method is called. Coroutines that Unity callbacks generate (such as Start callbacks that return an IEnumerator) first appear within their respective Unity callback.

The rest of a coroutine’s code (from the first time it resumes until it finishes executing) appears within the DelayedCallManager line that’s inside Unity’s main loop.

This happens because of the way that Unity executes coroutines. The C# compiler auto generates an instance of a class that backs coroutines. Unity then uses this object to track the state of the coroutine across multiple invocations of a single method. Because local-scope variables within the coroutine must persist across yield calls, Unity hoists the local-scope variables into the generated class, which remain allocated on the heap during the coroutine. This object also tracks the internal state of the coroutine: it remembers at which point in the code the coroutine must resume after yielding.

Because of this, the memory pressure that happens when a coroutine starts is equal to a fixed overhead allocation plus the size of its local-scope variables.

The code which starts a coroutine constructs and invokes an object, and then Unity’s DelayedCallManager invokes it again whenever the coroutine’s yield condition is satisfied. Because coroutines usually start outside of other coroutines, this splits their execution overhead between the yield call and DelayedCallManager.

UnityEvents

UnityEvents are a way of allowing user driven callback to be persisted from edit time to run time without the need for additional programming and script configuration.

UnityEvents are useful for a number of things:

  • Content driven callbacks
  • Decoupling systems
  • Persistent callbacks
  • Preconfigured call events

UnityEvents can be added to any MonoBehaviour and are executed from code like a standard .net delegate. When a UnityEvent is added to a MonoBehaviour it appears in the Inspector and persistent callbacks can be added.

UnityEvents have similar limitations to standard delegates. That is, they hold references to the element that is the target and this stops the target being garbage collected. If you have a UnityEngine.Object as the target and the native representation disappears the callback will not be invoked.

// add and remove event listener

void OnEnable()
{
    ScriptWithEventsInside.StaticEventName += FunctionToRun;
}
void OnDisable()
{
    ScriptWithEventsInside.StaticEventName -= FunctionToRun;
}

Null Reference Exceptions

A NullReferenceException happens when you try to access a reference variable that isn’t referencing any object. If a reference variable isn’t referencing an object, then it’ll be treated as null. The run-time will tell you that you are trying to access an object, when the variable is null by issuing a NullReferenceException.

Reference variables in c# and JavaScript are similar in concept to pointers in C and C++. Reference types default to null to indicate that they are not referencing any object. Hence, if you try and access the object that is being referenced and there isn’t one, you will get a NullReferenceException.

Summary

  • NullReferenceException happens when your script code tries to use a variable which isn’t set (referencing) and object.
  • NullReferenceException can be avoided by writing code that checks for null before accessing an object, or uses try/catch blocks.

Important Classes

GameObject

GameObject class is used to represent anything which can exist in a Scene.

GameObjects are the building blocks for scenes in Unity, and act as a container for functional components which determine how the GameObject looks, and what the GameObject does.

GameObjects are active by default, but can be deactivated, which turns off all components attached to the GameObject. This generally means it will become invisible, and not receive any of the normal callbacks or events such as Update or FixedUpdate.

The GameObject’s active status is represented by the checkbox to the left of the GameObject’s name. You can control this using GameObject.SetActive.

You can also read the current active state using GameObject.activeSelf, and whether or not the GameObject is actually active in the scene using GameObject.activeInHierarchy. The latter of these two is necessary because whether a GameObject is actually active is determined by its own active state, plus the active state of all of its parents. If any of its parents are not active, then it will not be active despite its own active setting.

Some of Unity’s systems, such as Global Illumination, Occlusion, Batching, Navigation, and Reflection Probes, rely on the static status of a GameObject. You can control which of Unity’s systems consider the GameObject to be static by using GameObjectUtility.SetStaticEditorFlags.

Tags provide a way of marking and identifying types of GameObject in your scene and Layers provide a similar but distinct way of including or excluding groups of GameObjects from certain built-in actions, such as rendering or physics collisions.

You can modify tag and layer values via script using the GameObject.tag and GameObject.layer properties. You can also check a GameObject’s tag efficiently by using the CompareTag method, which includes validation of whether the tag exists, and does not cause any memory allocation.

Creating and Destroying GameObjects (derrived from Unity.Object)

  • GameObject can be created using the Instantiate method which makes a new copy of an existing object.

  • Destroy method that will destroy an object after the frame update. Note that the 'Destroy' function can destroy individual components without affecting the GameObject itself.

Destroy(this); // destroy script

Destroy(gameObject); // destroy game object

To create instances of Unity’s built-in primitives, use GameObject.CreatePrimitive, which instantiates a primitive of the type that you specify. The available primitive types are Sphere, Capsule, Cylinder, Cube, Plane and Quad.

GameObject.CreatePrimitive(PrimitiveType.Cube);

Access to components

AddComponent<Type>();

GetComponent<Type>();

Finding child GameObjects

// because all GameObjects implicitly have a Transform
foreach (Transform t in transform) {}

You can also locate a specific child object by name using the Transform.Find method:

transform.Find("Frying Pan");

Finding GameObjects by Name or Tag

GameObject player;
GameObject chef;
GameObject[] stoves;

player = GameObject.Find("MainHeroCharacter");
chef   = GameObject.FindWithTag("Chef");
stoves = GameObject.FindGameObjectsWithTag("Stove");

Sending and Broadcasting messages

BroadcastMessage allows you to send out a call to a named method, without being specific about where that method should be implemented. You can use it to call a named method on every MonoBehaviour on a particular GameObject or any of its children. You can optionally choose to enforce that there must be at least one receiver (or an error is generated).

SendMessage is a little more specific, and only sends the call to a named method on the GameObject itself, and not its children.

SendMessageUpwards is similar, but sends out the call to a named method on the GameObject and all its parents.

MonoBehaviour

The MonoBehaviour class is the base class from which every Unity script derives, by default. It provides the framework which allows you to attach your script to a GameObject in the editor, as well as providing hooks into useful Events such as Start and Update.

  • Allows you to start, stop, and manage Coroutines.
  • Provides access to a large collection of event messages, which allows you to execute your code based on what is currently happening in your project.

Object

Object acts as a base class for all objects that Unity can reference in the editor. Classes which inherit from UnityEngine.Object have special functionality which means they can be dragged and dropped into fields in the Inspector, or picked using the Object Picker next to an Object field.

The Object class provides a few methods which allow you to Instantiate and Destroy them properly, as well as finding references to Objects of a specific type.

When creating your own objects via scripting, you typically do not want to inherit directly from Object. Instead, you should inherit from a class designed to be more specific to your goal:

  • MonoBehaviour if you want to write a custom component which you can add to a GameObject, to control what the GameObject does or provide some functionality relating to it.

  • ScriptableObject if you want to create custom assets which can store serialized data.

Both of these inherit from Unity’s Object class, but provide extra functionality to suit those purposes.

Physics

Math

Graphics

Audio

UI

Navigation

Networking

Footnotes

  1. Design Patterns

@mishazawa
Copy link
Author

Addressing a Computational Problem

Formalize

To formalize means to express a problem using adapted terminology and concepts.

Specify

To specify means to identify algorithms or methods that are well suited to solve the formalized problem.

Code

Coding should always be the last step in solving a computational problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment