A Game about Gears

This text is a written version of the video above, with future plans to incorporate graphics and figures for illustration.
As I'm working my way through the backlog of videos that were published before this website, I suggest reading the article alongside the video for a comprehensive experience.

I have a bone to pick with gears in video games: - Some games use it for non-gameplay decorations to emphasize a mechanical or steampunk environment - Some use mechanisms as part of their gameplay but using gears as simple keys - Some… aren’t even trying

Anyway, I’ve yet to find a game that makes use of gears as puzzle elements. I think picking what gears to put where inside a mechanism could be very fun. If you know of any game like this, please let me know in the comments of the youtube video.

What’s so interesting about gears is that the number of cogs (or teeth) on a pair of wheels will have an impact on speed.

If you have a gear that spins really fast, and you add a second gear with twice the number of teeth, the speed will be divided by two (and incidentally, torque will be doubled).

Today we’ll see if we can make a small puzzle game about gears, and if it’s any fun to play. I’m Leonard and welcome to Useless Game Dev.

Physics Engine Approach

Spoilers: it didn’t work

As you can see in the video, using Unity's physics engine didn't work because the more gears you add, the more complex the mechanism becomes, and it quickly becomes too much for the engine.
And we're not even talking about using proper gear shapes yet.

So we're going to have to code our own gear train system.

Coding Approach

Generation

The first thing we need to do is to generate gears based on their number of teeth. That’s simple enough.

private void Generate()
{
    float step = 360f / (float)m_cogs;
    float radius = m_cogs / m_radiusFactor; // Magic factor
    // Instantiate cogs
    for (int i = 0; i < m_cogs; i++)
    {
        var cog = Instantiate(m_cog, transform);
        cog.transform.rotation = Quaternion.Euler(0, 0, step * i);
        cog.transform.position += cog.transform.up * radius;
    }

    // Fill body
    float bodyScale = (radius * 1.2f) * m_bodyScale;
    var body = Instantiate(m_body, transform);
    body.transform.localScale = new Vector3(bodyScale, 0.5f, bodyScale);
}

Transmission

So let’s say we have multiple gears. Each gear has a list of the neighboring gears it’s connected to. And one or more of those gears is a driving gear, which means it has a manually set speed and is not driven by its neighbors.

We’ll then start from the driving gear, and recursively propagate the movement for every gear in the chain.

Unless it’s a driving gear, the speed of a gear is equal to the speed of the parent, multiplied by the ratio of teeth in both gears.

Oh and obviously the direction should be inverted.

private void Propagate(Gear child, Gear parent, List<Gear> chain)
{
    // Add this Gear to the list of known gears to avoid infinite recursion
    chain.Add(child);

    // Read speed/torque values from parent
    float speed = parent.ActualSpeed;
    float torque = parent.Torque;

    {


        float reduction = -((float)parentGear.Cogs / (float)childGear.Cogs);
        reduction *= -1;

        // Compute speed/torque for the child gear
        speed *= reduction;
        torque /= reduction;
    }

    {
        // Apply speed/torque
        child.SetForFrame(speed, torque);

        // Propagate to neighbours
        foreach (var n in gear.Neighbours)
        {
            // Avoid infinite recursion by checking if this node was visited
            if (chain.Contains(n))
            {
                continue;
            }

            Propagate(n, child, chain); // Recursively apply to every child
        }
    }
    chain.Remove(child);
}

Blocked

Sometimes, despite your best efforts, some mechanisms are not going to work. Because speeds or directions won’t match, we need to detect when a chain of gears is blocked.

To do this, before setting the speed of a gear down the chain, we’ll check whether its speed is already set.

If it’s set, to a value that is significantly different than what we’re expecting it to be, this means we’re in a bad chain, and we need to recursively propagate the news that these cogs won’t turn.

if(child.setThisFrame)
{
    if (SignificantlyDifferent(child.Speed, speed)) // If the child's speed is significantly different than what we expect, the chain is blocked
    {
        Debug.Log($"{child.name} blocked");
        PropagateBlock(child, new List<Gear>());
    }
}

Joints

There’s a second way speed can be transmitted, and that’s through joints. When elements are joined together, they have the same speed and same direction. So our recursive function should reflect this also.

foreach (var j in child.Joints)
{
    if (chain.Contains(j))
    {
        continue;
    }

    // propagate without applying any speed/torque

    Propagate(j, child, chain, true);
}

Puzzles

After a little bit of refactoring I now have a Gear and a Shaft class, which after the surprisingly painful development of a joint system can now dock together, and that’s cool because we’re now able to make multi-stage mechanisms.

That’s where the real fun begins really, because we can take a super fast wheel and add a ton of reduction to the speed. If you want to see what that looks like in real life I suggest heading over to the Brick Experiment Channel and watch them create a GOOGOL:1 reduction ratio with lego gears.

Anyway, I made a few puzzles, where you need to pick the right gears to transmit movement to another gear and open a door. Then I moved on to more complex puzzles where doors have a minimum torque requirement, to force the player to think about what gears they use.

And since it worked ok-enough, I decided to actually build this into a working game. It’s called Clockwise Temple and it’s available in the browser on itch.io. You can play it right now if you want. It’s not a ton of fun because it gets repetitive very fast and placing gears can get finicky, but the last levels with multi-stage puzzles are somewhat cool.

Have a good one!

Music Credits