Custom components in Engine

Published May 19, 2023
Advertisement

Enabling a user of your own software to write his own modules is not always an easy task. Enabling a user of your own software to write components for objects in such software is … well, even harder one. Most of the famous modern generic game engines like Unity do allow that - it is one of their most important features. And while they took it quite a bit further than what I did, unlike them - I'm still in the world of non-interpreted, but compiled languages. C++ to be precise.

I've decided to allow this kind of functionality in the engine, it might be a bit overkill (after all - at this point I can always just write components directly into the code) - but the feature may allow me to make smaller projects, like custom tools, to some extent faster. Let's jump to most basic example.

Let me introduce you a very basic and trivial DemoController:

#include "SkyeCuillin/SkyeCuillin.h"

class DemoController : public Engine::Component
{
public:
	float mValue;

	virtual bool Editor(std::string& prev, std::string& next)
	{
		SetupContext();

		return Engine::Component::DefaultEditor(this, &Reflection, prev, next);
	}

	virtual void Start()
	{
		mValue = 0.0f;
	}

	virtual void Update(float deltaTime)
	{
		mValue += 1.0f / 60.0f;
		if (mValue > 1.0f)
		{
			mValue -= 1.0f;
		}
	}

	virtual std::string Serialize()
	{
		std::stringstream ss;

		ss << "DemoController" << std::endl;

		ss << mValue << std::endl;

		return ss.str();
	}

	virtual void Deserialize(const std::string& data)
	{
		std::stringstream ss(data);

		std::string name;
		ss >> name >> mValue;
	}

	REFLECT()
};

REFLECT_STRUCT_BEGIN(DemoController)
REFLECT_STRUCT_MEMBER(mValue)
REFLECT_STRUCT_END()

SKYE_CUILLIN_COMPONENT(DemoController)

There are still going to be some syntax changes, but a brief description is necessary:

  • Editor - Overloaded function which renders an editor for given component, the SetupContext call is necessary there (due to how ImGui works), the DefaultEditor is a static function that uses reflection to build editor for each reflected attribute. Note that you can have this AND custom widgets in an editor, which is an important feature for me
  • Start - Called when game starts (or rather - in editor - when one presses Play)
  • Update - Called each frame when Playing
  • Serialize/Deserialize - these 2 functions store/restore values for undo/redo, for editor (to check whether anything changed), and also are important when saving/loading the world; I'm considering updating code to allow for DefaultSerialize and DefaultDeserialize - much like DefaultEditor - to process all reflected attributes of given class

So, how does this look in an engine? This like:

Fig. 01 - When adding a new component, each custom component like DemoController here is listed
DemoController has a custom auto-generated editor for all its reflected attributes

The implementation itself is far from trivial - but in short - specific (project) directory (and whole dir tree under it) is watched from start and during runtime. Each CPP file found inside that directory tree is considered a custom component script. For each script found this way, the following is done

  1. Check if there is DLL corresponding to the CPP script - if so, check whether CPP has been modified after DLL was built - if CPP is newer than DLL or has no corresponding DLL, continue
  2. Call compiler (MSVC at this point) to build DLL in temp location - respect the build type (DEBUG vs RELEASE - so we can actually debug components) - if it fails, let user know, otherwise continue
  3. Unload every previous instance of DLL (if applicable) - move DLL to correct location (overwriting the old one if applicable), and load it - since this point we can use it

In theory it doesn't sound that hard, but one has to go through various implementation details that make this a LOT harder to do properly.

If you made it here - thanks, I may share some implementation heavy details in future, once this is a bit more polished and I have at least some small project done this way. Implementing this was a lot more fun than what I expected though!

3 likes 1 comments

Comments

Jiehong Jiang

I implemented a scripting system before, basically a script class that is a component which can be further derived. The functionality itself isn't hard but I have to introduce other functions to make scripting feature-complete, so I eventually gave up.

May 19, 2023 10:25 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement

Latest Entries

Merry Christmas!

7657 views

Progress

3683 views

Roadmap for 2020

5220 views

DOOM: Post Mortem

4810 views

DOOM: GROOM

4224 views

DOOM: Placeholders

4805 views

Ludum Dare 45

4532 views
Advertisement