Наследование Angelscript от класса C++
Я почесал голову над этим в течение нескольких дней, подумал, что у вас, ребята, может быть есть идея получше.
По сути, я хочу определить стандартный интерфейс, который затем может быть унаследован в классах Angelscript. Например, скажем, у меня есть карточная игра, такая как Magic The Gathering, мой базовый класс может выглядеть так:
class Card
{
public:
virtual void PreDrawPhase() = 0;
virtual void PostDrawPhase() = 0;
// etc....
};
Затем я хочу иметь возможность определять новые карты и их соответствующее поведение в Angelscript, в то же время имея возможность обрабатывать их в C++ (путем обработки интерфейса). Как мне этого добиться?
1 ответ
Отказ от ответственности: я никогда не использовал Angelscript ни в одном "реальном" проекте, поэтому следующий ответ должен быть взят с крошкой соли. Он работал в небольшом тестовом коде, который я настроил для него, и большинство фрагментов кода были подробно объяснены в официальном руководстве, но это абсолютно не гарантирует, что это продуманный дизайн и его можно разумно использовать в вашей игре.
Полагаю, что соответствующие части для достижения вашей цели довольно хорошо описаны в официальном руководстве:
Здесь описывается, как определить и использовать интерфейс.
Здесь описывается, как создавать и использовать "классы сценариев" в C++.
Используя эту информацию, мы могли бы потенциально написать упрощенный класс-обертку, который при создании создает экземпляр определенного класса и освобождает его при уничтожении, одновременно предоставляя функции-члены, которые вызывают соответствующие члены класса сценария:
(Примечание: для краткости я пропустил всю обработку ошибок и некоторые другие важные приемы, такие как правило дерева (класс, приведенный ниже, не может быть скопирован, не нарушая много...))
Заголовок:
class Card
{
public:
Card(asIScriptEngine *engine, asIScriptContext *ctx, const std::string &module_name, const std::string &class_name);
~Card();
void PreDrawPhase();
void PostDrawPhase();
private:
asIScriptContext *ctx;
asIScriptObject *obj;
asIScriptFunction *PreDrawPhaseFunc, *PostDrawPhaseFunc;
};
Реализация:
Card::Card(asIScriptEngine *engine, asIScriptContext *ctx, const std::string &module_name, const std::string &class_name):
ctx(ctx)
{
asIScriptModule *module = engine->GetModule(module_name.c_str());
auto type_id=module->GetTypeIdByDecl(class_name.c_str());
asIObjectType *type = engine->GetObjectTypeById(type_id);
PreDrawPhaseFunc=type->GetMethodByDecl("void PreDrawPhase()");
PostDrawPhaseFunc=type->GetMethodByDecl("void PostDrawPhase()");
asIScriptFunction *factory = type->GetFactoryByDecl((class_name+" @"+class_name+"()").c_str());
ctx->Prepare(factory);
ctx->Execute();
obj=*(asIScriptObject**)ctx->GetAddressOfReturnValue();
obj->AddRef();
}
Card::~Card()
{
obj->Release();
}
void Card::PreDrawPhase()
{
ctx->Prepare(PreDrawPhaseFunc);
ctx->SetObject(obj);
ctx->Execute();
}
void Card::PostDrawPhase()
{
ctx->Prepare(PostDrawPhaseFunc);
ctx->SetObject(obj);
ctx->Execute();
}
Я считаю, что код довольно понятен (поправьте меня, если я ошибаюсь, и я постараюсь уточнить), поэтому я просто опишу основную идею:
- Каждый экземпляр Card содержит указатель на некоторый объект Angelscript, который имеет некоторый произвольный тип, реализующий желаемый интерфейс (единственный способ, которым это применяется в настоящее время, - сбой, если что-то пошло не так, что отчасти плохо).
- По построению это создается (из переданных в engine, context и names) и указатели на его функции-члены накапливаются, при уничтожении оно освобождается.
- Каждый раз, когда вызывается член, он пересылается соответствующему члену объекта Angelscript.
Как уже упоминалось в начале, я не очень разбираюсь в Angelscript, так что это действительно может быть очень неоптимальный метод, и вполне вероятно, что существуют лучшие решения.