people often ask about object orientation (OOP) and inheritance, but can get confusing or incomplete guides. I want to help everyone understand how prototypes work!
lua does support object-oriented programming (but does not require it), based on prototype inheritance — not classes.
metatables are part of this but also often misunderstood. this is a complete explainer that I wrote on discord and keep not putting here in a clean way. so I am putting it here as it is!
so what is a prototype?
goblin={ sp=42, } function goblin:new(x,y) local obj={x=x, y=y} return setmetatable(obj, {__index=self}) end function goblin:draw() spr(self.sp,self.x,self.y) end gob1=goblin:new(40,40) gob2=goblin:new(60,70) |
alright so this is like 6 different things working together.
1) table with methods: goblin
is a table with property sp
and methods new
and draw
(a method is a function attached to an object)
calling goblin:new(40,40)
is the same as goblin.new(self,40,40)
; self is «the object we are working on»; defining function goblin:draw()
is the same as function goblin.draw(self)
and the same as goblin={sp=42, draw=function(self) spr(...) end, somethingelse=true}
there is a difference between the two methods in my example: when we call goblin:new
, the self
param inside it will be the goblin
table itself (the prototype), but when we have gob1:draw()
then self inside draw will refer to gob1.
2) metatables. there are special methods that lua uses to implement operators (+
, /
, ..
, etc). the method for addition for example is __add
(we call these metamethods). if I want to be able to do vector1 + vector2
for my custom vector tables for example, I need to define a method named __add
but I can’t do it in the vector
prototype table, lua will not look it there; it is required to define these metamethods in a table’s metatable. (the metatable is similar to a class in other object-oriented languages: it is the template, or the definition, or the model for many tables.) so that’s what happens in goblin:new
: I define a metatable with one metamethod and set it on my goblin object.
3) prototype. these are great! if I have 50 goblins on screen and they have different health and coordinates but the same properties for sprite, width, height, etc, and the same methods for update, attack, draw, etc, I would like to define all the shared things in one place, then have custom things in each goblin table. that’s what a prototype is for! here in goblin
example we have sp
and draw
shared, and in a specific goblin table (it starts as obj
on line 6, then it’s gob1
or gob2
in my example) we have specific data like x and y.
much more info on this really useful site: https://gameprogrammingpatterns.com/prototype.html
alright so how do I say «gob1 and gob2 should delegate to goblin prototype for some properties»? I define a metatable with an __index
property. it is called by lua when I do gob1.sp
and sp
is not found inside gob1
. it can be a function (can be useful to have a dynamic property that’s computed from other properties) or another table: the prototype!
more: https://www.lua.org/pil/13.4.1.html
now this is the cool thing with prototype-based object-oriented language that you don’t have in such a nice way with class-based OO languages
what if I want 40 goblins on screen and 10 archer goblins? these should have a different sprite and different attack method.
archer=goblin:new() archer.sp=52 function archer:attack(target) --use the bow end arcgob1=archer:new(20,50) arcgob2=archer:new(40,70) |
look at this from bottom to top:
arcgob1.x
will be 20,y
50- arcgob1’s and arcgob2’s prototype is
archer
, because when we callarcher:new
,self
on line 6 of my first code block points toarcher
arcgob1.sp
is 52 (the value comes from the prototype)arcgob1:attack(hero)
will use ourarcher:attack
method.arcgob1:draw()
will usegoblin:draw
! archer itself is a table that has goblin for prototype
draw
is not found in arcgob1, look at its prototype archer, not found, look at its prototype goblin, found! call it withself
pointing to arcgob1.
result:
Looking at lucky draw and the game with bbs thread title "Fit" Santa
(https://www.lexaloffle.com/bbs/?pid=fit_santa_00-1) was displayed with & quot ; (can’t type it here even in backticks!)
In game code, calling ls()
only returns names of cart files.
To make it possible to write local game launchers and other mini-splores, it would be nice to have a way to get directories too (some of us are hoarders and have to organize carts).
Ideas:
ls() -- same as now ls(0x2) -- get directories only ls(0x3) -- (maybe) get other kinds of files -- (for art tools and such, to build dialogs instead of -i / stdin / drag n drop) -- or ls() -- unchanged lsdir() -- new -- will also need `cd(path)` to go there and list carts, or `ls(path)` |
Thanks for considering this!
To ease the workflow of developing with p8 file, then saving as PNG for sharing, it would be really useful to have a command-line switch to save as PNG (combined with the positional parameter that loads a cart), for example: pico8 game.p8 -save game.png
This would behave like 'SAVE game.png' in the console, adding the .p8 string in the middle, but not prompting for overwrite confirmation (goal is headless/automatic conversion, like -export — of course errors are possible if the cart doesn’t respect limits).
Rationale: I find myself working with both P8 and PNG carts for the same game/project, using the P8 file as authoritative version (limits don’t prevent saving + compatible with all text tools) and also saving as PNG to see what the label looks like and share it when desired. It is cumbersome to work with the P8, save, save as png, confirm overwrite, then have to remember to load the P8 to avoid that the next save saves PNG then have to overwrite the P8. I would prefer to consider the PNG form a compiled form that I don’t edit, and avoid human mistakes by saving it using a command line or makefile.
Another use case from Sam Hocevar on discord: he wanted to convert P8 to PNG to study the compression algo, and had to resort to export to JS, extract cart data, serialise it, save in blank cart, which is quite convoluted.
In the code editor, add a new tab with the first line being a comment line containing only puny caps and spaces, then add a second comment line with regular text. Hover over the tab, the title preview shows the second comment instead of the first. Bug doesn’t happen if the title mixes cases ("--UI helpers" is ok).
I wondered why I was not logged in when opening some links, but I was logged in in other tabs.
Found out that the website answers to both lexaloffle.com and www.lexaloffe.com, but logged in status is not shared.
Possible solutions:
- change cookies to be valid for both variants
- pick one variant as canonical and redirect transparently
Thanks!
Hello! On the Carts category page https://www.lexaloffle.com/bbs/?cat=7&sub=2 , the three featured carts at the top are not real links (a elements), but divs with a custom JavaScript handler. This does not work with all the tools that know about links: ctrl+click to open in a new tab, middle click, long press on mobile, bookmark link, contextual menu, search links in page, etc. Please consider changing this!