Shaarigan

Engine Core Features

Recommended Posts

So here again with some more opinion/discussion related topic about what features should/are normally be related into a game engine's core system from the implementation side of view. With a huge time I invested into writing and even more into research for my very own game engine project, there were as many differences as there are to coding guidelines. Especially open source engines (Urho3D, Lumberyard) and those that offer there source code (Unreal, CryEngine, Phyre) change during the centuries depending on current technical, coding and hardware standards. They all consist of third party code and libraries that are set apart from the core system bur rely on it.

Now I'm on a point where I want restructure/cleanup my project(s), removing obsolete and integrate prototyping code as same as change the growing code base to have a better clean managed file structure. The question that has had to emerge for long or short is what parts of the code base should be a core feature in order to keep the core system clean and flexible. Again reading many cross references it pointed some similarity out but also huge differences (like Unreals hashing/encryption code beeing in core) so I made a list of important stuff any game needs (I left out platform specifics and audio/renderer here because they are platform dependent and should stay in an own module/system)

  • Allocator (memory management)
  • Math (Vector, Matrix, Quaternion ...)
  • Threading (Threading, Locks)
  • Container (apart from the STL, Queue, Buffers, certain types of Array ...)
  • String (management, encoding, utils)
  • Input (seen a game without input?)
  • IO (reading/writing files and manage filesystem structure)
  • Type Handling

And a list that is optional but may/may not be inside the core code

  • Logging (because it is a development tool)
  • Profiler (see logging)
  • Serialization
  • Plugins
  • Events

In my opinion, logging and profiler code is a heavyly used feature in development but is stripped out mostly when deploying a release version, not all games rely on events rather than using other interaction (like polling, fixed function loops) and also the same for plugins and serializing/deserializing data; so what do you think should also be a must have in core system or should go from the list into core system as well as a must have? What else should go outside as an own module/system (into an own subfolder inside the engine project's code base)?

Courious to read your opinions ;)

Edited by Shaarigan

Share this post


Link to post
Share on other sites

Cross platform GPU wrapper (preferably low level in the core module - with models/scenes/materials as a higher level module), Audio, 3D collision and rigid body dynamics are all just as core as input (for most games). Though yes these can be modules that live alongside the core... But in that case, input should be too.

A networking module needs friendly sockets, HTTP, SSL and encryption (some platforms require all traffic to be encrypted).

Algorithms for hashing, etc, make sense in the core. e.g. The hash map will reference them!

I don't have a string class in my engine; it's a good way to discourage people from using strings :P 

Under threading, I'd have a thread-pool and job graph, not individual threads and locks.

Core game IO should be a lot more limited than generic IO. Loading of named assets, and reading/writing profile / savegame files (no general OS disk access). These are really two different things - not two uses of one IO library. e.g. assets are always streamed asynchronously from known formats and never written to. Savegames are always stored in some OS-dependent path such as "my documents"/appdata/home/etc.

Profiling, logging, assertions are mandatory development tools required to do your job, so should be in the core. Same goes for a good memory manager with debugging features. I'm leaning more and more towards having something like ImGui and a line rendering API as core, simply for development/debugging purposes. 

If you plan on using a Scripting language (or even reloadable C++), a binding/reflection system makes sense as it will be pervasive. You can also use these bindings to generate serialisation/visualisation functions.

Share this post


Link to post
Share on other sites

Forgot the memory management/allocator stuff so added it to the list :P I'm on the hop to force strict memory management at all levels of the engine to at least throw ya an assertion failure arround the head when leaving anything on the heap on scope exit.
 

59 minutes ago, Hodgman said:

Cross platform GPU wrapper (preferably low level in the core module - with models/scenes/materials as a higher level module)

I think you mean the graphics API (DX, GL, VK, whatever) here?

1 hour ago, Hodgman said:

3D collision and rigid body dynamics

Highly depends on the game, Puzzle or 2D platformer might not need any of these so I see them in an optional external instead of core where I'm not sure about input system here (what is a reason to write this post). But I think you were right and it depends a level upward than placing it in core directly.

Networking should stay external as not every game needs it but as I have seen for example Unreal hosts its HTTP stuff inside the core module. I personally would avoid this and instead keep networking low level on the one hand (to have possibility to write also server code) but also more high level as a game client.

