Generate 3D Character Collections in Unity

Date: 2022-05-01 | unity | 3d | art |

Generate 3D Character Collections in Unity

Generative character NFT collections like Bored Ape Yacht Club and CryptoPunks have traded for millions of dollars on leading marketplaces. In this post, I'm going to show you how to create your own 3D generative character collections using Unity and C#.

Table of Contents

Generation Basics

Before we can generate our character collections, we first need to understand how computer generation works and why it's useful.

The art of computer generation is typically about combining a finite number of elements in many ways such that you can produce a large number of potential outputs. By doing so, we can leverage the power of computers to produce outputs that would be impossible (or at least extremely arduous) to make by hand.

Computer Generation = Finite elements * Many combinations => Large opportunity space

An example of this from real life might be lego characters.

  • Each character has a few basic parts:
    • Head
    • Torso
    • Arms
    • Legs
    • (plus you can attach stuff to it / it to stuff)

Lego character combinations

With just a few different heads, torsos, and legs to play with we can actually create a very large number of "different" characters.

  • 3 of eachType = 3 heads * 3 torsos * 3 legs => 27 combinations
  • 5 of eachType = 5 * 5 * 5 => 125 combinations
  • x of eachType = x ^ eachType => large component opportunity space

Thus we can see how a system that combines a few elements can lead to a very large opportunity space of outcomes. This is what we're going to leverage to build our own generative collections with Unity and C#.

Character Components

Now that we understand how generation works, we've got to get all of the components together to start building our character collections.

Since we're building in 3D, we'll need to collect the different parts we want to use in our characters.

For example, if we were building those lego characters we'd need:

  • Head
  • Torso
  • Arms
  • Legs

Finding these parts is out of the scope of this post and the ones you choose will be largely dependent on the kind of characters you want to build. That said, I recommend checking these sites out for free / cheap, good quality 3D models:

I'll be using the POLYGON Modular Fantasy Heroes pack I downloaded from the Unity Asset Store awhile back for another project. It's a good fit because for this because:

  • Modular components
  • Same theme / aesthetic
  • Easy to import into Unity

Once you've got your components picked out, we can continue onto generation.

Generate a Character Collection

At this point, we understand how generation works and we've got all the individual character pieces together that we'd like to build a character collection from. Now we've just got to put them together.

We can do this in a few steps:

  • Randomize the components to use
  • Instantiate them in our scene
  • Place them in world space

Screenshot of my scene setup in Unity Editor

In Unity, I created a basic scene with:

  • Lighting and background
  • An empty GameObject named GenerativeCharacter
    • Child GameObjects for each "component type" (like Head, Hair)
      • All individual components are stored based on its component type

Then the code itself:

  • Loads the GameObjects into the generator (line 40)
  • Generates the characters (line 48)
    • Activates the chosen component (to make it visible)
    • Sets its position
    • Set its color

I've provided my full C# source code below:

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class GenerativeCharacterCollection : MonoBehaviour
{
    // Colors
    private List<Color> Colors = new List<Color> {
        new Color32(0, 0, 0, 255), // black
        new Color32(255, 255, 255, 255), // white
        new Color32(188, 0, 45, 255), // red
    }; 

    // Positions
    [SerializeField]
    private Vector3 EyeBrowPositionRight { get; set; } = new Vector3(-0.07f, 0.1f, 0.16f);

    [SerializeField]
    private Vector3 EyeBrowSize { get; set; } = new Vector3(0.005f, 0.005f, 0.005f);

    [SerializeField]
    private Vector3 HeadCoveringPosition { get; set; } = new Vector3(0, 0.25f, 0);

    [SerializeField]
    private Vector3 HeadPosition { get; set; } = new Vector3(0, 0, 0);

    // Component Lists
    private List<GameObject> AllEyeBrows;
    private const string AllEyeBrowsObjectName = "AllEyeBrows";

    private List<GameObject> AllHeadCoverings;
    private const string AllHeadCoveringsObjectName = "AllHeadCoverings";

    private List<GameObject> AllHeads;
    private const string AllHeadsObjectName = "AllHeads";

    private System.Random _random; 

    void Awake() {
        this._random = new System.Random();
        BuildComponentLists();
    }

    // Start is called before the first frame update
    void Start()
    {
        GenerateCharacter();
        Debug.Log("Generate Character");
    }

    private void GenerateCharacter()
    {
        GenerateHead();
        GenerateHeadCovering();
        GenerateEyeBrows();
    }

    private void GenerateEyeBrows()
    {
        // Right eyebrow
        GenerateEyeBrowHelper(
            EyeBrowPositionRight,
            new Vector3(0, 0, 0) // no rotation
        );
        
        // Left eyebrow
        GenerateEyeBrowHelper(
            new Vector3(
                EyeBrowPositionRight.x * -1,
                EyeBrowPositionRight.y,
                EyeBrowPositionRight.z
            ), // mirror horizontal
            new Vector3(0, 180, 0) // 180 horizontal
        );
    }

    private void GenerateEyeBrowHelper(
        Vector3 position,
        Vector3 rotation) {
        var randomEyeBrow = this.AllEyeBrows[
            this._random.Next(this.AllEyeBrows.Count)
        ];
        randomEyeBrow.transform.localPosition = position;
        randomEyeBrow.transform.localScale = this.EyeBrowSize;
        randomEyeBrow.transform.Rotate(
            rotation,
            Space.Self
        );
        randomEyeBrow.SetActive(true);
        SetColorOnGameObject(
            randomEyeBrow,
            GetRandomColor()
        );
    }

    private void GenerateHead()
    {
        var randomIndex = this._random.Next(this.AllHeads.Count);
        var randomHead = this.AllHeads[
            randomIndex
        ];
        randomHead.transform.localPosition = HeadPosition;
        randomHead.SetActive(true);
        SetColorOnGameObject(
            randomHead,
            GetRandomColor()
        );
    }

    private void GenerateHeadCovering()
    {
        var randomHeadCovering = this.AllHeadCoverings[
            this._random.Next(this.AllHeadCoverings.Count)
        ];
        randomHeadCovering.transform.localPosition = HeadCoveringPosition;
        randomHeadCovering.SetActive(true);
        SetColorOnGameObject(
            randomHeadCovering,
            GetRandomColor()
        );
    }

    private Color GetRandomColor()
    {
        var randomIndex =  this._random.Next(
                this.Colors.Count
            );
        return this.Colors[
           randomIndex
        ];
    }

    private void SetColorOnGameObject(
        GameObject go,
        Color color
    )
    {
        var goRenderer = go.GetComponent<Renderer>();
        goRenderer.material.SetColor("_Color", color);
    }

    private void BuildComponentLists()
    {
        var headsFolder = GameObject.Find(AllHeadsObjectName);
        this.AllHeads = GetAllChildrenGameObjects(headsFolder);

        var headCoveringsFolder = GameObject.Find(AllHeadCoveringsObjectName);
        this.AllHeadCoverings = GetAllChildrenGameObjects(headCoveringsFolder);

        var allEyeBrowsFolder = GameObject.Find(AllEyeBrowsObjectName);
        this.AllEyeBrows = GetAllChildrenGameObjects(allEyeBrowsFolder);
    }

    private List<GameObject> GetAllChildrenGameObjects(GameObject targetRoot)
    {
        return targetRoot.transform
            .GetComponentsInChildren<Transform>(includeInactive: true)
            .Select(t => t.gameObject)
            .Where(g => g != targetRoot)
            .ToList();
    }
}

That's how I generate 3D character collections in Unity!

For more on Unity, checkout:

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.