2
\$\begingroup\$

I'm working on a game engine to learn how it's done (so I might better take advantage of the techniques involved and other, similar engines in the future), and I've run into a problem when handling position when it should be able to be either 2D or 3D.

Since the physics updating the position must also affect the rendering of the graphical components' positions, I need to hold the position of the entity itself separately. Since it is used by most systems anyway, it is fine to break the purity of the design for this bit.

However, I can't decide a default position component type as the engine is meant to have both 2D and 3D capabilities. I know Unity has both 2D and 3D systems and uses a similar Entity-Component system, and there may be other engines that have a similar setup. How is it determined what type of position component to use in those systems? Or rather, what would be the best way to handle this sort of conflict (in terms of simplicity and memory)?

I'm using C++ with a little bit of inheritance with the components, where every component has an update method that is defined per component that does any sort of batching or default updating behavior (e.g. the SpriteComponent class's update will send the necessary data to the SpriteBatcher). However, the Entity class simply stores them all in a std::unordered_map<std::string, Component*> and iterates through them, calling their update methods.

Edit: I should mention I'm using GLM for my vector types, mostly out of convenience and to keep everything consistent with OpenGL (which I'm using for rendering). Since some functions are defined for 2D but not 3D and vice versa, I can't just use a 3D vector for 2D operations without constructing a new, temporary 2D vector from the 3D vector, which is inconvenient for various reasons (memory usage, construction time, not saving state correctly or having to convert data to save state, etc).

\$\endgroup\$
7
\$\begingroup\$

Use a 3D vector. For your 2D components, simply ignore the third component.

The extra "cost" of an unused float is trivial in comparison to the rest of your architecture, and things like std::unordered_map<std::string, Component*> are of far greater performance and memory concern than an occasionally-unused float anyway.


You say that this would be

inconvenient for various reasons (memory usage, construction time, not saving state correctly or having to convert data to save state, etc).

That's largely irrelevant.

The memory impact of the temporary 2D vectors is transient, on the stack (in almost all reasonable cases) and easily budgeted for since it exists only for the duration of a single update (thus the upper limit is some constant times your concurrent object update limit). Plus, the impact is going to be small. If you cared about memory footprint that much you wouldn't be using the memory-guzzling std::unordered_map with std::string keys.

The conversion, being transient, should have no impact on state saving. You can easily disregarding the ignoring component during state serialization if you can know enough about the object to at that time to know it's only using two of the three components, also. The performance impact of having to convert from a three- to two-component vector during any kind of serialization is likely to be dwarfed by the IO overhead, so that shouldn't matter.

The performance cost of the conversion at runtime for the actual use of the 2D-only math functions pales in comparison to the performance cost of the hashing of the string keys you'll need to be doing just to fetch the object, and likely to the cache-miss-thrashing you are incurring by using what appears to be a "for each entity, for each component, update" style of loop with a cache-inefficient data structure.

\$\endgroup\$
  • \$\begingroup\$ I'm using GLM for the vector types though, and certain methods are defined only for vec2s and some only for vec3s, so I can't just mix them. I could construct a new vec2 every time I have to do some calculation with the vectors involving those , but I'd prefer to avoid that for the sake of simplicity and not copying vectors like that every frame (although this is probably me making premature optimizations). \$\endgroup\$ – Cave Dweller Dec 2 '14 at 0:00
  • \$\begingroup\$ Copying the XY components of a 3D vector into a 2D vector to utilize the 2D-only methods you may have available is going to be trivial. You are making pre-mature optimizations by avoiding that copy, especially given the inefficiency of your existing architecture (the fixing of which would have far more positive performance impacts than skipping a few float copies). \$\endgroup\$ – Josh Dec 2 '14 at 0:17
  • 1
    \$\begingroup\$ It's not going to uglify code, either, if you use GLM_SWIZZLE. Then you can just do vec2_operation(my_vec3.xy()), meaning you just have to add an extra .xy() here and there for the most part. \$\endgroup\$ – Sean Middleditch Dec 2 '14 at 0:24
  • \$\begingroup\$ @SeanMiddleditch I didn't know that feature was supported, that definitely sounds like a clean way to handle it then. Thank you for mentioning that. It'd be nice if comments could be accepted as an answer. \$\endgroup\$ – Cave Dweller Dec 2 '14 at 0:32
  • \$\begingroup\$ @JoshPetrie I realize the std::unordered_map<std::string, Component*> is probably way more memory heavy than I should be using, and the "for each entity, for each component" loop is also probably horribly inefficient, and they were added early when I was prototyping and getting scaffolding set up for this engine, so I should probably replace them with something more efficient. Any suggestions? \$\endgroup\$ – Cave Dweller Dec 2 '14 at 0:37
-1
\$\begingroup\$

I see two possible solutions.

One, is to use a 3D Vector even in a 2D space, and keep one of the coordinates fixed to the same number.

Another one, is to create two different classes, Vector2 and Vector3, both inheriting from a base Vector class, and make your systems work with this Vector class directly, looping trough its components.

\$\endgroup\$
  • \$\begingroup\$ I'm not the one who gave the downvote, but as a guess: while your first suggestion is solid, the second suggestion is a terrible object-oriented sin; don't do that. \$\endgroup\$ – Sean Middleditch Dec 2 '14 at 0:21
  • \$\begingroup\$ Introducing inheritance into geometry types like vectors is almost always a terrible terrible idea. \$\endgroup\$ – Josh Dec 2 '14 at 0:21
-1
\$\begingroup\$

Another solution, tough much more memory demanding is to simply use matrices. A 4x4 matrix can hold both a position and an orientation, its directly used by the rendering system(usually) so you can just simply read its last row to get position you want.

I think the memory demand is still manageable because even with 10000 objects you use only 10000 * 16 * 4 = 625 KB of memory.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.