Procedural Character Generation

By Patrick Duijster

This project was about how I managed to make a decent Character Generator in 4 weeks and all of it being generated through code and not using a existing model.
The goal was to have a character generator that anyone could quickly use to select what they wanted on the model and with what scale and afterwards export it to their desired format.

A rough version of my goal

Table of contents

  1. The Concept
  2. Vertices
  3. Scaling
  4. Triangles
  5. Conclusion/Results
  6. Future
  7. Sources

The Concept

This idea sounds great on paper: A character Generator from only code with a lot of freedom in customization.
By having everything generated through code the idea was that it would be really easy to make varied and complex characters because of it.
But as it turned out it wasn’t that easy because everything that you wanted to add had to be made in a for loop to allow for easier modifications so in the end it would actually take quite a while to get things like arms and legs in properly while also maintaining somewhat clean code.
While at the same time an already existing model could’ve allowed for quicker editing and would’ve allowed for more customization.

Vertices

The vertices of the standard model

To generate round objects like the arms, legs and head I’ve used the torus calculation which is in simple terms calculating circles in a circle to create a donut like shape.
For the body and feet I’ve used an if statement to create a pattern that I can easily use with triangles later on.

So in this code snippet it can be seen that I’m generating the left arm vertices by calculating a circle relative to the amount of vertices I want on a single layer and then multiplying that circle by the amount of circles I want to form the arm with.
For this to properly work I also have to put one vertice less on each ring otherwise the last one will be created on the starting position and that would happen on each layer.
Having these duplicate vertices earlier on meant my normals were broken and I had to instantiate more vertices to account for these duplicates.

