Chap. 07B --- Class Definitions

Classes and Instances

Wild Pockets provides a means for you to define your own classes. Classes work similarly to classes in other object-oriented languages- you can create a class template that describes a set of functionality, and then create instances of that class. Additionally, you can set the class of a SceneObject, so that the object takes on the behavior described by your class. You define a class by calling the “new” method on the global Class:

MyClass = Class.new()

To create an instance of the class, use the new operator on the class:

local instance = MyClass.new()

When an instance is created, a table is created to store data for the instance. You can access fields of the instance using the usual dot-notation:

local instance = MyClass.new()
instance.field1 = "hello"
instance.field2 = "goodbye"
print(instance.field1) -- prints hello
print(instance.field2) -- prints goodbye

Fields are not declared. You simply store data to create a field, as shown above.

Instance Methods

You can add instance methods to your class using this notation:

function MyClass:method1(arg1, arg2, ...)
    print(self.field1)
    print(self.field2)
end

This method actually has three formal parameters: self, arg1, arg2. The self parameter is hidden, it is meant to hold the handle of the instance. Using the instance handle, you can access the fields of the instance, as shown above. Note the colon in the method declaration. The colon is what instructs Lua to add a hidden parameter, self, to hold the instance handle. To call this method, you need to use a colon in the method call as well:

local instance = MyClass.new()
instance:method1("apple", "banana")

The colon in the method-call instructs lua to pass the instance handle prior to the other parameters. In other words, this method call is actually passing three actual parameters: instance, "apple", "banana". These three actual parameters correspond to the three formal parameters self, arg1, arg2. Note to C++ programmers: the self argument is directly analogous to this in C++. In C++, you can evaluate the expression this->m_instanceVariable to access an instance variable, but you can also just access the instance variable directly, using the expression m_instanceVariable. The latter syntax does not work in Lua: in lua, you have to use the instance handle explicitly. If you're in a method, the hidden parameter self usually contains the instance handle, so you would say self.instanceVar.

Class Methods

A class method differs from an instance method as follows: to invoke a class method, you don't need an instance. For example, the method MyClass.new is a class method. Class methods are defined as follows:

function MyClass.method2(arg1, arg2, ...)
    etcetera...
end

Note the dot instead of a colon in the method declaration. The method call must also use a dot instead of a colon:

MyClass.method2("hello", "goodbye")

So as you can see, class methods are invoked by naming the class explicitly. Since no specific instance of the class is involved, no instance is passed to the method, and the method doesn't have a self parameter to hold an instance handle. You actually can invoke these methods using an instance handle, but it acts the same as if you had used the class name directly. This call is functionally equivalent to the one above:

local instance = MyClass.new()
instance.method2("hello", "goodbye")

Setting the Class of a Scene Object

You can associate a class with a SceneObject. If you do, the SceneObject will have all the methods of the class, and all the methods of SceneObject, combined. To set the class of an object programmatically, use setScriptClass, which takes a lua script filename and a classname:

obj:setScriptClass("josh/RobotCode","TankBot")

The lua script filename will be imported using the module system. Then, the module will be searched for the specified classname. In the example above, the module "josh/RobotCode" will be imported. This module should contain the command

TankBot = Class.new()

A SceneObject whose class has been set acts like a hybrid between a SceneObject and an object of the class. You can access fields on it, just as you would for any instance of the class:

local obj = SceneManager.getObject("robot")
obj:setScriptClass("josh/robotCode", "TankBot")
obj.hitPoints = 20
obj.attackStrength = 5

It also will support all methods of the class, and all methods of SceneObject, combined. If the class contains a method whose name is the same as a method of SceneObject, then the class method overrides the SceneObject method. If you do override a SceneObject method, WildPockets built-in methods never call your overriddes. Only your code is affected by your overrides. For example, you can override setPosition if you wish, but this won't alter the behavior of built-in mechanisms like the PathManager.

Specifying a Class in the Builder

You can also set the class of an object in the builder. Select the object, open the properties panel, and select the script properties. From there, you can edit the script module filename and script classname. When the game starts, the SceneObject will automatically be made into an instance of the class.

Specifying a Class in a Model Exporter

