THE DOOM THAT CAME TO PETROV, PT. 2

FULLY TORQUED

As I approached the end of my endless fiddling with wheel curve settings, I kept confronting the same problem: a given set of values would work great at low speeds, but at higher speeds the car would flip or otherwise behave erratically. I eventually realized that this was simply the car behaving correctly. If the player was holding down the gas pedal, my script instructed the car to continue to apply more torque to each wheel, up to an arbitrary maximum. In essence, I was modelling a car that could do 0 to 70, all in first gear, which in the real world would probably result in fires and mayhem, so a little flipping doesn’t seem as bad in context.

What I needed was a way to apply less torque as the car got faster, and more torque when it was slower. This is basically a transmission. I suspected that modeling one in any kind of accurate way, with my limited automotive knowledge, was a fool’s game, and apparently that view was shared by Unity’s example content implementers, as evinced by this snippet from the Standard Assets car controller:

private void CalculateRevs()
{
    // calculate engine revs (for display / sound)
    // (this is done in retrospect - revs are not used in force/power calculations)
    […]
}

Apparently this was a kettle of fish someone didn’t feel like getting into, and who could blame them? Have a look at the top comment on this reddit post about simulating a transmission; it’s not the kind of thing you’d whip up in an afternoon. Fortunately, I had no desire to approach that level of accuracy. I just wanted to turn down the amount of torque available to the wheels at higher speeds, in a way that felt “stepped” and somewhat plausible. Here’s what I ended up with:

[System.Serializable]
public class Gear
{
    public float minSpeed;
    public float maxSpeed;
    public float torqueRatio;
    public float accelMod;

    public Gear(float min, float max, float ratio, float accMod)
    {
    minSpeed = min;
    maxSpeed = max;
    torqueRatio = ratio;
    accelMod = accMod;
    }
}

public class SimpleGears : MonoBehaviour {

    SimpleCarController driveScript;
    int numGears = 5;
    public int currentGear = 1;
    float fwdSpeed;
    public Gear[] gearBox;

    // Use this for initialization
    void Start () {
    driveScript = GetComponent<SimpleCarController>();
    gearBox = new Gear[numGears];

    gearBox[0] = new Gear(0, 15, 1f, 1f);
    gearBox[1] = new Gear(10, 25, 0.4f, 0.15f);
    gearBox[2] = new Gear(20, 40, 0.375f, 0.135f);
    gearBox[3] = new Gear(35, 55, 0.3f, 0.12f);
    gearBox[4] = new Gear(45, 70, 0.2f, 0.075f);
    }

    // Update is called once per frame
    void Update () {

    fwdSpeed = driveScript.fwdSpeed;
    if (fwdSpeed > gearBox[currentGear - 1].maxSpeed && currentGear < numGears)
    {
    // print("Shift up!");
    currentGear++;
    //driveScript.motor = 0;
    //rather than drop to zero, let's drop to fifth max
    driveScript.motor = driveScript.currentMaxMotorTorque / 5;

    }
    if (fwdSpeed < gearBox[currentGear - 1].minSpeed && currentGear > 1)
    {
    //print("Shift down!");
    currentGear--;
    }

    driveScript.currentMaxMotorTorque = driveScript.initMaxMotorTorque * gearBox[currentGear - 1].torqueRatio;
    driveScript.currentAccelFactor = driveScript.initAccelFactor * gearBox[currentGear - 1].accelMod;
    }
}

The numbers in the gearbox are just magic values arrived at over hours of tinkering, and having a nice RPM readout onscreen helped with this immensely. The whole thing is, of course, a cheat. The speed it’s reading from the car controller script is just the car rigidbody’s velocity along the Z axis, which was a bit of a neat trick in itself:

//SPEED
localVeloVector = transform.InverseTransformDirection(rigidbody.velocity);
fwdSpeed = localVeloVector.z;

Regardless, the shifting of gears is in no way dependent on engine rotations, simulated or otherwise. I could have tried taking rotation measurements from the wheels, but of course they still slip sometimes and figuring out how to account backwards to get the engine rotation from the wheels while managing slip… just, no thanks. The end result is just a series of numerical clamps so that I can boost acceleration at lower speeds and limit torque at high speeds. It allows me to have a significant difference between low and high speed, but still have that “fast out of the gate” response at slow speeds that one would expect from a muscle car, and at the high end produce a feeling of “woah, this is kind of hectic”, as other objects in the scene fly past. I’m not going to say it turned out great, but it turned out good enough that in order to improve it I’d probably want to start over.

My dream of “pure wheel behavior” evaporated in these final tunings, as I added various sweetening measures to the control script. I managed to avoid the Thumb Of God downforce, but I did end up liking the results of increasing drag at higher speeds:

