Polymorphism in Angelscript
by Xavier Shay


ADVERTISEMENT

Summary

Natively, Angelscript does not support inheritance. This article shows a method that can be used with Angelscript that allows virtual function overrides to be written in Angelscript, effectively allowing you to create derived classes in script.

It is assumed the reader is familiar with the basics of Angelscript, and can register objects and functions with the Angelscript engine.

This article was written for Angelscript v1.7. Proper support for objects is planned for later versions, so with any luck this article will one day become obsolete.

As good a place as any

In our game we have a player that can collide with items. Each item will affect the player in a different way. For simplicity, our player class will only have one variable: health. We could conceivably use the following classes:

// C++
class CPlayer {
public:
  float health;
};

class CItem {
public:
  virtual void collidePlayer(CPlayer *p);
};

In C++ it would be trivial to derive classes from CItem for each item type. But say we wanted the item behaviours to be defined in script. We are unable to use derived classes, so we must find another method.

Let's work backwards and start with what we would like the script to look like:

// Angelscript
bool item_init() {
  registerItemType("health");
  return true;
}

void health_collidePlayer(CPlayer *p) {
  p->health += 15;
}

That looks peachy. Obviously, we will need a static variable to hold the item types, and a member variable to hold the type of the item. The collidePlayer function will look at the itemType and call the appropriate function in script. Note that it is not actually necessary to register the item types, but this allows us to implement some sort of error checking (ie. Forbid creation of non-existent item types).

// C++
class CItem {
public:
  static void registerItem(const char *itemName);
  static string itemTypes;
  
  string type;  
  
  virtual void collidePlayer(CPlayer *p);
}

void CItem::registerItem(const char *itemName) {
  CItem::itemNames.push_back(itemName);
}

void CItem::collidePlayer(CPlayer *p) {
  string func;
  list<asDWORD *> args;

  CItem *thisPtr;
  
  thisPtr = this;
  func = itemType + "_collidePlayer";  // Function to call
  
  args.push_back((asDWORD *)&thisPtr);
  args.push_back((asDWORD *)&p);
  eng.callScript(func, &args);
}

CItem::registerItem("health"); // Or we could just call the item_init() function in script

a = new CItem("health"); // See example code for this constructor
p = new CPlayer;

p->health = 100;

a->collidePlayer(p);  // p->health will now be 115

Members Eat Free

That's all well and great, but Bob the stingy level designer desires to add health items that only give 5 health. He could create a new item type, but Bob also happens to be lazy. If the health item could have a member variable "amount", Bob could create as many different health types as pleased him without ever having to create an item type of his own.

For this we are going to use an STL map. In the item type constructor, it will "register" its member variables, similar to how Javascript objects work. For simplicity, we will just allow variables of type "float".

// Angelscript

// Called from the CItem constructor
void health(CItem *this) {
  this->registerFloat("health");
}

void health_collidePlayer(CItem *this, CPlayer *p) {
  p->health += this->getFloat("health");
}

// C++
class CItem {
public:
  /* as before */
  
  map<string, float> vars;
  
  // See sample code for implementations of the following functions
  void registerFloat(const char *varname);    // Adds key to vars
  void setFloat(const char *varname, float value);
  float getFloat(const char *varname);
}

CItem::registerItem("health"); // Or we could just call the item_init() function in script

a = new CItem("health"); // See example code for this constructor
p = new CPlayer;

a->setFloat("health", 10);
p->health = 100;

a->collidePlayer(a, p);  // p->health will now be 110

Note that now we are using object specific variables, we must now pass a "this" pointer to our functions so that we can access member functions.

Stepping Stones

  • A handy byproduct of this method is that if we want to create an item type in C++, we only need to override all the functions that would normally call script functions, and the interface will be exactly the same, so we can mix and match at whim.
  • Angelscript cannot call virtual functions, so if you are planning on calling collidePlayer from script, you will need to write a wrapper function that simply calls this->collidePlayer().
  • Support for different types of member variables could possibly be generalised with the use of a Dator class.

Have a look at the sample code for additional insights. A MSVC 6.0 project file is included, but it should be portable. You will require the angelscript library.

Xavier Shay
spam@noreality.net

Discuss this article in the forums


Date this article was posted to GameDev.net: 11/5/2004
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Scripting Languages

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!