Skip to content

Program_CreateObjectInUnity

bozar42 edited this page Feb 23, 2021 · 3 revisions

How To Create An Object In Unity

< Back to Programming >

Introduction

How do we create an object from prefab in Unity? Very simple:

GameObject go = Instantiate(Resources.Load("MyObject") as GameObject);

If we encapsule this line into a method, Create(), it accepts a string as input and outputs a game object. However, there are three potential problems. First, Create() requires a string as input, which is error prone. Second, we might want to set the object's position during creation. And lastly, we might need to add more components to the object. Let's get these things done one by one.

Image: Object factory framework

You can find the actual code here:

Object Tag & Position

We can create an enumeration, ObjectTag, and use it as input.

public GameObject Create(ObjectTag objectTag, Vector3 position)
{
    string prefab = objectTag.ToString();
    GameObject go = Instantiate(Resources.Load(prefab) as GameObject);
    go.transform.position = position;

    return go;
}

If we use two tags to describe one object, Create() will require three arguments, which is kinda messy. That is why we need a custom data object, ProtoObject, and change the method's signature into Create(IPrototype proto).

public class ProtoObject : IPrototype {}
public interface IPrototype
{
    ObjectTag ObjectTag { get; }
    Vector3 Position { get; }
}

Now it is time to define an interface (ICreateObject) and create a class (CreateObject) as the object factory.

public interface ICreateObject
{
    GameObject Create(IPrototype proto);
}
public class CreateObject : ICreateObject {}

Adding Component

Remember our three problems at the beginning of this article? String input, position and adding component. Let's take a look at the third one. Suppose a building object has a Facade component, and an actor has a Momentum component, how do we identify the newly created object? By ObjectTag, which is one of the input arguments.

If there are only a few object tags and components, switch...case can handle the job perfectly:

switch (objectTag)
{
    case ObjectTag.Building:
        go.AddComponent<Facade>();
        break;
    case ObjectTag.Actor:
        go.AddComponent<Momentum>();
        break;
}

However, as you can imagine, the code will grow in size rather rapidly. So a better solution is to publish an event, AddingComponent.

public GameObject Create(IPrototype proto)
{
    // Create an object.
    string prefab = proto.ObjectTag.ToString();
    GameObject go = Instantiate(Resources.Load(prefab) as GameObject);
    // Set position.
    go.transform.position = proto.Position;
    // Add components.
    OnAddingComponent(new AddingComponentEventArgs(proto.ObjectTag, go));

    return go;
}

public class AddingComponentEventArgs : EventArgs
{
    public AddingComponentEventArgs(ObjectTag objectTag, GameObject data)
    {
        ObjectTag = objectTag;
        Data = data;
    }
    public GameObject Data { get; }
    public ObjectTag ObjectTag { get; }
}

The class ActorComponent subscribes AddingComponent:

private void ActorComponent_AddingComponent(object sender, AddingComponentEventArgs e)
{
    if (e.ObjectTag == ObjectTag.Actor)
    {
        e.Data.AddComponent<Momentum>();
    }
}

Lo and behold. Our object factory, which works smoothly, is at present obscured by one cloud. What is the value of IPrototype.Position?

Blueprint

The main purpose of a dungeon generation algorithm is to decide the position of game objects. The code could be quite complicated, so we'd better put this part into another class, Blueprint.

public interface IBlueprint
{
    IPrototype[] GetBlueprint(ObjectTag objectTag);
}
public class Blueprint : IBlueprint {}

And now we need two steps to create a object:

IPrototype[] protos = Blueprint.GetBlueprint(objectTag);
foreach (IPrototype p in protos)
{
    CreateObject.Create(p);
}

Similar to AddingComponent, we publish another event, DrawingBlueprint to get position value from various subscribers.

public IPrototype[] GetBlueprint(ObjectTag objectTag)
{
    var ea = new DrawingBlueprintEventArgs(objectTag);
    OnDrawingBlueprint(ea);
    return ea.Data;
}

public class DrawingBlueprintEventArgs : EventArgs
{
    public DrawingBlueprintEventArgs(ObjectTag objectTag)
    {
        ObjectTag = objectTag;
    }
    public ObjectTag ObjectTag { get; }
    public IPrototype[] Data { get; set; }
}

Object Pool

By tweaking the creation code a little, we can try to fetch an object from pool.

public GameObject Create(IPrototype proto)
{
    // Load an object from pool.
    GameObject go = ObjectPool.LoadFromPool(proto.ObjectTag);
    // Create an object from scratch.
    if (go == null)
    {
        string prefab = proto.ObjectTag.ToString();
        go = Instantiate(Resources.Load(prefab) as GameObject);
        // Add components.
        OnAddingComponent(new AddingComponentEventArgs(proto.ObjectTag, go));
    }
    // Set position.
    go.transform.position = proto.Position;

    return go;
}

The interface IObjectPool is defined like this:

public interface IObjectPool
{
    GameObject LoadFromPool(ObjectTag objectTag);
    void SaveToPool(GameObject go);
}

< Back to Programming >