Clone wiki

Basic Class System / Home

Including Basic Class System in a Project

To use Basic Class System in a project, the lua source (class.lua) must be placed in a subdirectory of whichever directory your main.lua file is in called basic_class_system. If you have the BCS plugin for Game Kitchen installed, the IDE will automatically ensure that this is the case.

Defining a Class

Press Ctrl-N to launch the module creation dialog. Check that “Basic Class System” is selected in the dropdown box. The dialog should look like this:

Image Here

To create a class which does not derive from any existing class, type the desired class name into the “Class Path” field and click okay. Note that the new class need not be in the root package. For example, you may create a new color class in the graphics package by putting “graphics.color” in the “Class Path” field.

Game Kitchen will create a file with the following contents:

local class = require('basic_class_system.class')
local color = class:new()

function color:init()

end

return color

Constructor

The init function is the constructor of our new class. We proceed to implement the constructor as follows:

--@param(r : number) Red component (0-255)
--@param(g : number) Green component (0-255)
--@param(b : number) Blue component (0-255)
--@param(a : ?number) Alpha component (0-255), 255 default
function color:init(r,g,b,a)
  self.contents = {r,g,b,a or 255}
end

Methods

We now add a method get to color which returns its contents data member. Unlike instances of the color class, the value returned by get can be passed into love API functions which require color arguments.

--@ret({number}) a color table which can be used by love.graphics functions
function color:get()
  return self.contents
end

Note that we cannot access a color's constants by merely typing colorInstance.contents. This is because in BCS, all methods are public and all data members are protected; i.e., it's data members are accessible only internally, but its methods are accessible both internally and externally. To provide external access to members, we must create getter methods such as this one.

Operators

In BCS, arithmetic operators can be added by defining methods whose names are the lua standard arithmetic metamethod names.

For example, suppose we want to add a function which scales each of a color's coordinates by a number, returning the resulting color. We could add a new method for this, but instead we enable a more concise notation by using the multiplication operator.

--@param(s : number) The scalar to scale by
--@ret(graphics.color) This color, with rgb coordinates scaled by s
function color:__mul(s)
  local c = self.contents
  return color(c[1]*s,c[2]*s,c[3]*s,c[4])
end  

The current supported operators are add, sub, mul, div, and unm.

Abstract Methods

An abstract method is a method which is declared, but not implemented, in a base class; the implementation of an abstract method is delegated to child or descendant class definitions. Classes with unimplemented abstract methods may not be instantiated.

We declare an abstract method as follows:

--@param(x : number) the x position of the mouse
--@param(y : number) the y position of the mouse
widget.OnClicked = {}

A child of widget, command_box, would then implement OnClicked as follows:

function command_box:OnClicked(x,y)
  print(I was clicked)
end

Abstract method implementations are associated with their declarations by name. There is no need to provide type annotations above the implementations, though it doesn't hurt to do so.

Events

An event is similar to an abstract method in that its implementation has been delegated. However, instead of delegating the implementation to an inheriting class, an event delegates its implementation to zero or more external sources which are selected at runtime.

We declare a “Text Changed” event for an edit box as follows:

--Called whenever the contents of the editbox changes
--@param(text : string) The new contents of the editbox
editbox.events.TextChanged = {}

We can now trigger the event internally within the method definitions of editbox by calling TextChanged as if it were a method.

function editbox:OnKeyPress(key,code)
   
  self:TextChanged(self.contents)
   ...
end

Note that event triggers are not included in an object's public interface. When an event trigger is called, it passes the supplied arguments (self.contents in this case) to a list of callbacks which have subscribed to the event.

Events which are published by an object are subscribed to via that object's events interface.

--@param(newText : string) The new contents of the editbox
local function OnTextChanged(newText)
  print(new contents:  .. newText)
end

local myEditBox = require('editbox')
myEditBox.events.TextChanged:subscribe(OnTextChanged)

Each instance of a class which publishes events contains an events field, which contains subscription interfaces for each published event. A subscription interface contains two methods:

subscribe - Adds the supplied argument to the event's list of subscribers.

unsubscribe - Removes the supplied argument from the event's list of subscribers.

Delegates

Each BCS object has a table of delegates, one delegate for each method. Delegates provide a short, convenient means of using the object's methods as callbacks. If a class Foo has a method called Bar, then each Foo object will have a Bar delegate, located at fooObject.delgate.Bar.

fooObject.delegate.Bar is a function defined as follows:

function () return fooObject:Bar(...) end

Each delegate is statically typed in accordance with the method that it represents.

Inheritance

Basic Class System also supports single inheritance. To define a new class which inherits from an existing class, press Ctrl-N. Ensure that “Basic Class System” is selected in the dropdown box. Now, in addition to entering the name of your class into the “class path” box, you must also provide the parent's path in “parent path” box. Don't worry about making a typo while typing the parent path; the BCS plugin will only allow you to inherit from existing BCS classes.

After clicking “Okay”, the BCS plugin will generate a file which looks like this:

local game_object = require('game_object')
local npc = game_object:new()

function npc:init()
  self:init_super()
end

return npc

Note that a call to a method called init_super has been placed at the top of the constructor. init_super is actually the base class constructor. If the base class constructor takes arguments, you will have to provide these yourself. init_super must be called at the top of your constructor, or else your child class will generate an error.

Using Class Instances

To instantiate an instance of your new class, you must first obtain its constructor, which is returned from a call to require("modulename"). Call the constructor to create new instances of you class.

--this is the constructor
local color = require("color")
--calling the constructor instantiates our class
local red = color(255,0,0)

The name of the new class can also be used in annotations. But note that only fully qualified paths may be used; Game Kitchen currently provides no means of creating type abbreviations.

--@var(graphics.color)
local currentColor = red

--@param(c : graphics.color) the new current color to set
local function SetCurrentColor(c)
  currentColor = c
end

Updated