Skip to content

Arx 03 Classes

noooway edited this page Aug 28, 2017 · 2 revisions

Currently we have only a single brick in our game, but we'll need more of them to construct a level. Different bricks share most properties, such as update and draw functions, but differ in position. We can create several bricks by making and maintaining an array of copies of the current brick table. However, the same problem -- maintaining several objects that share a lot of common properties but still have some differences -- can be accomplished much cleaner using classes. For standardization and unification purposes, it is also convenient to define classes for the platform and the ball.

Intro to Lua Class System.

From a technical point, a class in Lua is a table, containing class functions (a.k.a. methods) and class variables. Each instance (a.k.a. object) of the class is also a table, configured such that any missing field is looked at the class-table. This allows objects to have customized properties stored in their own table, while sharing common properties using the class table. In Lua, such arrangement is achieved by configuring so-called __index metamethod in each instance of the class. This __index metametod is either a function or another table that is used when an inquired field is missing in the quered table.

Here is an example:

> class_A = {}
> class_A.name = 'The Class'
> class_A.say_hello_method = function( z ) print( "hello from " .. z.name ) end
> class_A.name 
The Class --(*1)
> class_A.say_hello_method( class_A )
hello from The class   --(*2)
> object_of_class_A = {}
> object_of_class_A.name
nil --(*3)
> object_of_class_A.__index = class_A
> setmetatable( object_of_class_A, object_of_class_A )
> object_of_class_A.name 
The Class --(*4)
> object_of_class_A.say_hello_method( object_of_class_A )
hello from The Class
> object_of_class_A.name = "An object"
> object_of_class_A.say_hello_method( object_of_class_A )
hello from An Object --(*5)

(*1): Field name in the class_A table is set to "The Class".
(*2): We call a function, stored in the field say_hello_method of the class_A table.
It expects a single argument z. We pass class_A itself as such argument.
(*3): We define a table object_of_class_A. An attempt to index it's name field returns nil.
(*4): After __index and setmetatable magic, an attempt to index object_of_class_A.name field doesn't return nil any longer. However, at this point the field name still isn't set at the object_of_class_A, so the attempt to index it results in an invocation of the __index method. In this case, it points to the class_A, where it redirects the query for the name. The value of this field is 'The Class', which is printed in the output.
(*5): We set field name in the object_of_class_A. Now it can be successfully indexed without any redirections. Still, the field say_hello_method does not present in the object_of_class_A and it's value is searched for in the class_A (where __index points to).

There is a syntactic trick in Lua, which simplifies writing and calling methods — the colon syntax. When a method is prefixed by the colon instead of the dot, a table before ':' is substituted as the first formal parameter of the method:

some_table:function_inside_table() 
-- is equivalent to
some_table.function_inside_table( some_table )

That way it is possible to write:

> class_A:say_hello_method()
hello from The Class
> object_of_class_A:say_hello_method()
hello from An Object 

It is also possible to define methods using colon syntax. In that case, a first formal parameter with name self is implied.

function some_table:function_inside_table( arg1, arg2, etc )
  ..... 
end
-- is equivalent to
function some_table.function_inside_table( self, arg1, arg2, etc )
  .....
end

In our case:

> function class_A:self_say_hello_method( hello_ending )
>> print( "self-hello from " .. self.name .. hello_ending )
>> end
> class_A:self_say_hello_method( "!!!" )
self-hello from The Class!!!
> object_of_class_A:self_say_hello_method( "???" )
self-hello from An Object???

The dot and the colon forms can be mixed freely:

> object_of_class_A.self_say_hello_method( object_of_class_A, "!!!" )
self-hello from An Object!!!

A common Lua idiom for class constructor looks like this:

function Class:new( o )
   o = o or {}
   setmetatable(o, self)
   self.__index = self
   o.name = o.name or "Class"
   o.property_1 = o.property_1 or 10
   o.property_2 = o.property_2 or 20
   .....
   return o
end