rigidbody.drag = initDrag + (speedFrac * dragFactor);

This is really about the same as the Thumb, but it does cloak itself as a stroke of physics-based realism, which is appealing. Similarly, a lot of Unity forum threads and examples mention the idea of dynamically adjusting the vehicle’s center of mass based on speed, which is I guess something that happens in the real world. I messed with this a bunch but never found any settings where I could be sure things had improved. Finally, I upped the amount of brake torque to apply automatically whenever the player lets off the gas, so the car doesn’t coast to a stop for half a mile. This was around about the time I realized you can’t drive the car backwards, but I had to put my foot down: reverse gear is coming in a patch. Maybe.

SMOKE BREAK

There are a lot of fun visual effect helpers in Unity that I have been meaning to explore forever, and this wasn’t really the project for that, but I did get to have some fun messing with the TrailRenderer component, which I was using to create skid effects. Unfortunately, the script wasn’t giving me good results, but that’s a great thing about Unity: if you’re having a problem, it’s a good bet someone out there has hand-crafted a solution. In my case, that of wanting a trail that could be stopped and started arbitrarily, wiki contributor Yoggy’s TimeBasedTrailRenderer was the answer. Another great thing about Unity: if you don’t like how a provided class is implemented, in most cases you can rewrite it to do what you need.

At this point, as I was getting into skids, I started looking closely at the scenery, grass, trees and buildings, and figured I could easily make some small improvements. I updated the shaders to Unity 5 standards, and tried some bump mapping options on the trusty Catamount mesh. I realized a lot of the other scenery objects were using legacy shaders as well, so I updated those and various things became slightly glossier. Around that time I had this fantastic idea that maybe I could improve performance by baking the lights. This was a mistake.

I will definitely make a future post about lights in Unity, there’s a lot to learn and a lot of decisions to make if you choose to turn away from the standard real-time lighting. I’m sure baked lighting is critical to many projects, and I’m sure I could get another few frames a second if I built the project to take advantage of it, but in my case all it did was thrash my hard drive mercilessly for about twenty minutes, then turn all my trees black.

I never did get to the root cause of this, despite the cornucopia of suggestions online (apparently terrain trees and lights are a Known Issue) such as: shader values, tree brush values, re-import the mesh with different UV options, directional light settings, putting the shader in a special folder, and recreating the terrain as a mesh inside a prefab and then lighting that (OK that one is actually a special kind of genius). I spent just enough time on this that I realized I was spending way too much time on this. Realtime lighting it would be.

The dust emitted when the player leaves the road is mostly just a matter of calling .Play and .Stop on a particle system, but creating “brown smoke” required some thought. The default “soft additive” particle shader doesn’t like trying to render opaque things, dark colors just make it shrug and fade out of existence, or sometimes do this mystical space gas thing:

It has a certain charm but it’s a little too steamy. Alpha Blended Particles turned out to be the shader type most capable of simulating a cloud of stirred-up dust and dirt.

After some tedious UI trials I have forgotten the details of, I had this incarnation of Petrov sewn up and was ready to press a WebGL build. The process was simple and the initial index.html page worked great. I did have some pangs and pains getting wordpress to play nice with the new player. Now, the details of this are a  bit hazy as I am most definitely Not A Web Dev and don’t comprehend much of this, but: the new webplayer accesses the various game files in the format .jsgz, which is a compressed JavaScript file that the player is somehow able to “serve” in chunks to the client browser, almost like a mounted .iso or one of those old plastic circles they used to drag a needle around at parties. It’s elegant, and explains some of how Unity is getting viable perf out of an HTML5 Canvas, a piece of tech that promised to be the answer to browser games sometime around 2010, but is only lately starting to filter into the mainstream web.

WordPress choked on my initial setup because it didn’t understand how to mount those compressed .js files as archives, and was trying to just straight up read them like so many term papers, and this was just not tenable. Fortunately, some posts and threads pointed me back once again to the invaluable docs, where Unity has provided a helpful template file that you can paste into the .htaccess file in the root folder of your website, which will instruct it how to properly host a WebGL Unity game.

The last hurdle was hurdled. What did we learn? Well, despite conventional wisdom, updating your engine mid-stream can be worthwhile, if the gain of the new engine features outweighs the cost in time and resources of retrofitting. That’s always going to be a judgement call, and I can just as easily imagine getting a game to a point where it’s not practical to jump forward. It’s always worth considering, as the engine devs’ primary goal is to make our lives easier (even though that goal is sometimes realized in a roundabout way).

COMING UP

You make one project look better, and now the other one looks worse. Time to start taking humanoid animation more seriously.