#region LEFT ARM VERTICES
        float vStep = (2f * Mathf.PI) / vertsPerLayer;
        float uStep = ringDistance / curveRadius;
        // 3 armlengths + 10 verts will mean 40 vertices or 4 rings
        armLVertices = new Vector3[vertsPerLayer * (layers + 1)];
        for (int k = 0, j = 0; j <= layers; j++)
        {
            for (int i = 0; i <= vertsPerLayer - 1; i++, k++)
            {
                Vector3 p;
                float r = (curveRadius + pipeRadius * Mathf.Cos(i * vStep));
                p.= (r * Mathf.Sin(j * uStep)) + armsDistance;
                p.= (r * Mathf.Cos(j * uStep)) + (bodyLength * 2f- bodyOffset;
                p.= pipeRadius * Mathf.Sin(i * vStep);
                var pos = p;
                armLVertices[k] = pos;
            }
        }
        #endregion

For feet and the body the if statement is just a single check to see if it’s halfway through the layer.
If it’s not halfway done the vertices are added additive and if it is the vertices are added subtractive. This is done to create a similar round pattern so that setting up the triangles can be done easily.

#region LEFT FOOT VERTICES
        footLVertices = new Vector3[((xSize + 1* (zSize + 1)) * ySize];
        for (int i = 0, y = 0; y < ySize; y++)
        {
            for (int z = 0; z <= zSize; z++)
            {
                for (int x = 0; x <= xSize; x++, i++)
                {
                    if (z <= zSize * 0.5f)
                    {
                        if (i >= footLVertices.Length - (zSize + 1)) { footLVertices[i] = new Vector3(
                            (((x + 0.5f/ y) - legDistance) - (xSize * 0.5f), z - (heightOffset * 2f+ offset, y);
                        } else { footLVertices[i] = new Vector3((x - legDistance) - (xSize * 0.5f), z - (heightOffset * 2f+ offset, y); }
                    }
                    else
                    {
                        if (i >= footLVertices.Length - (zSize + 1)) { footLVertices[i] = new Vector3(
                            ((((xSize - x) + 0.5f/ y) - legDistance) - (xSize * 0.5f), z - (heightOffset * 2f+ offset, y);
                        } else { footLVertices[i] = new Vector3(((xSize - x) - legDistance) - (xSize * 0.5f), z - (heightOffset * 2f+ offset, y); }
                    }
                }
            }
        }
        #endregion

To get all of these vertices with their respective patterns into one array we simply divide each pattern into their own array and when all of them are created we add them together into a new single array.

Vector3[] combinedVertices = legLVertices.Concat(footLVertices).Concat(legRVertices).Concat(footRVertices).ToArray();
    me.vertices = combinedVertices;

Scaling

The scaling was done by applying simple multipliers to the vertices where it’s needed like both of the arrays for the arms to have it scale with the body.

Triangles

Because of the way I instantiated my vertices it became quite easy to create the triangles because all I had to do was to loop through most of them with the same code.

This is a snippet of the triangles code for the left arm, the only difference that I had to make in this snippet of code for other parts was when it was flipped like with the right and left arm.
(which is shown in the notes I made below)
And also when it reached the end because I wasn’t using a duplicate vertice I had to use an if statement to make it loop to the original point instead of going up a layer.

#region LEFT ARM TRIANGLES
for (int ti = armTiLSize, vi = armViLSize, z = 0; z < layers; z++, vi++)
{
    for (int x = 0; x < vertsPerLayer; x++, ti += 6)
    {
        if (x < vertsPerLayer - 1)
        {
            triangles[ti] = vi;
            triangles[ti + 4= triangles[ti + 1= vi + 1;
            triangles[ti + 3= triangles[ti + 2= vi + vertsPerLayer;
            triangles[ti + 5= vi + vertsPerLayer + 1;
            vi++;
        }
        else
        {
            triangles[ti] = vi;
            triangles[ti + 1= vi - vertsPerLayer + 1;
            triangles[ti + 2= vi + vertsPerLayer;
            triangles[ti + 3= vi + vertsPerLayer;
            triangles[ti + 4= vi - vertsPerLayer + 1;
            triangles[ti + 5= vi + 1;
        }
    }
}
#endregion

One key difference between adding different vertices together and adding different triangles together is that instead of being able to generate a new array with the vertices.
I had to account for the fact that the vertices are in a different part of a bigger array so the triangles were generated in order of how I’ve added the vertices to a bigger array which also meant manually adding a stepsize for each triangle loop and vertice loop so it addressed the correct triangles and vertices.
(Sounds complicated but it was just dumb as seen below in the stepsizes that I made for the different triangle loops)

int footLTriangleStart = (vertsPerLayer * layers * 6);
 int footLVerticeStart = vertsPerLayer * (layers + 1);
 int legRTriangleStart = (vertsPerLayer * layers * 6+ ((layerSize * 6 * (ySize - 1)) + 6);
 int legRVerticeStart = (vertsPerLayer * (layers + 1)) + (((xSize + 1* (zSize + 1)) * ySize);
 int footRTriangleStart = (vertsPerLayer * layers * 6+ ((layerSize * 6 * (ySize - 1)) + 6+ (vertsPerLayer * layers * 6);
 int footRVerticeStart = (vertsPerLayer * (layers + 1)) + (((xSize + 1* (zSize + 1)) * ySize) + (vertsPerLayer * (layers + 1));
 int[] triangles = new int[(vertsPerLayer * layers * 6+ ((layerSize * 6 * (ySize - 1)) + 6+ (vertsPerLayer * layers * 6+ ((layerSize * 6 * (ySize - 1)) + 6)];

Conclusion/Results

With all of my experience from these past 4 weeks I can safely say that I can create a bunch of meshes through code but that I probably shouldn’t if I can just make the mesh in any editor like blender.
Because the results from 4 weeks don’t show much promise when it comes to making anything beyond basic shapes or manipulating existing meshes.
So I’d say that for making like a race track this could be fine.(still not the best but it’s something) And for anything beyond that it’s not worth the time and computing power to do it.
https://github.com/IFlippie/CharacterCreator

Future

In the future I’d like to finish my original goal of being able to export these code generated meshes, and beyond that improve the experience by adding uv’s or solid colors to make it more presentable.
But besides finishing up this project I feel like taking up the advice that was given to me halfway through this project which was to take an existing model and edit that instead of generating and editing everything through code.

Sources

Flick, J. (n.d.). procedural-grid. Retrieved from catlikecoding: https://catlikecoding.com/unity/tutorials/procedural-grid/

Related Posts