I think planning is finally actually coming to a close this week. This is probably the third week I’ve said that. But I think I mean it this time. I’m eager to see how implementation time pans out.
This week’s problems:
1. Who’s renderer is this anyway?
As previously discussed, sometimes you want to pre-render complicated unchanging visuals to a texture and then just draw that instead of each individual element every time. Naturally this means coordinates need to start being relative to a sprite’s parent. It also means that the renderer object- the thing that decides whether it’s doing a dynamic or cached render- needs to start being what decides what to do with those relative coordinates. A dynamic renderer is going to want to use the accumulated xy positions of its parents. A cache renderer is going to want to start fresh since it’s having its children draw to it instead of directly to the world.
This caused a great deal of pain in terms of deciding whether the object handling all general positional information should deal with these final coordinates, or whether the renderers that changed the behavior to begin with should handle the coordinates. Eventually the renderers themselves won out at the logical place to put it.
2. Draw order is a pain.
Generally the draw order of a scene graph is determined by the ordering of the graph itself. This causes some issues in a 2d game. The scenario I use to illustrate the problem is this: characters have nametags positioned above them. Logically, these nametag sprites should be attached to the characters since their positions are related. But the draw order of these things is very different. Since characters are drawn in y-order, you end up with nametags being placed underneath characters when they should always be above all characters!
This doesn’t seem to be that commonly discussed of a problem online. I’m guessing most people just work around this relatively rare case by manually positioning such nodes elsewhere in the code, but that defeats part of the point of using a scene graph to begin with! (in early drafts of my work around I tried building this type of functionality directly into sprites, but ran into it quickly becoming a mess). In one case I saw a library using parts of OpenGL directly to accomplish differing draw order in special cases, with a host of caveats attached to it. A friend I asked used the technique of having draw order completely unrelated to the graph by having sprites associated with layers that determine their draw order.
I didn’t like any of these solutions because using the scene graph for draw order is extremely useful to me. You want a window’s contents to be drawn on top of the window? That’s the default behavior! You want to move a window to the top of the window draw order when it gets selected? Simple as moving it to the top of its parent’s child ordering. You want JUST these sprites to be y-ordered? Set their ordering metric to their y values when they change. It allows for dealing with complicated draw order problems in a simple manner, especially when it comes to UI.
So I finally settled on an unnervingly complicated solution. There are now two graphs: the transform graph (inheriting position etc) and the layer graph (determining draw order). Most of the time these graphs are identical, but in cases where they need to diverge they can by giving a sprite a different layer parent than their transform parent. I don’t really like how complicated this makes things. Bugs are almost certainly going to arise from me forgetting which graph I’m concerned about at a given moment. It’s wasting some resources for every sprite that only a few sprites need to worry about. But it does solve the problem.
3. Scrolling is pain.
Supporting UI scrolling is basically death by a thousand cuts. You need to a scroll offset to shift children around to create a scrolling effect, but you must only pass that offset to your children instead of using it yourself. You must be a specific type of sprite so that you know to create scrollbar children to adjust you. You must use clipping/masking on your children to deal with partial visibility. Scrolling has its fingers in a lot of different pies, which makes structurally containing it a nightmare even if the individual parts aren’t so complex.