C#/ Unity – Building a 2D Genetic Algorithm

So this article will be a little different to most of what’s on this site, but the benefit to writing your own blog is you’re not limited on what you can post so here goes!

Today I’ll be demonstrating a 2D genetic algorithm using some fun little aliens, this will basically represent how genetics can diverge over generations, in this case selecting for the reddest coloured alien. I’ll also include how to introduce mutations over several generations.

This article is based on “How to build a genetic algorithm” by Wael Dimassi, which provided a good basis for this work.

This won’t cover any Unity basics as is more focused on the coding aspect.

What we’ll be doing

When we’re finished we’ll have something that starts off like this in generation 1 –

Generation 1 – Showing randomly colored aliens due to randomly chosen genetics

Which will over several generations tend towards converging on to a very similar colour pallet, in this case due to a mutation chance of 10% (Which interestingly is exceedingly high compared to humans, which show a mutation rate of roughly 0.00000003%) –

Generation 13 – Showing the aliens converging to very similar colours due to a low mutation rate and us selecting for in this case the most dominant blue gene.

The model we’re making is extremely simple compared to reality, in our world our little aliens will only have 3 genes selecting for red, green and blue which make up what colour they can be and they’ll inherit their colour based on their parents.

Starting off

So the first step is to create a new project in Unity and set up the following scripts –

DNA

The DNA script is very simple and just contains public variables we’ll use to track a creatures genetic makeup –

using UnityEngine;

public class DNA : MonoBehaviour
{
    // Holds the Red, Green and Blue values;
    public float r;
    public float g;
    public float b;

}

CreatureMover

This script will just handle a very basic system for the aliens walking around during their lifetime, it doesn’t affect the genetic algorithm in any way. It’s purely here to make it a little more interesting to look at.

using UnityEngine;

public class CreatureMover : MonoBehaviour
{
    public float maxSpeed = 3f; // The maxium speed an alien can reach
    public float accelerationTime = 2f;

    private Rigidbody2D rigidBody;
    private float timeBetweenChangeDirection;
    private Vector2 movement;

    // Start is called before the first frame update
    void Start()
    {
        rigidBody = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update()
    {
        // Will set a new direction for the alien to walk in every frame
        timeBetweenChangeDirection -= Time.deltaTime;
        if(timeBetweenChangeDirection <= 0)
        {
            movement = new Vector2(Random.Range(-1f, 1f), Random.Range(-1f, 1f));
            timeBetweenChangeDirection += accelerationTime;
        }
    }

    // If the alien collides with something then walk in the opposite direction
    void OnCollissionEnter2D(Collision2D col)
    {
        movement = -movement;
    }

    // Make the alien walk
    void FixedUpdate()
    {
        rigidBody.AddForce(movement * maxSpeed);
    }
}

Controller

The Controller script will be attached to an empty GameObject and this will handle setting up of the initial population, ongoing breeding, updating the text showing what generation we’re on and the remaining lifetime of the current population.

For this script to work you will need to set up the following –

