Log In  

Sorry if this is really dumb - I don't think I've seen this mentioned anywhere but happy to be corrected!

In our games so far, I've used init() to set up 'global' variables and do the one-time-only kind of stuff. But I've seen other code snippets where the same kind of initialisation stuff is done simply by having lines of code at the 'top' of the code area.

Is there a difference? I read that init() only gets called once - if it exists, does init() get called before any 'code at the top'? What is the order of this stuff? Is there a reason to use one approach over another (other than I personally think code 'at the top' looks untidy, I prefer all the init type stuff to be at the end of the file out of the way).

Obviously I could sit and play with this and 'black box' it all out, but I think that not knowing this seemingly fundamental point might indicate a basic misunderstanding of the system on my part :-D

If there's a good resource for this 'fundamentals' stuff, happy to get a pointer; my go-to at the moment is mostly the manual and the api cheat sheet.

P#58323 2018-10-24 05:38 ( Edited 2018-10-24 09:38)

@zep @itsalina is another spammer.

P#58342 2018-10-24 10:32 ( Edited 2018-10-24 14:32)

@robsoft to answer your question --

The way pico-8 works is that it will first run everything in the file. So if you have "x=64" in there, that variable gets set to 64.

The same is true for _init, _update, _draw and all the other function definitions. Those functions get saved to those name spaces.

THEN, after that pass is done, it calls _init() (if it has been defined).

A good best practice is to set up your _init so that you can just call "_init()" in code and it will put things back to the starting state. Then you can call it every time there's a game over and the player wants to restart..

Keep in mind, doing Ctrl+R (or choosing "Reset Cart" in the pause menu) will re-load the entire file anyway. Try this:

x=64
function _init()
    x=10
end
print(x)
function _update()
end

See how it always prints '64' when you reset? That's because it re-parses the file every time. (or maybe it sets it back to that original start as though it was re-parsed -- whatever).

Anyway here's one use case for splitting things up..

There could be some things you don't want to reset to the start after a game over. Like the player's score from their last attempt maybe? A lot of arcade games do this; you still see the most recently achieved score at the top while it plays the attract mode, and it just resets to 0 when someone actually starts a new game.

For the player's 'high score' you can save that to cartdata so it persists after shutdown. But to make it so that the player's 'most recent score' of the session stays on the screen after a game over, you could set the 'score' to 0 in 'code at the top' instead of _init. Then, when you call _init(), it could reset things.. like reverting the game state to the 'title screen',etc.. but things defined in 'code at the top' would stay the same since they are only executed when you run the game (or reset).

You could use that technique for other details too.. like easily giving the player a chance to return to their last achieved stage after a game over, or default to their previously chosen character -- whatever things you might want to stay persistent between game overs within the same play session.

P#58343 2018-10-24 10:46 ( Edited 2018-10-24 14:50)

Thanks @enargy, I think that makes sense :-)

What we've been doing in the mummy games is to basically set it up as a 'state' machine - _init() does all the initialisation code and sets up a state of 'splash screen'. Then we have a simple _update() and _draw() that look at the current 'state' against some 'constants' and decide which sort of 'sub function' (not sure of Lua parlance) to call... ie;

function _draw()
  if state==gs_splash then draw_splash()
  else if state==gs_menu then draw_menu()
  else if state==gs_ingame then gs_draw_game()
  else if state==gs_nextlevel then draw_next_level()
  else if state==gs_gameover then draw_game_over()
  end
end

etc, and similarly with update too (update_splash(), update_menu() etc. The individual update routines are responsible for changing the state variable, of course).

(This is one reason why I'd love you to add a 'switch/case' statement, @zep, if you're reading. And maybe there's a better way to have readable constants than just using global variables that I won't change)

Our games work like your last few paragraphs - although we save to cart data, we have a few bits of state regarding the cart since it was booted etc. We don't ever have a reason to want to run init() again to reset, but I can see the point of that and it's good to know that this is how it works. I might need that one day.

So, thanks for the detailed explanation, that makes sense and I can see the value of having that. Cool. :-)

P#58345 2018-10-24 11:24 ( Edited 2018-10-24 15:24)

No need for switch/case IMHO. Here is how your code should look like:

function _draw()
    state.draw()
end

That’s all! Just declare "function gs_splash.draw() ..." instead of "function draw_splash() ..." and so on.

P#58393 2018-10-25 06:19 ( Edited 2018-10-25 10:19)
2

Yes, this is a good case for some more object-oriented thinking. Or you could simply have different _draw() functions, and do this when changing states:

_draw = state_draw

Or like this: http://pico-8.wikia.com/wiki/Yet_Another_Multiple_States_System

Or like this: http://pico-8.wikia.com/wiki/Multiple_States_System