Same thoughts for the crypto module. It consist of a lot of static functions to utilize AES, ECDSA, XTEA as same as different hashing functions SHA, RIPEMD and MD5 (as short hash) where the core system contains some text related 32/64 bit hashing functions used for example to identify names.

I agree on your arguments about strings and IO in general. Assets should be converted to packages of engine preprocessed data and savegames should be serialized/ deserialized in order to keep a level of error proofness but these systems may need to interact themselfs with the disk/ network/ cloud storage so I keeped this in my entire list ;)Nearly anything inside my code is already using streams/ buffered reading and static shared memory (aka memory mapped files) as needed and passing a stream interface is preferreable by any data processing function.

Under threading I have currently threads and locks as utility code wrapping the underlaying OS APIs where my JobPool and event system is in an own module but I see that this might go as well into core too. What do you think about event driven engines vs. polling/ update driven ones?

Profiler and logging were currently own modules as I thought of having them in build while development but strip them out when shipping. Profiler may be a point of diskussion but I see logging at least for support purposes as a core feature now so maybe move it there. Dont know about profiler yet :)

Reflection is currently realized as an external module named MetaType that is intendet to provide some meta information and C# like function invocation (for runtime class manipulation) as same as some serialization frontends. As I have seen this in core for various projects, I will potentially move it into core level, for at least support scripting and maybe editor UI interaction from C# level.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Forum Statistics

    • Total Topics
      627701
    • Total Posts
      2978700
  • Similar Content

    • By Baemz
      Hello,
      I've been working on some culling-techniques for a project. We've built our own engine so pretty much everything is built from scratch. I've set up a frustum with the following code, assuming that the FOV is 90 degrees.
      float angle = CU::ToRadians(45.f); Plane<float> nearPlane(Vector3<float>(0, 0, aNear), Vector3<float>(0, 0, -1)); Plane<float> farPlane(Vector3<float>(0, 0, aFar), Vector3<float>(0, 0, 1)); Plane<float> right(Vector3<float>(0, 0, 0), Vector3<float>(angle, 0, -angle)); Plane<float> left(Vector3<float>(0, 0, 0), Vector3<float>(-angle, 0, -angle)); Plane<float> up(Vector3<float>(0, 0, 0), Vector3<float>(0, angle, -angle)); Plane<float> down(Vector3<float>(0, 0, 0), Vector3<float>(0, -angle, -angle)); myVolume.AddPlane(nearPlane); myVolume.AddPlane(farPlane); myVolume.AddPlane(right); myVolume.AddPlane(left); myVolume.AddPlane(up); myVolume.AddPlane(down); When checking the intersections I am using a BoundingSphere of my models, which is calculated by taking the average position of all vertices and then choosing the furthest distance to a vertex for radius. The actual intersection test looks like this, where the "myFrustum90" is the actual frustum described above.
      The orientationInverse is actually the viewMatrix-inverse in this case.
      bool CFrustum::Intersects(const SFrustumCollider& aCollider) { CU::Vector4<float> position = CU::Vector4<float>(aCollider.myCenter.x, aCollider.myCenter.y, aCollider.myCenter.z, 1.f) * myOrientationInverse; return myFrustum90.Inside({ position.x, position.y, position.z }, aCollider.myRadius); } The Inside() function looks like this.
      template <typename T> bool PlaneVolume<T>::Inside(Vector3<T> aPosition, T aRadius) const { for (unsigned short i = 0; i < myPlaneList.size(); ++i) { if (myPlaneList[i].ClassifySpherePlane(aPosition, aRadius) > 0) { return false; } } return true; } And this is the ClassifySpherePlane() function. (The plane is defined as a Vector4 called myABCD, where ABC is the normal)
      template <typename T> inline int Plane<T>::ClassifySpherePlane(Vector3<T> aSpherePosition, float aSphereRadius) const { float distance = (aSpherePosition.Dot(myNormal)) - myABCD.w; // completely on the front side if (distance >= aSphereRadius) { return 1; } // completely on the backside (aka "inside") if (distance <= -aSphereRadius) { return -1; } //sphere intersects the plane return 0; }  
      Please bare in mind that this code is not optimized nor well-written by any means. I am just looking to get it working.
      The result of this culling is that the models seem to be culled a bit "too early", so that the culling is visible and the models pops away.
      How do I get the culling to work properly?
      I have tried different techniques but haven't gotten any of them to work.
      If you need more code or explanations feel free to ask for it.

      Thanks.
       
    • By AyeRonTarpas
      A friend of mine and I are making a 2D game engine as a learning experience and to hopefully build upon the experience in the long run.

      -What I'm using:
          C++;. Since im learning this language while in college and its one of the popular language to make games with why not.     Visual Studios; Im using a windows so yea.     SDL or GLFW; was thinking about SDL since i do some research on it where it is catching my interest but i hear SDL is a huge package compared to GLFW, so i may do GLFW to start with as learning since i may get overwhelmed with SDL.  
      -Questions
      Knowing what we want in the engine what should our main focus be in terms of learning. File managements, with headers, functions ect. How can i properly manage files with out confusing myself and my friend when sharing code. Alternative to Visual studios: My friend has a mac and cant properly use Vis studios, is there another alternative to it?  
    • By Defend
      Not asking about singletons here (nor advocating). With that clarified:
      If we assume someone wants a global + unique object, why isn't a namespace always the preferred approach in C++, over implementing a singleton class?
      I've only seen the namespace approach encouraged when there aren't statics being shared. Eg; from Google's style guidelines:
      But why not have non-member functions that share static data, declared in an unnamed namespace? And why isn't this generally suggested as a better alternative to writing a singleton class in C++?
    • By Finalspace
      I am playing around with ImGui, adding a UI to my level editor, but i am having trouble with mouse clicks in ImGui triggers a click in my actual editor (Because it uses the same input states).
      Right know i have just a main menu bar and the editor rendering is behind that. When i click on a menuitem for example, and select another sub item and click, i place a block in my editor - just because the input events are the same for both ImGui and my editor.
      So i have a few questions:
      1.) Is there a way to detect when ImGui has taken any action/hover in the previous frame, so i can skip the input handling for my editor? (This way i can prevent the issue i have right now)
      2.) Can i add a imgui window which fully fits the rest of the entire screen - after a main bar has been added? (Do i manually need to calculate the size?)
      3.) Should i make the entire editor with ImGUI only - so i wont get any input fuzziness problems?
    • By irreversible
      I'm writing some code for automatic function argument type deduction using a tuple. I'm then iterating over it to narrow down and check each argument separately. I spent a cozy evening tinkering with getting by-value, const value and const references to work, until I discovered that some functions need to return more than one result. This is where I need to identify non-const lvalue references, which a tuple has difficulty in handling.
      As far as I can tell most problems out and about on the web focus on creating fairly simple stand-alone tuples that only contain lvalue references using std::tie. In particular, this stackexchange thread outlines how that can be accomplished. 
      Problem is, I have a packed array of types, which may or may not contain one or more lvalue references interleaved with other types. forward_as_tuple is suggested here and there, but I'm unsure how to use it. 
      Here's there relevant code:
      // split the return type from the argument list template<typename R, typename... Args> struct signature<R(Args...)> { using return_type = R; using argument_type = std::tuple<Args...>; // 1 }; template<typename FUNCSIG> editor::nodetype_t& CreateNodeType( IN const char* category) { // instantiate the argument list tuple. No need to post any further code as this // is where things fail to compile signature<FUNCSIG>::argument_type arglist; // 2 } // the below snippet outlines how CreateNodeType() is called: #define DEFINE_MTL_NODE(function, category, ...) \ auto& nodeType = CreateNodeType<decltype(function)>(category); // a sample function for completeness. I'm intentionally not using the return value here. void Lerp( IN const math::vec3& ColorIn1, IN const math::vec3& ColorIn2, IN float Alpha, OUT math::vec3& ColorOut) { .. } void main() { DEFINE_MTL_NODE(Lerp, "Color"); } Either the line marked with 1 or 2 needs to be something else, but apparently my C++ level is not high enough to figure out what. PS - to further complicate things, I'm stuck on C++11 for now.
      Ideas?
  • Popular Now