setmetatable(o, self) sets Class as the metatable for an each new Class object. self.__index = self sets __index metamethod in the Class to point to Class itself.

To implement inheritance, it is necessary to configure __index metamethod of the child class to point to the table representing the parent class. In this case it can be done simply by SubClass = Class:new(). Then it is possible to proceed defining SubClass methods. I won't need this, since I'm not going to use inheritance in this project.

One last thing: in Lua syntax some_function{} is identical to some_function( {} ). Such shorthand is commonly found in calls to class constructors, so don't be surprised.

Defining the game object classes.

A common convention is to start class names from the capital letter. Let's convert the ball module to the Ball class using class constructor definition idiom:

local vector = require "vector"  --(*1)
local love = love
local setmetatable = setmetatable
.....
function Ball:new( o )
   o = o or {}
   setmetatable(o, self)
   self.__index = self
   o.name = o.name or "ball"
   o.radius = o.radius or 10  -- (*2)
   o.position = o.position or vector( 300, 300 ) --(*3)
   o.speed = o.speed or vector( 300, 300 )
   return o
end

(*1): I'm loading HUMP vector module, which contains a class definition for a 2d-vector.
It is used at (*3) to set speed and position of the ball.
(*2): All the necessary ball properties are placed in the constructor.
(*3): Position and speed are now vector objects.

The update and draw functions now become methods, defined with the colon syntax:

function Ball:update( dt )
   self.position = self.position + self.speed * dt
end

function Ball:draw()
   local segments_in_circle = 16
   love.graphics.circle( 'line',
                         self.position.x,
                         self.position.y,
                         self.radius,
                         segments_in_circle )
end

No other changes are needed for the Ball. A procedure to define the Brick and the Platform classes is mostly similar.

After the classes are defined, we need to call constructors of the classes in love.load() to create game objects, that we are going to update and draw:

function love.load()
   ball = Ball:new{}
   platform = Platform:new{}
   brick = Brick:new{}
end

In love.update and love.draw, the only change is basically the replacement of '.' by ':' .

function love.update( dt )
   ball:update( dt )
   platform:update( dt )
   brick:update( dt )
end
 
function love.draw()
   ball:draw()
   platform:draw()
   brick:draw()
end

While syntactically simple, it marks a shift in point of view from tables and functions in them to classes and methods.

Possible todo: Use hump.classes instead.

    Home
    Acknowledgements
    Todo

Chapter 1: Prototype

  1. The Ball, The Brick, The Platform
  2. Game Objects as Lua Tables
  3. Bricks and Walls
  4. Detecting Collisions
  5. Resolving Collisions
  6. Levels

    Appendix A: Storing Levels as Strings
    Appendix B: Optimized Collision Detection (draft)

Chapter 2: General Code Structure

  1. Splitting Code into Several Files
  2. Loading Levels from Files
  3. Straightforward Gamestates
  4. Advanced Gamestates
  5. Basic Tiles
  6. Different Brick Types
  7. Basic Sound
  8. Game Over

    Appendix C: Stricter Modules (draft)
    Appendix D-1: Intro to Classes (draft)
    Appendix D-2: Chapter 2 Using Classes.

Chapter 3 (deprecated): Details

  1. Improved Ball Rebounds
  2. Ball Launch From Platform (Two Objects Moving Together)
  3. Mouse Controls
  4. Spawning Bonuses
  5. Bonus Effects
  6. Glue Bonus
  7. Add New Ball Bonus
  8. Life and Next Level Bonuses
  9. Random Bonuses
  10. Menu Buttons
  11. Wall Tiles
  12. Side Panel
  13. Score
  14. Fonts
  15. More Sounds
  16. Final Screen
  17. Packaging

    Appendix D: GUI Layouts
    Appendix E: Love-release and Love.js

Beyond Programming:

  1. Game Design
  2. Minimal Marketing (draft)
  3. Finding a Team (draft)

Archive

Clone this wiki locally