Classes

A class is the template or blueprint that object instances are built from. A class defines variables and functions that every object made from that class is guaranteed to have. Each object, however, has its own copy of these variables, independent of each other.

For example, if we have a class to represent the enemies in a game, we can assume that each enemy has some health, an attack value, and some defense value. This is pretty easy to code up:

Enemy = { }
Enemy.health = 200
Enemy.attack = 4
Enemy.defense = 20

This Enemy table will serve as the blueprint for all enemy objects. So, how can you create new objects from this blueprint? In OOP terms, a constructor is needed. A constructor is a function that instantiates a new object from a class. In Lua, this constructor is just a function that does some special things; by convention, this function is usually called new. For the enemy class, it's going to be Enemy.new.

The constructor for the enemy object will need to take two arguments. The first argument is the actual Enemy table; the constructor needs to know that this is the class being used to instantiate the new object. The second argument is an optional table, which represents the object to create. If no table is provided, a new table (object) will be created.

The constructor needs to assign the object instance's meta table to be the Enemy table. Once the meta table is set, assign the __index meta method to be the same as the __index meta method of the Enemy table:

-- By convention, the first argument should be names self.
-- The reason for this will be explained later in this section
Enemy.new = function (self, object)
object = object or {} -- Use provided table, or create new one
setmetatable(object, self) -- Assign meta table
self.__index = self
return object
end

This code sets the new object's meta table to be the Enemy table. After the meta table is set, the __index meta method of the object to be the Enemy table as well. Whenever a field is accessed on the new object, if that field does not exist, it will return the Enemy table's copy of that field.

The constructor, Enemy.new, can be called like any other function. The first argument is mandatory, but the second argument is optional. The following code creates three objects from the Enemy class. Two of the three objects have unique health values:

grunt = Enemy.new(Enemy) -- Health is stored in "Enemy"
miniBoss = Enemy.new(Enemy) -- Health is stored in "Enemy"
boss = Enemy.new(Enemy, { health = 500, defense = 100 } ) -- Health is stored in "boss"

miniBoss.health = 250 -- Health is now stored in "miniBoss"

-- grunt does not have a health variable, so the enemy table health is returned
print ("grunt health: " .. grunt.health)
-- miniBoss has a health variable, it was created in the above assignment
print ("mini boss health: " .. miniBoss.health)
-- boss also has a health variable, so the boss table health is returned
print ("boss health: " .. boss.health)

Objects, by definition, combine state and logic. So far, the Enemy class only contains state. The following code adds a hit function to the Enemy class. This function would be called whenever the player hits an enemy, causing damage. As such, the function will take two arguments. The first argument is the table that represents the enemy being attacked (in this case, grunt or boss) and the second argument is how much damage is being done:

-- By convention, the first argument should be names self
-- The reason for this will be explained later in this section
Enemy.hit = function(self, damage)
damage = damage - self.defense
if damage < 0 then
damage = 0
end
self.health = self.health - damage
end

Even though the hit function is a field inside the Enemy table, we never reference Enemy.health directly. Instead, we use the first argument of the function, self, to determine which enemy to attack. This is because the Enemy table is a class not an instance. The Enemy instances are grunt or boss. The first argument to this function is expected to be an enemy instance.

The following code snippet shows how we can use the new hit function. Because the function belongs to the Enemy table, we call Enemy.hit. Unlike the constructor, the first argument to the function is an instance of the Enemy table not the table itself:

print ("Hero attacks both boss and grunt")

Enemy.hit(boss, 50)
Enemy.hit(grunt, 55)

print ("grunt health: " .. grunt.health)
print ("boss health: " .. boss.health)

The enemy table only set its __index meta method not the __newindex meta method. This means before the hit function executes, grunt.health actually returns the health field stored in the Enemy table. But the hit function contains the following piece of code:

self.health = self.health - damage

When the function is called self is a reference to grunt, a new field named health is added to grunt. After that line of code executes, grunt will have its own health field and no longer return Enemy.health. This all works because the __index meta method is set, but the __newindex meta method is not.

While the code presented in this section has been object-oriented, the syntax has not. Lua actually provides some syntax sugar for working with objects. The next section talks about Lua's syntactic sugar for OOP programming and the self argument.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.27.72