Log In  


Hello everyone.

As I strive to become better at programming, theres one thing that is annoyingly very recurrent in my code which are counters.

I have counters for playing a sound effect only when changing scene, or when a collision first happens.

I have counters to manage the speed of swapping animation frames

I also have counters just to tell the passage of time or trigger elements or behaviours after a while.

My general approach is: Create a global time variable with the name for the thing it's timing

animationcounter = 0

Then on update or draw I have some sort of

if animationcounter <= 20 then
   sp +=1
   animationcounter = 0
else
   animationcounter +=1
end

Is there a better way to do this that can be used for all counters necessary? With a function? or maybe an object where we instantiate counters or something?

1


I am using an object that can obtain a counter, duration, start flag, and end flag.
It also has a function that obtains a current value separate from the counter from the ratio of the count and duration.

I will be using the following libraries and adding commands and functions.

KNUTIL Library

https://www.lexaloffle.com/bbs/?tid=32411

  • SCENE MANAGER
scmd[[
anm st animationcounter 20
]]
function animationcounter()
 if _lst then
  sp+=1
  scmd[[anm st animationcounter 20]]
 end
end

No additional global variables are required, and the counter will count automatically, but one line to generate the SCENE and one line to process the SCENE together in foreach are required.

uscene=mkscenes({'anm'})

function _update()
 foreach(uscene,transition)
end

postscript:
When doing sprite animation, it may be simpler to calculate the sprite ID from the passage of time rather than incrementing it.

In that case, the code would be

scmd[[
anm st animationcounter 0
]]
function animationcounter()
  sp=_cnt\20 --_cnt increments each time this function is processed.

--  sp=_cnt\20%sprite_end -- When looping an animation

--  sp=_cnt>0 and min(_cnt\20,sprite_end-1) or sprite_end-1 -- When stopping an animation

end

(Note that sp will become 0 in this code)


3

It depends on your programming preferences. If you're satisfied with what you're currently doing, it's probably fine to keep doing it. If not, here's a couple alternatives that come to mind:

-- Attaching the timer to the relevant object

Basically, just add a value called "counter" to the game object in question and handle it whenever you update that object.

This is the method I usually use.

-- Make a table of counter objects and update all of them at once.

A version that uses named counters

counters = {animation={0,20,animfunc}, obj1={0,20,obj1func}, obj2={0,20,obj2func}}
function update_counters()
  for _,v in pairs(counters) do
    v[1]+=1
    if(v[1]>=v[2])v[1]=0 v[3]()
  end
end

A version that uses numeric indices instead

counters = {{0,20,animfunc}, {0,20,obj1func}, {0,20,obj2func}}
function update_counters()
  for _,v in all(counters) do
    v[1]+=1
    if(v[1]>=v[2])v[1]=0 v[3]()
  end
end

1

Thank you for your repliers

kimiyoribaka, after writing this post I was starting to play with something similar. Thank you for your version. It's easy for me to understand and also validating of my thought process.

shiftalow, wow, that looks advanced for me. I have no idea what the code does, it's using some patterns I have never seen. I have had a look at the library and it looks like thats the source of that confusion. Thank you for introducing that to me. I will try and learn it


Try using timers with time(), that way you uncouple computation speed and perceived execution speed.

Function _init()
animtimer=0
anim_duration=.2--.2 seconds
End

Function _update()
If time()>animtimer then
animtimer=time()+anim_duration
--animate stuff
end
End

Code was written on my phone,so maybe redo :D

With time() you get the time in seconds since the start of the program. You add your custom duration to time() whenever the timer variable exceeds time(). So if your games frame rate got really slow you're animations(or whatever you'd like to use this for) would still work as expected.

I hope you get the idea and I hope it helps :)


@taxicomics
I'm sorry if this comes across as mean, but I would recommend that you read the manual and do testing before you try to use wisdom from other game engines.

The time() function doesn't give you the actual time in seconds. It gives you the time in seconds that would have passed if there wasn't any lag. The actual way it's calculated is by adding 1 to a value every time the _update() or _update60() function is called, then dividing by the requested rate of updates (30 if _update(), 60 if _update60()). That's why the manual specifically says that it's not a real-world time.

The best way to make sure animations work as expected is to keep the timer calculation in the _update() or _update60() function but keep the actual drawing part in the _draw() function. That helps pico-8 stay as accurate as possible, by allowing pico-8 to skip the part that, for purposes of accuracy, is least important in the event of lag.


2

I use a mix of timers depending on what's happening. Sometimes the way you're doing it is the easiest and most understood. I do that all the time.

But I tend to think in terms of "real time" when thinking about timers in my games, so like "shoot bullets every 5 seconds" or something like that. I manage those timers a couple different ways.

One method of functions I wrote uses the time() function like was mentioned above.

function check_timer(tm) return t()>=tm end
function set_timer(tm,i) i=i or t() return i+tm end

mytimer = set_timer(10) --set timer for 10 seconds

if check_timer(mytimer) then
	-- do something
end

Just pass the number of seconds you want to measure into the set_timer() func and then check it with the other. The check_timer() returns TRUE if the number of seconds has passed. Put them inside whatever loop you're using. I use these all the time in my games. They're handy and I still understand them months later when I'm reading my own code!

The other method I sometimes use is coroutines. Using the yield() function within one can give you good delays and timing. But I mostly use these when I'm timing out some sort of sequence, "wait 1 sec, fire bullet, wait 2 sec, move left, wait 3 sec, etc..." I don't use them very often because it hurts my head and it's very easy to get lost in your own spaghetti. I probably wouldn't worry about them at this point but they can be handy as you make more involved games.

But like was mentioned above...whatever you understand and works for you is what's probably best. I know it can feel wrong just having a bunch of increment counters all over the place but "if it ain't broke..." - I'll file it under the "don't pre-optimize" trap which I get caught in all the time. Unless you're banging up against token limits or struggling with maintenance, with PICO8 often it's the simpler the better.


@kimiyoribaka Learnt a thing today, didn't know that was the case. The solution is still serviceable, if a project struggles that much with lag it is probably a bigger issue than the milliseconds lost by working with timers ^^ Thx for the info



[Please log in to post a comment]