I decided to expand on the discussion started with Thomas back at the blog here, 'cause the forums are more readable and support code formatting, as well because I wanted to avoid all the hassle with Bloggers spam filter (the thing is on the loose!).
OK, in summary Thomas expressed his concerns about (1) the possibility of creating a function that would act only on specific instances of a specific class, and (2), the fact that AngelScript does not allow for script-side class to be derived from a C++ class (or an exposed class even).
So, Thomas, you ended your last comment with:
Quote:Problem is that AngelScript does not let classes declared in script inherit from classes I implement in C++ code.
I know, but you're not hearing me:
why do you feel the need to derive from the C++ class in the first place?
In any case, always remember that composition is a flexible alternative for inheritance.
You have several options - let's start with the straightforward one.
Say you have a C++ class that represents an entity or something else in the game, and you want to expose it to AngelScript.
Well, (1) is easily solved by making the function in question a member function of the C++ class, and exposing it as a member method of it's script counterpart. As any instance method (function), it will operate only on instance-specific data.
As for (2), as I've said - you don't actually need it. Once exposed, the script version of the class act's as an out-of-engine API to the C++ class. Every method call is delegated back to the corresponding function call on the corresponding object, so semantically it's just like using the C++ class in C++ code on it's own.
If you
really need to extend the functionality of the exposed class in-script, you can use a little trick [the following is all script-side]: create a wrapper class in AngelScript, that contains the exposed C++ class as a member variable; then wrap all the methods, by simply calling the corresponding ones on the member var.
The AngelScript
will not prevent you to inherit from the wrapper class.
As I've said on the blog, in order to test all this, I downloaded the AngelScript SDK, and set it up to work with a simple console app.
So here's the sample class declaration (C++):
class ClassToExpose
{
private:
static int s_instanceCount;
int m_refCount;
int m_id;
public:
ClassToExpose(void);
virtual ~ClassToExpose(void);
// let's say that GetDescription() is the function in question.
string GetDescription();
void AddRef();
void Release();
};
// Note that the factory function used by AngelScript to create the
// instance of the exposed class actually creates a C++ instance.
ClassToExpose* ObjectFactory();
Some implementation details of interest (C++):
ClassToExpose::ClassToExpose(void)
{
m_refCount = 1;
s_instanceCount++; // static, keeps track of the overall number of instances created
m_id = s_instanceCount; // the idea is to show later in script that objects are indeed different
}
//...
string ClassToExpose::GetDescription()
{
// This just uses the m_id to identify the instance later in the script
stringstream out;
out << m_id;
return string("[ I'm a class exposed from C++! (").append(out.str()).append(") ]");
}
Next step was to set it all up, along with some global helper functions (C++):
engine->RegisterGlobalFunction("void Write(const string &in)", asFUNCTION(Write), asCALL_CDECL);
engine->RegisterGlobalFunction("void WriteLine(const string &in)", asFUNCTION(WriteLine), asCALL_CDECL);
engine->RegisterObjectType("ScriptClass", NULL, asOBJ_REF);
engine->RegisterObjectBehaviour("ScriptClass", asBEHAVE_FACTORY, "ScriptClass@ f()",
asFUNCTION(ObjectFactory), asCALL_CDECL);
engine->RegisterObjectBehaviour("ScriptClass", asBEHAVE_ADDREF, "void f()",
asMETHOD(ClassToExpose, AddRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("ScriptClass", asBEHAVE_RELEASE, "void f()",
asMETHOD(ClassToExpose, Release), asCALL_THISCALL);
engine->RegisterObjectMethod("ScriptClass", "string GetDescription()",
asMETHOD(ClassToExpose, GetDescription), asCALL_THISCALL);
And now the fun part (AngelScript code):
void main()
{
ScriptClass o;
WriteLine(o.GetDescription());
ScriptClass o1;
WriteLine(o1.GetDescription());
Wrapper w;
WriteLine(w.GetDescription());
DerivedWrapper dw;
WriteLine(dw.GetDescription());
}
class Wrapper
{
ScriptClass o;
Wrapper()
{
}
~Wrapper()
{
}
string GetDescription()
{
return "{Wrapper: " + o.GetDescription() + "}";
}
}
class DerivedWrapper : Wrapper
{
string GetDescription()
{
return "{DerivedWrapper -- " + Wrapper::GetDescription() + "}";
}
}
The output:
[ I'm a class exposed from C++! (1) ]
[ I'm a class exposed from C++! (2) ]
{Wrapper: [ I'm a class exposed from C++! (3) ]}
{DerivedWrapper -- {Wrapper: [ I'm a class exposed from C++! (4) ]}}
Now, if your C++ class is
not meant to be directly exposed to AngelScript for some reason, but you still need to use it's functions, there are two ways to go.
First, you can derive (in C++) from that class, and then expose the derived version, or you could write a C++ wrapper that would contain an instance of the original class (possibly sharing a parent/ancestor with it), and expose the wrapper.
I hope that helps.
P.S. If you spot some peculiarities about the C++ code, that's because I'm actually a C# programmer, but I think I managed to illustrate the point. And if there's something off that's important, please point it out.