I’ve just pushed to github the vast set of changes to UnitySteer, which completely re-organizes the way that vehicles are created.
So what changed?
If you used UnitySteer before, you know it was pretty much a straight port of OpenSteer. This means that we had:
- A base Vehicle class from OpenSteer, providing a kitchen-sink of interfaces that other vehicles might need, and duplicated a lot of properties from Unity’s Transform and Ridigbody;
- A SteerLibrary class that descended from it, and provided some general functions, such as how to steer to align with neighbors or avoid obstacles;
- A SimpleVehicle that was itself a SteerLibrary descendent and used those methods to provide some more vehicle-oriented functionality, such as actually applying the steering forces;
- The actual honest-to-Cthulhu vehicles, which wrapped SimpleVehicle and used what methods they needed to actually provide a behavior; and as if that wasn’t enough
- A MonoBehaviour that wrapped the vehicles, and which you actually dropped in a GameObject to make it navigate your scene.
In short, a huge monolith of code where the interactions between the methods weren’t immediately obvious, even after the efforts of cleaning up that I’d put into it. It acted as a huge show-stopper for people attempting to work with the codebase, as it brought a steep learning curve before people felt comfortable with providing their own steering modifications.
During this recent restructure, I’ve separated the code on specific MonoBehaviours for each function we wish to provide. This means that vehicles are now composed of:
- One or more Steering behaviors, which provide their own inputs on where the agent should move.
- A Vehicle behavior, which aggregates these behaviors and moves the agent in the scene.
- Possibly a Radar behavior, which helps the vehicle detect nearby obstacles and neighbors.
That’s it. Each Steering behavior contains just the code it needs to calculate its own force, and gets invoked by the Vehicle when necessary.
Let’s look at an example. Suppose that you want to build the perennial boid example, which is comprised of three basic steering forces:
- Steer to align yourself with your neighbors,
- Steer to keep a distance from your neighbors, and
- Steer for cohesion with your neighbors.
Instead of having to write this MonoBehaviour in order to wrap this class, which in turn inherits from the aforementioned monoliths, you would simply use the provided steering behaviors and drop them into your agent, ending up with something like this:
Each class fully isolates its own logic, and if we need to provide new steering behaviors, we just write a new, short class for them (for instance, to steer to remain tethered to a position).
Another advantage of this approach is that it makes it immediately obvious how much force each behavior is providing for the vehicle. In this case, you can see that the tethering behavior only fires up when we get too far away from the designated point, and that the behavior to avoid obstacles contributes only when we’re about to crash into one.
In the process, I’ve also cleaned up some unused methods, quarantined classes that I’m not sure if we’ll keep around, and documented the code some more. I still have some changes that I wish to implement, such as having better local obstacle avoidance and more test case scenes – these will be added in the coming weeks.
If you have any questions, please post them on Answers.Unity3d.com, which will make them easier to find for others as well.
Many thanks to Laurent Lavigne for providing the suggestions and nudges necessary to convince me that this change was worth the effort.