4 Hugues Ross - Blog: DFEngine: What's new
Hugues Ross


DFEngine: What's new

As I promised in my post about rewriting DFEngine, here's a look at some of the differences in the new version. Buckle up, this is going to be a long one.

 The Scene Graph

This may be the biggest departure from the old DFEngine, which is why I put it at the top. A scene graph is a type of structure that treats the scene as a tree of nodes. Like in any tree, nodes can have parents and children. The children are affected by their parent, and usually by their parent's transformation. Each child 'locks' to their parent and moves/rotates/scales along with it. If that was a bit hard to follow, take a look at the diagram below:

Hopefully, that helps explain things a bit. Want your character to hold a sword? Easy, just make it a child of the player's hand. Want them to drop the sword? Undo that, and give it physics. The alternative would be to do tons of math in a script to find where the character's hand is each frame, and warp the sword there. This would be very slow, and much more difficult. Scene graphs solve a ton of problems, from making an object produce light, to the aforementioned equipment issue. My previous system didn't really account for having a scene graph, and my ill-fated attempt to shoehorn one in is what ultimately convinced me to do a rewrite in the first place.

The Event System

Why on earth did I not have one of these before? The closest I had was a few basic script callbacks for specific events, but otherwise I'd have to constantly check for an event or do some 'clever' workaround. Having a system like this also lets objects talk to each other by sending messages.

Better Components

I've been dancing around the component pattern for a long time now, almost 2 years in fact! I usually start by making components handle too much, then too little. I think I'm finally starting to get the balance right, though. In the new engine, components can be constructed and such, and thanks to the event handling code they can work more directly with the game's systems.

Entity Inheritance

This is basically just a cool thing I thought of when I was working. In level files, you can make an entity that depends on another. Essentially, this means that it 'borrows' the other entity's starting values and components, making it a copy. However, anything you put in the new entity's data can override the old. By doing this, you can define some type of object in an entity file, then adjust its behavior in each instance of the object. This avoids all of the "Fun" that I had developing AMAZE, letting me say something on the lines of "Give me a bonus item, but put it here and make it give more points" instead of re-entering all of the bonus item data and modifying the relevant parts(for every single object in the game!).

Less Coupling

Coupling defines how different parts of an application are linked. I should probably mention first off that coupling is not always a bad thing. Coupling is necessary, to some degree, or you application won't be a single whole. However, when there's too much of it, it starts to get in the way. When most of your engine's internals directly connect to each other, it becomes difficult to make major changes to any single part without having to update all the others in turn. If only one or two parts rely on each other, on the other hand, it becomes much simpler to change things around.

Materials and Shaders

When I first started on DFEngine, I had very little graphics programming experience. Now that I've (mostly) taken a class on modern graphics programming, I've got a better handle on what the graphics-related parts of an engine need. If you look at how DFEngine handles things, it pretty much handles a very specific kind of shader, and absolutely nothing else. To be fair, it should work fine for many cases, but it still makes it harder to do things when you want to do something different or unique. The new has materials, which not only define things such as what color an object is, or how shiny it is, but can also handle sending data to their shader. That way, whenever I need to make some kind of unique shader, the process is as simple as making a new material to handle it. I would've had to modify certain parts of the code to make this work properly before, but now I can just extend it instead. In general, it's good practice to extend rather than modify when adding new features, to reduce the risk of breaking other parts in the process.

Smarter Asset Handling

In the old DFEngine, the workflow for making a new object was a little like this:
  1. Before making the object, load the model.
  2. Before making the object, load the texture.
  3. Before making the object, load the shader.
  4. Before making the object, load anything else you need.
  5. Make the object.
  6. Get all the assets you loaded, one at a time.
  7. Add all of the components, one at a time.
Now, here it is in the new DFEngine:
  1. Ask the game to make the object, passing in the name.
Everything else is handled internally. If things aren't loaded yet, they get loaded(although you can still load them ahead of time to keep the game running smoothly). The components are defined in an object file along with any other info. The file, like everything else, is loaded if it's not there when you ask for it. As you can see, the changes to loading alone are enough to save tons of time. Another thing that I'm playing around with as well is CRC32 hashing. When you hash something, you take some arbitrary data(such as a file name), and use math to convert it to a unique numerical value, the hash. In most cases, checking if 2 hashed values are the same is significantly faster than checking if two strings of text are the same. Thus, hashing the names of my assets would probably save a decent amount of time whenever I need to grab them. I haven't actually added this to the engine, but I've left it in a state where it would be pretty easy to do. That way, I can add it in if loading/getting things ever becomes an issue.

Improved Coding Style

 My coding style doesn't often change, but in the year or so since I finished AMAZE I've changed the way I do things significantly. For those of you who aren't coders, it's really important to make all of the code in a project use consistent formatting. The main reason is simple: It can really hard to figure out what a piece of code is actually doing, unless you wrote it recently or commented it well. I've gotten much better about making my code readable, but my old stuff is pretty hard to figure out. A mixture of old and new would be even worse. However, with a newer and nicer-looking codebase, I think it'll be easier to expand on things later.

Easier Game-Specific Changes

I've set up DFEngine so that many core parts, like the game class at the center of everything, are missing. Well, not exactly. I've set these classes up in such a way that they only have the most basic things in them, and must be extended further for each project. The idea is that this way, I can ensue that a project's specific needs are met no matter what. I've tried the one-size-fits-all approach for a long time, and I'm not too impressed with it. Even though the broader parts of an engine rarely change, there are always details to tweak. This was never really supposed to be a tool for designers, like Unity or Game Maker, and I think it's time I stop treating it like one. At the scale I'm aiming for, tweaking and writing small amounts of engine code makes much more sense than trying to configure or script everything.

Safer, With 100% More Caution!

I used to subscribe to the "Run around with your pants down and hope no one notices" school of programming, where I would rush out large batches of features then check back later for safety issues. In fact, my old code has a tendency to assume that everything it receives is correct, and make few attempts to sanitize input/make sanity checks. If you gave it bad input, it could easily destroy itself in an instant. In the new rewritten version of DFEngine, I've included a pretty comprehensive set of sanity checks, warnings, and so on. I also run every new version through Valgrind's 'memcheck' tool several times, just to be sure. Remember what happened to Akradion? If not, go jog your memory. I'll wait.
That's what happens when you throw caution to the wind. Had I provided more error checking, and taken more care when writing DFEngine, maybe I could've averted that issue. As it was, I had too much code written to find the issue, and I was stumped. At the end of the day, that's the thing with C and C++. It's not difficult to prevent errors as long as you take your time, but it can be nearly impossible to fix them if you don't.

No comments: