Object-Oriented


OOP Tables

Object-oriented programming (OOP) is a way of writing computer programs using objects that have both data (like numbers or words) and actions (like moving or making sounds). It helps in organizing and making programs easier to work with and reuse. Some programming languages are designed with OOP principles in mind, and have classes to easily make objects but Lua does not. However, Lua does support OOP with object tables, metatables, prototypes, and first-class functions. If you don't understand what those mean yet, don't worry, we'll explain!

To create an object table in PICO-8:

obj = {
	sprite = 1,
	x = 10,
	y = 5,
}
obj
sprite 1
x 10
y 5

Tables in Lua can hold all types of data, including functions! In OOP, the term method refers to a function that is inside of an object table.  Here are four ways you can add methods to your table (see Lua Docs):

--1 method inside table
obj = {
	sprite = 1,
	x = 10,
	y = 5,
	draw = function(self) ... end
}

--2 method outside table
obj.draw = function(self, a, b) ... end

--3 method outside table
function obj.draw(self, a, b) ... end

--4 method outside table, hidden self
function obj:draw(a, b) ... end

All four examples will do the same thing: add a draw() method to table obj. It is up to you which you prefer or which works best in your situation. Just notice that the fourth example uses a colon operator ( : ), which implicitly declares a self parameter.

Now that you have created methods in your objects, here are four examples of how to call them:

--1 pass self argument
obj.draw(obj)

--2 implicitly pass self
obj:draw()

--3 pass self and arguments
obj.draw(obj, 1, 2)

--4 with arguments, implicitly pass self
obj:draw(1, 2)

Again, it is up to you and your case to determine which style you want to use because all four examples do the same thing: call the draw method in obj and pass arguments to the function. 


Lua does not have classes (Lua Docs), but it does support prototype-based programming which is a style of OOP and achieves similar goals (code reuse and inheritance).


Now that you know how to create an object table with methods, here is an example of how you might create an enemy object in your game that has update and draw methods:

--object table
enemy = {
	sp = 1,
  x = 0,
  y = 0,
	dx = 0,
	dy = 0,
  update = function( self ) --method 1
    self.x += self.dx
    self.y += self.dy  
  end,
	draw = function( self ) --method 2
		spr(self.sp, self.x, self.y)
	end
}

We can then turn this enemy table into a prototype and create new enemy objects from it. This uses metatables and metamethods, so make sure you understand those before continuing.

--add method to prototype for copying/inheriting
function enemy:new(obj)
	obj = obj or {}   -- if obj isn't passed, create empty object table

	--any key not found in obj,
	--look for it in prototype (this enemy table)
	setmetatable( obj, {__index = self} )

	return obj
end

--create goblin from enemy prototype
goblin = enemy:new()

--specify values in goblin table
goblin.sprite = 3
goblin.x = 6
goblin.y = 10

--or you can pass the values to the "new" method
ogre = enemy:new( {sprite=4, x=40, y=50} )

This example is just to show how goblin and ogre both use the enemy prototype's new() method to be created which leaves enemy unchanged and can continue to be used as the prototype for more enemies. We can add more variables and functions to the prototype enemy table as default values for objects created from it. Since we use this prototype as a metatable, when goblin or ogre tables try to use a key that is not found in its own table, it will look for it in the enemy table and use that.

Prototype

enemy
sprite 1
x 0
y 0
new function
update function
draw function

Object

goblin
sprite 3
x 6
y 10

Object

ogre
sprite 4
x 40
y 50

With the metatable being the prototype, you can call goblin:draw() and even though it is not in the goblin table, it won't error because it will find the draw method in the prototype enemy.




Further Reading


Take this to the next level by reading what Merwok wrote on the BBS that has more explanations and examples of taking OOP in PICO-8 another step farther.


After you've got experience working with OOP, and are feeling ready to tackle the next big step to using OOP while saving many tokens, then you're ready to learn about and use _env.

100

29 Apr 2024

Font