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)
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
@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!
For anyone interested, this is a better explanation than my message: https://www.lexaloffle.com/bbs/?tid=3342
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.
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
[Please log in to post a comment]