It is possible to specify the module and class of a SceneObject in the model exporter. In this case, when you call SceneManager.createObject, the SceneObject will automatically be made into an instance of the class.

The Constructor

If you wish, you can add an instance method _init to the class. This method is called 'the constructor.' This is called whenever the instance comes into existence. There is a secondary constructor called onStart which is only called when a SceneObject comes into existence with a class already assigned. To clarify, here is a table of all the possible ways an instance can come into existence:

Way to Create Instance   _init   onStart
Instance created using new   YES   NO
SceneObject given a class using setScriptMethod   YES   NO
SceneManager.createObject using a model with a Class   YES   YES
Object created in builder and given class in builder   YES   YES

In addition, there is an optional onDelete method which is called if a SceneObject with a class is deleted.

Passing Instance-Methods to the Timer

You may be familiar with the Wild Pockets Timer functionality, which lets you set up code to run after a delay. Passing instance-methods to the timer is a little tricky. This won't work:

Timer.later(object1:method1, 10) --- doesn't work.

This seems intuitively correct, it would seem that you're asking the timer to invoke the method object1:method1 10 seconds in the future. But it doesn't work. Remember, a method is really just a function with a hidden parameter, self. When you call a method, you're really just calling a function and passing it an extra, hidden parameter. So we need the timer to call method1, and we also need it to pass object1 as a hidden parameter. That's too much to ask, though: the timer will call method1 if we ask it to, but it won't pass in object1 for us: the timer doesn't have a mechanism to pass parameters at our request. Here's the solution:

Timer.later(function () object1:method1() end, 10) --- This works

Here, we've created a tiny function whose sole behavior is to invoke object1:method1(). We've passed that function to the timer.

The Instance-Local Timer

Classes on SceneObjects call for some extensions to the way timers work. For example, it is frequently desirable to have a function that is called periodically while an object exists, but to have that timer automatically be terminated when the object is destroyed. This is achieved by providing you two sets of timer functionality:

  • Timer.later() and Timer.every() create a global timer that will persist beyond the lifespan of your SceneObject.
  • self:later() and self:every() create a timer that is tied to your SceneObject, and will automatically be cleaned up.

So for example:

-- doMethod1 is called every 10 seconds, forever
Timer.every(function() self:doMethod1() end, 10)

-- doMethod2 is called every 10 seconds, until object destroyed
self:every(function() self:doMethod2() end, 10)

Handling Mouse Clicks

A SceneObject that has a class can have click-handling methods: onClick, onLeftClick, onMiddleClick, and onRightClick. If these methods exist, they will get invoked whenever you click on the SceneObject with the class. These methods are invoked by an EventMap which is at the bottom of the EventHandler stack. Therefore, if you put any other mouse-click-catching handler on the EventHandler stack, it will override these methods.

Built-in and User-definable Methods of Classes

  • new - The one built-in method: it instanciates a new instance of the class.  This is called automatically when you call setScriptClass on a SceneObject - the resultant instance is then tied to that SceneObject.
  • _init - To be implemented by the user: this is called on a new instance when that instance is created: this can happen when an existing SceneObject is assigned to the class or a SceneObject of the class is instanciated, or just from manually using YourClassName.new()
  • _del - To be implemented by the user: this is called on an instance when that instance goes away: this can happen when a SceneObject of the class is destroyed or has its class changed, or when an instance of the class is just manually deleted.

User-definable Methods of Instances of Classes attached to SceneObjects (events)

  • onStart - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being instanciated. (Note that this is therefore only called when the SceneObject already has a class assigned to it at creation time)
  • onDelete - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being deleted.
  • onClick - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on (regardless of mouse button: this will be called in addition to any of the following functions).
  • onLeftClick - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on with the left mouse button.
  • onMiddleClick - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on with the middle mouse button.
  • onRightClick - When this exists in the class of a SceneObject, it is called as a response to the SceneObject being clicked on with the right mouse button.

Built-in Methods of Instances of Classes

  • every - Analogous to Timer.every, this will call a function every so often, but will stop when the instance is deleted.
  • later - Analogous to Timer.later, this will call a function once, later, unless the instance is deleted beforehand.
  • sleep - Analogous to Timer.sleep, this will sleep for some amount of time, but will stop early if the instance is deleted.