Log In  

Hello everyone!

I am having plenty of fun with pico8 but I feel as though i have gotten to the point where i can comfortably use lua to make a decent game. However, when i peek inside some big games like Celeste and Dank Tomb, it seems to chuck all i know out the window. I investigated and found out about metatables.

My first question would be should i learn about them? Will it help me get to the next stage of pico8 programming?

My second question is regarding an example of a 'simple' metatable:

-- meta class
rectangle = {area = 0, length = 0, breadth = 0}

-- derived class method new

function rectangle:new (o,length,breadth)
   o = o or {}
   setmetatable(o, self)
   self.__index = self
   self.length = length or 0
   self.breadth = breadth or 0
   self.area = length*breadth;
   return o
end

-- derived class method printarea

function rectangle:printarea ()
        print("the area of rectangle is "..self.area)
end

r=rectangle:new(nil,10,20)
r:printarea()

I have no clue what anything here does, so would it be possible if someone could go through line by line and explain.

Thanks a lot!
(If the answer to question 1 is no, could you give some pointers as to where to find something that will help)

P#77058 2020-05-23 09:25 ( Edited 2020-05-23 17:31)

:: merwok
1

I think that code is a little confusing, given that Lua does not use classes for its object orientation but prototypes and metatables.

Some definitions:

A prototype is a table that is used as a base for other tables (meaning that these other tables are made up of their own data + the data in the prototype). I mention it here but let’s ignore it for now.

An object is a table that contains data and methods (for example, player.sprite can be a number and player:draw() is a method call to draw the player (with something like spr(self.sprite,self.x,self.y)). The benefits are abstraction and reuse. Abstraction: if I change my sprite to be 2x2 tiles big, I don’t have to hunt for all the functions where I draw the player, but I change the draw function that I defined in my metatable. (That said, I may have to change other things like collisions, but at least I have grouped together some things that belong together.) Reuse: I can have a number of objects (players, particles, bullets…) that share the same update/draw code by using the same metatable.

A metatable is a table that holds methods (functions) for another table, including special metamethods that are called for operators (example: vector1 + vector2 uses the __add function in the metatable of vector1; see code here)

So should you use them? Metatables are convenient to put all of an object’s behaviour in one place, and for reusing it for multiple objects. It is worth learning about them to understand code you read, but not necessary if you are getting started.
You can focus on what you enjoy (animations, music, gameplay, multiplayer…) and come back to this when you feel a need for it.

To give a concrete example, here is code without and with metatables:

def _init()
 sun={x=64,y=64}
 earth=make_planet(sun,40,0.1)  --orbit center, distance, turn increase
 venus=make_planet(sun,25,0.1)
 moon=make_planet(earth,5,0.01)  --not a planet but works the same way
end

def _update()
 move_planet(earth)
 move_planet(venus)
 move_planet(moon,false)  --need extra option for moon
end

def _draw()
 draw_sun(sun)
 draw_orbit_oval(earth)
 draw_orbit_oval(venus)
 draw_orbit_circle(moon)
end

This is all fine and lets me write code to understand circles, ellipses and basic trigonometry to make planets move around the sun. When I get annoyed that things are defined in different places, I rewrite the functions to be methods in a couple metatables:

def _init()
 sun={x=64,y=64}
 earth=planet:new(sun,40)
 venus=planet:new(sun,40)
 moon=satellite:new(earth,5,0.01)
end

def _update()
 earth:update()
 venus:update()
 moon:update()
end

def _draw()
 draw_sun(sun)
 earth:draw()
 venus:draw()
 moon:draw()
end

In this example sun still uses functions (it’s a unique object so there is nothing to reuse), but the other celestial bodies are created with a custom method. Now the decision to draw elliptical or circular orbit is inside the metatables instead of being hard written in _update/_draw.

Hope this does not contain wrong statements and is helpful! If you want more, we could copy the vector metatable from the thread I linked and go over it line by line.

References:
https://www.lua.org/pil/16.1.html
https://www.lua.org/pil/16.2.html

P#77102 2020-05-24 04:14

@merwork Thankk you, I think i was very much over-complicating them, so this makes it very clear. Now i understand the purpose, i realise that they aren't as game-changing as i though they were. Thanks!

P#77115 2020-05-24 07:51
:: merwok
1

For anyone interested, this is a better explanation than my message: https://www.lexaloffle.com/bbs/?tid=3342

P#77148 2020-05-25 02:35
:: shy
1

Due to the existence of foreach() and all(), I haven't found much use for metatables in Pico-8. I'm sure they're helpful if you want operator overloading (+, -, [], etc) or a very specific class-like hierarchy, but most classes don't need that. A lot of the examples here and elsewhere of how you can use metatables are better optimized with large tables of similar objects, and global functions that work with them.

Need a special kind of update function for some of those objects? Just re-set the function directly, like this:

objects = {}                                 -- all of your "object"-class tables go here
function common_update_fcn(o) ... end
function new_obj(x,y)
 o = { x=x, y=y, update=common_update_fcn }  -- all "object"s have: .x, .y, and .update
 add(objects, o)                             -- and get added to the global group, so they stay alive
 return o
end

obj = new_obj(5,5)
obj.update = function(o) ... end              -- magic metatable-like inheritance on everything else!
function _update()
 for o in all(objects) do o:update() end
 -- or if all objects use the same function:
 foreach(objects,common_fcn)
end

With metatables you could also override the common update function, but why bother with the expensive setup and function lookup failures when you can just redefine a function this easily? I think this is object-oriented enough for most people, it's just a little more type-unsafe, since each global function just assumes the generic object it gets has the necessary variables.

P#77192 2020-05-25 21:43 ( Edited 2020-05-25 21:48)
:: merwok
1

I agree with you! And it does not take a lot to push your version a little and make it safe: https://www.lexaloffle.com/bbs/?tid=30039

P#77207 2020-05-26 02:25

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2020-07-11 01:38 | 0.030s | 4194k | Q:25