Adding a switch/case construct will be pretty hard since there is no such thing in Lua.

P#58396 2018-10-25 08:04 ( Edited 2018-10-25 15:03)

ok.... bit of reading to do there to get my head around OOP in Lua/Pico. I understand OOP (big Pascal/Delphi/C# background) but not touched it here at all, other than seeing it in passing in Bridg's tutorials.

I didn't realise you could effectively use a variable to store a pointer to a routine...

Thanks folks, that gives me something to go read-up on :-)

P#58397 2018-10-25 09:03 ( Edited 2018-10-25 13:03)
1

There can be upsides to using OOP.. but the scope of a pico8 cart is so small that I'd definitely weigh benefits/costs before trying to use OOP in a pico-8 project..

I think the biggest benefit is that some people are just wired in a way that they want to use it, so that's the most efficient way for them.

If you really wanted some case/switch behavior, you'd could put that set of logic in its own function. Then you could use ifs with a 'return' at the end of each thing where you didn't want fall-through.

Reassigning _draw and _update as needed like @tobiasvl suggested is one way to go. I usually do that for games that have sub-games; the minigame gets its own init() that's called, and part of the init sets _draw and _update to the draw/update functions for that minigame. You could do the same thing for your states.

If you want an example:
Monster Mix and Match Jam Gallery Cart -- this uses that 'reassign _draw method'

Oh, one thing about that though -- you don't want to call it with (). I mean, you could if 'state_draw' was a function that returned a function based on the current state, but that's sort of silly. So normally you'd just want to reset the pointer. So instead of the example @tobiasvl gave:

_draw = state_draw()

You'd want to do this:

_draw = state_draw

Another option is to organize things like this:

function _init()
 --states
 title, win, lose = 1, 2, 3
 draw_funcs = {[title]=titledraw, [win]=windraw, [lose]=losedraw}
 state=title
end
function _update60()
 if(btnp(⬆️))state=title
 if(btnp(⬅️))state=win
 if(btnp(➡️))state=lose
end
function _draw()
 draw_funcs[state]()
 -- or draw_funcs.state() would work too
end
function titledraw()
 cls(1)
 print("title",56,64,7)
end
function windraw()
 cls(9)
 print(" win",56,64,5)
end
function losedraw()
 cls(8)
 print("lose",56,64,7)
end
P#58403 2018-10-25 10:20 ( Edited 2018-10-25 14:26)

Yeah, sorry, that was a typo :D I edited my comment before I read your reply.

P#58406 2018-10-25 11:03 ( Edited 2018-10-25 15:03)

Ok, there's food for thought there! Thanks everyone. I don't know how much use they will be, but I picked up a couple of Lua books from a cheap s/hand reseller too - Lua Reference Manual 5.1 and Programming in Lua, 4th Ed - both from Roberto Ierusalimschy. I know Pico isn't necessarily a strict version of Lua, but I thought some of the topics and ideas might help.

Having said that, your point @enargy about people being wired in a certain way - using Pico the way we have done so far kind of makes a lot of sense to me.
I think understanding the way it does OOP is more interesting from the point of view of understanding other people's code & ideas, than it is for possibly changing how I might approach things myself. Unless I see something that really makes life easier/cheaper on the tokens etc :-)

P#58444 2018-10-26 07:00 ( Edited 2018-10-26 11:00)

It's the 'functions as first class values' thing that has most done my head in (I'm a bit old-school, I do pointers and byte-orders but some of this stuff here is a bit wibbly-wobbly for me).

There looks to be a decent chapter in the 'Programming In Lua' book about this, and how Lua does closures. Light bit of train reading for the near future :-)

P#58445 2018-10-26 07:09 ( Edited 2018-10-26 11:09)

Hopefully those books help give you some ideas. I would want to warn you that pico8 lacks some lua features/built-ins while adding its own things, and there are going to be patterns that make sense in one that won't translate well to the other.

P#58453 2018-10-26 13:27 ( Edited 2018-10-26 17:27)
2

Responses on this thread pretty thoroughly describe the behavior, but I haven't seen the primary purpose of _init() mentioned: _init() can call functions defined anywhere in the code, whereas a given line of "top level" code can only refer to functions defined above that line.

This is because it's the "top level" code's job to define those functions. When you run a cart, all "top level" code is evaluated, including "function" statements that define function objects and assign them to global symbols (the names of the functions). Then (if _init, _update/_update60, and _draw are defined) Pico-8 calls _init() and starts the game loop.

This makes _init() consistent with _update() and _draw() is how it can access the global namespace, and allows you to move it around without breaking it. Any function calls in top level code must be careful to appear after those functions are defined.

P#58497 2018-10-27 23:17 ( Edited 2018-10-28 03:17)

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-28 10:46:19 | 0.038s | Q:33