  • An alien PreFab –
    • Sprite Renderer with any image you want for the aliens
    • Attach the DNA script, we don’t need to set any initial values
    • Attach a RigidBody2D with no gravity and freeze the Z axis rotation
    • Attach a Box Collider 2D.
    • Attach the CreatureMover script to allow the aliens to walk around.
  • An empty GameObject for which you’ll attach the Controller script to
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class Controller : MonoBehaviour
{
    public int populationSize = 50;
    public GameObject creaturePrefab;
    public List<GameObject> population;
    public float populationLifetime = 5.0f;
    public int mutationRate; // Chance of a mutation occuring, 0 - 100%

    public Text generationText;
    private int currentGeneration = 1;
    public Text timeText;
    private float lifetimeLeft;

    // Start is called before the first frame update
    void Start()
    {
        // Initialise a random starting population
        InitialisePopulation();

        // For each population Lifetime, breed a new generation, this will repeat indefinitely
        InvokeRepeating("BreedPopulation", populationLifetime, populationLifetime);

        // Set the value so we can show a countdown for each population
        lifetimeLeft = populationLifetime;
    }

    // Update is called once per frame
    void Update()
    {
        // Update the text showing what generation we're on
        generationText.text = "Generation " + currentGeneration;

        // Perform a countdown, showing the lifetime of the current population, reset on breeding
        lifetimeLeft -= Time.deltaTime;
        timeText.text = "Time " + (lifetimeLeft).ToString("0");
    }

    /**
     * Initialises the population
     */
    private void InitialisePopulation()
    {
        for (int i = 0; i < populationSize; i++)
        {
            // Choose a random position for the creature to appear
            Vector2 pos = new Vector2(Random.Range(-9, 9), Random.Range(-4.5f, 4.5f));

            // Instantiate a new creature
            GameObject creature = Instantiate(creaturePrefab, pos, Quaternion.identity);

            // Set the colour of the creature
            DNA creatureDNA = creature.GetComponent<DNA>();
            creatureDNA.r = Random.Range(0.0f, 1.0f);
            creatureDNA.g = Random.Range(0.0f, 1.0f);
            creatureDNA.b = Random.Range(0.0f, 1.0f);

            creature.GetComponent<SpriteRenderer>().color = new Color(creatureDNA.r, creatureDNA.g, creatureDNA.b);

            // Add the creature to the population
            population.Add(creature);
        }
    }

    private void BreedPopulation()
    {
        List<GameObject> newPopulation = new List<GameObject>();

        // Remove unfit individuals, by sorting the list by the most red creatures first
        List<GameObject> sortedList = population.OrderByDescending(o => o.GetComponent<DNA>().r).ToList();

        population.Clear();

        // then breeding only the most red creatures
        int halfOfPopulation = (int)(sortedList.Count / 2.0f);
        for (int i = halfOfPopulation - 1; i < sortedList.Count - 1; i++)
        {
            // Breed two creatures
            population.Add(Breed(sortedList[i], sortedList[i + 1]));
            population.Add(Breed(sortedList[i+1], sortedList[i]));

        }

        // Then destroy all of the original creatures
        for(int i = 0; i < sortedList.Count; i++)
        {
            Destroy(sortedList[i]);
        }

        lifetimeLeft = populationLifetime;
        currentGeneration++;
    }

    // Breeds a new creature using the DNA of the two parents
    private GameObject Breed(GameObject parent1, GameObject parent2)
    {
        Vector2 pos = new Vector2(Random.Range(-9, 9), Random.Range(-4.5f, 4.5f));

        // Create a new creature and get a reference to its DNA
        GameObject offspring = Instantiate(creaturePrefab, pos, Quaternion.identity);
        DNA offspringDNA = offspring.GetComponent<DNA>();

        // Get the parents DNA
        DNA dna1 = parent1.GetComponent<DNA>();
        DNA dna2 = parent2.GetComponent<DNA>();
        
        // Get a mix of the parents DNA majority of the time, dependant on mutation chance
        if (mutationRate <= Random.Range(0, 100))
        {
            // Pick a range between 0 - 10, if it's less than 5 then pick parent1's DNA, otherwise pick parent 2's
            offspringDNA.r = Random.Range(0, 10) < 5 ? dna1.r : dna2.r;
            offspringDNA.g = Random.Range(0, 10) < 5 ? dna1.g : dna2.g;
            offspringDNA.b = Random.Range(0, 10) < 5 ? dna1.b : dna2.b;
        }
        else
        {
            int random = Random.Range(0, 3);
            if (random == 0)
            {
                offspringDNA.r = Random.Range(0.0f, 1.0f);
                offspringDNA.g = Random.Range(0, 10) < 5 ? dna1.g : dna2.g;
                offspringDNA.b = Random.Range(0, 10) < 5 ? dna1.b : dna2.b;
            }
            else if (random == 1)
            {
                offspringDNA.r = Random.Range(0, 10) < 5 ? dna1.r : dna2.r;
                offspringDNA.g = Random.Range(0.0f, 1.0f);
                offspringDNA.b = Random.Range(0, 10) < 5 ? dna1.b : dna2.b;
            }
            else
            {
                offspringDNA.r = Random.Range(0, 10) < 5 ? dna1.r : dna2.r;
                offspringDNA.g = Random.Range(0, 10) < 5 ? dna1.g : dna2.g;
                offspringDNA.b = Random.Range(0.0f, 1.0f);
            }
        }

        offspring.GetComponent<SpriteRenderer>().color = new Color(offspringDNA.r, offspringDNA.g, offspringDNA.b);

        return offspring;
    }
}

Once you’ve got all of this and it’s hooked up correctly you’ll also need to add 2 new Text objects that will contain the text for the current generation and the populations remaining lifetime.

For my example below I added some free sprites I found online for the alien prefab and also added a basic tiled background and some walls just outside the viewable area which due to the RigidBody2D and Box Collider 2D means the aliens won’t leave the screen.

Generation 11 – Showing a convergence towards Red

Interesting things to play with

There are a few simple and interesting tests you can do that will affect the outcome over the generations, these can be tweaked by modifying the mutation rate on the Controller object/script and by modifying line 74 of the Controller script which is what we’re selecting for between generations –

0% Mutation, selecting for the red gene –

50% Mutation, selecting for the red gene –

10% Mutation, selecting for the green gene –


This is a very simple model and could quite easily be expanded so that for example only the aliens that reached food had a chance to reproduce, you could include behaviours such as sharing or fighting etc and really this an interesting Sandbox but I’m going to leave it here for the moment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.