Rants + suggestions

General discussion about LÖVE, Lua, game development, puns, and unicorns.
spectralcanine
Citizen
Posts: 65
Joined: Sat Dec 22, 2012 8:17 am

Rants + suggestions

Post by spectralcanine »

Some rants, suggestions below.

There are 3 tile map scrolling tutorials. One even goes as far as to call itself "efficient", because it uses a sprite batch. Can you see what's common in them?
They go and move all of the sprites that a tile map is made from.
How is that anything even close to being efficient? That's the way you would draw on the CPU, with no hardware acceleration. It is now the year 2013, so let's get a little updated, ok?

Code: Select all

love.graphics.translate(x, y) -- A little more "efficient"
If you want something more in-depth, go look at the Camera tutorial, because it already explains this.

The Box2D (aka physics) callbacks tutorial doesn't show the last argument to postSolve. This argument is the actual impulse of the collision response, and it's quite important for anything that reacts to it (e.g. damage).

love.run: it is bad. More precisely, the dt parameter sent to love.update is bad.
If you are making any sort of movement over time that requires accuracy, you will see why.
The only way to have such movements not explode in your face badly (change over time), is to run the update loop at a constant rate, decoupling it from the drawing function.
The issue with a constant dt, is that moving graphics get "jittery", and don't move as smoothly. This is because you need interpolation between updates, since (most likely) the rendering function will run at a different speed.
If you can guarantee they both run at the same rate, all the good for you, however I don't suggest forcing this. Instead, the drawing routine simply gets an alpha value in the range of [0, 1] and you interpolate between movements using it when drawing.
Here is a love.run variant that does all this.
Note that dt in this variant defines the frames per second. 1 / 60 = 60 FPS for the update function (but not related to the drawing function).
maxframetime defines the biggest difference of seconds between frames.
If you don't put a maximum, and your game gets stuck for some time, your update function will suddenly be called many times. An example for this happens when in Windows you grab the title part of the Love2D window, wait any amount of time you want to, and then release it.

Code: Select all

function love.run()
    math.randomseed(os.time())
    math.random() math.random()

    if love.load then love.load(arg) end

    local dt = 1 / 60
    local maxframetime = 0.25
    local time = 0
    local currenttime = love.timer.getTime()
    local accumulator = 0
    
    while true do
        if love.event then
            love.event.pump()
            for e,a,b,c,d in love.event.poll() do
                if e == "quit" then
                    if not love.quit or not love.quit() then
                        if love.audio then
                            love.audio.stop()
                        end
                        return
                    end
                end
                love.handlers[e](a,b,c,d)
            end
        end

        local newtime = love.timer.getTime()
        local frametime = newtime - currenttime
        
        if frametime > maxframetime then
          frametime = maxframetime
        end
        
        currenttime = newtime
        accumulator = accumulator + frametime
        
        while accumulator >= dt do
          if love.update then love.update(dt) end
          
          time = time + dt
          accumulator = accumulator - dt
        end
        
        local alpha = accumulator / dt
        
        if love.graphics then
            love.graphics.clear()
            if love.draw then love.draw(alpha) end
        end

        love.timer.sleep(0.001)
        
        if love.graphics then love.graphics.present() end
    end
end
Now suggestions.

Sprite batches, or "Why must you use them".
Drawing works on your graphics card. Your graphics card likes to get a low amount of big batches of data, instead of a big amount of small batches of data.
Sprite batches take all your sprites, and draw them in one go, conforming with what your graphics card wants.
If you ever tried to render a couple of thousands or more of sprites one by one, and your code became slow, this is why.

Now, using sprite batches is sometimes less obvious than using sprites alone, so here are a few suggestions how to make them easier to use.

You can only have one texture per sprite batch. Since you most likely need more than one, you will need to use a texture atlas.
A texture atlas is a big image, that has all your smaller images in it.
You can create a texture atlas in a program designed for it, or just about any drawing program.
However, there is another way, which people seem to shy from for some reason, and that is dynamically creating it in your code.
That is, you accept a list of images, and you create one big image that holds all of them.
The idea is to generate a blank (that is, black) image that you know is big enough to hold all your images. For example, a 512x512 image can hold 256 32x32 images.
You then fill it with your textures (ImageData:paste), and at the same time keep a map, that maps from image names / indices / anything you like, to the quads that describe them in your big image.

Here is a little sample, assuming no class() (plain old Lua):

Code: Select all

TextureAtlas = {}

-- "size" is the size of the texture atlas, "textureSize" is the size of each image and "textures" is an array of file names
function TextureAtlas:new(size, textureSize, textures)
    local self = {imageData = love.image.newImageData(size.x, size.y), image = {}, textureMap = {}}
    local w = math.floor(size.x / textureSize.x)
    local h = math.floor(size.y / textureSize.y)
    
    for i, v in ipairs(textures) do
        local imageData = love.image.newImageData(v)
        local offset = (i - 1) * textureSize.x
        local x = math.floor(offset % size.x)
        local y = math.floor(offset / size.x) * textureSize.y
        
        -- Paste the image to the texture atlas
        self.imageData:paste(imageData, x, y, 0, 0, textureSize.x, textureSize.y)
        
        -- And add a quad to the mapping table
        self.textureMap[v] = love.graphics.newQuad(x / textureSize.x, y / textureSize.y, 1, 1, w, h)
    end
    
    self.image = love.graphics.newImage(self.imageData)
    self.image:setFilter("nearest", "nearest")
    
    setmetatable(self, { __index = TextureAtlas})
    
    return self
end

function TextureAtlas:getImage()
    return self.image
end

-- Get the quad that maps to textureName
function TextureAtlas:map(textureName)
    return self.textureMap[textureName]
end
Adding, editing and removing sprite images from the sprite atlas after creation is left for you to implement (it is quite easy to code, but I never needed it so I never coded it).

Mapping coordinates to sprites in sprite batches.
At first, it might seem hard to add, edit or remove specific sprites from a sprite batch (and there were many questions about this), but it is actually very easy.
The idea is, like the texture atlas example above, to map the sprite indices inside the batches to something you can easily use.
As an example, if you have a tile map, it makes sense to map sprites in the batch to [X, Y] coordinates, so let's do that.

Code: Select all

tileMap = {}
  
for y = 1, height do
  for x = 1, width do
    -- Let's get the texture quad we want from the texture atlas
    local quad = textureAtlas:map(...)
    -- Our id for this sprite inside the batch
    local id = batch.addq(quad, x, y)
    
    -- Map this batch ID to its [x, y] coordinate, and also add the tile type for later processing
    tileMap[x .. "x" .. y] = {id = id, tiletype = ...}
  end
end
And with that simple code, we now have random access to the sprite batch.

As a continuation, you might have noticed I used the [x, y] numbers directly in the sprite position This is the next suggestion.
If you are working on a tile map, always work in tile-units.
That is, if you are talking about "position", "size", "distance", or anything related to them your world, then "1" is "tile", not pixel. Pixels don't matter.
If you express all the sizes in your game in tile-space, you will not require many, many calculations to convert between pixels and tiles, which otherwise you will have to do in initialization, logic, rendering, physics, and what not.
Once everything is expressed in tiles, and you want to actually draw it, it's a simple love.graphics.scale(pixels) to draw everything in the size you want in pixels.

I'll see if I got anything else (I most likely do, but I can't think of anything at the moment).
User avatar
slime
Solid Snayke
Posts: 3162
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Rants + suggestions

Post by slime »

spectralcanine wrote:There are 3 tile map scrolling tutorials. One even goes as far as to call itself "efficient", because it uses a sprite batch. Can you see what's common in them?
They go and move all of the sprites that a tile map is made from.
How is that anything even close to being efficient? That's the way you would draw on the CPU, with no hardware acceleration. It is now the year 2013, so let's get a little updated, ok?

Code: Select all

love.graphics.translate(x, y) -- A little more "efficient"

The Box2D (aka physics) callbacks tutorial doesn't show the last argument to postSolve. This argument is the actual impulse of the collision response, and it's quite important for anything that reacts to it (e.g. damage).
If you want something more in-depth, go look at the Camera tutorial, because it already explains this.
The 'efficient' tutorial certainly isn't as efficient as possible, although sometimes it's simpler to have only nearby tiles in the SpriteBatch, especially since there's a bug in LÖVE 0.8.0 which prevents SpriteBatches with more than 16,383 sprites from working correctly. In my opinion beginner tutorials should favour simplicity over hardcore performance - otherwise people may get confused and sidetracked for no good reason.

Anyone with a forum account is able to edit the wiki!
spectralcanine wrote:love.run: it is bad. More precisely, the dt parameter sent to love.update is bad.
If you are making any sort of movement over time that requires accuracy, you will see why.
For realistic physics simulation and networking you might want a constant accumulated timestep as you have demonstrated (although games often don't need to prevent the slight variabilities that are introduced with a variable timestep.)

love.run's variable delta time approach is very simple compared to the accumulator/interpolated one (which is very important, the interpolation in particular can add a huge amount of potentially worthless code complexity), and most of the time games won't need to change it. If they do then love.run is there for them. :)

You can even have the accumulator approach just for physics worlds inside love.update without having to modify love.run.
spectralcanine wrote:Sprite batches, or "Why must you use them".
SpriteBatches are very useful for performance in many situations, but again throwing them at everything will just add useless code complexity. If/when LÖVE comes to iOS and Android, using them will become more important for general situations because the cost of draw calls on mobile tends to be much greater than on desktop systems.
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Rants + suggestions

Post by raidho36 »

Also, since 0.9, timer becomes microsecond accurate, therefore you can rely on it in your accurate computations. And as of 0.8 you can use getMicroTime anyway; if you're not ok with default love.run (simple and reliable enough), you can override it. The problem with your suggestion is that you can't really get your logic to run at a constant rate, since OS is not guaranteed to wake up a thread at sleep timeout instantly, and it will set a thread to halt forcibly if it won't call OS functions for long enough, again to the undetermined amount of time. In fact, it will set thread to halt whenever it thinks another thread deserves some CPU time right now. With deltatime approach, in case of lack of CPU time, game will experience visual FPS drop or lags, but it will run more or less consistently, which makes gameplay less fluid but overall it's still playable, more or less. But with constant rate approach it will have slow-motion thing and have it very inconsistent, like this moment time is half as fast and next moment it's normal and another moment it's almost froze and then it's normal again, it'll break any kind of user action timing, which would ruing gameplay experience for good. You can only ensure that doesn't happen if you set your thread a realtime priority, which is bad practice always and all-around (unless you really need it to be realtime, like it's perfroms processing of a nuclear plant readings), and it doesn't save from trying to compute too much stuff at once, which will cause logic FPS drop neverheless.
spectralcanine
Citizen
Posts: 65
Joined: Sat Dec 22, 2012 8:17 am

Re: Rants + suggestions

Post by spectralcanine »

slime wrote: The 'efficient' tutorial certainly isn't as efficient as possible, although sometimes it's simpler to have only nearby tiles in the SpriteBatch, especially since there's a bug in LÖVE 0.8.0 which prevents SpriteBatches with more than 16,383 sprites from working correctly. In my opinion beginner tutorials should favour simplicity over hardcore performance - otherwise people may get confused and sidetracked for no good reason.

Anyone with a forum account is able to edit the wiki!
I can't see how iterating over a 2D array and updating it every frame is simpler than explaining that "translate" is like "move the camera" in two sentences...but maybe that's just me.

Too lazy/lame to edit the wiki :)
slime wrote: For realistic physics simulation and networking you might want a constant accumulated timestep as you have demonstrated (although games often don't need to prevent the slight variabilities that are introduced with a variable timestep.)

love.run's variable delta time approach is very simple compared to the accumulator/interpolated one (which is very important, the interpolation in particular can add a huge amount of potentially worthless code complexity), and most of the time games won't need to change it. If they do then love.run is there for them. :)

You can even have the accumulator approach just for physics worlds inside love.update without having to modify love.run.
It's beyond "realistic", it's any timed movement really. I had a Box2D body move X distance to one direction over some time, and then move that same X distance to the opposite direction, effectively returning to place.
This simple thing sometimes worked, and sometimes it returned "too much" because of the varying dt (which cause the update check to work one more time).

I agree that adding interpolation in the rendering routine is a bit annoying (though it can be generalized pretty easily), but it's worth it at the end if you want physics of any kind, not necessarily realistic.
I prefer b * t + a * (1 - t) over movements that change themselves over time even in the simplest of cases.
slime wrote: SpriteBatches are very useful for performance in many situations, but again throwing them at everything will just add useless code complexity. If/when LÖVE comes to iOS and Android, using them will become more important for general situations because the cost of draw calls on mobile tends to be much greater than on desktop systems.
I was more aiming at tile maps and games with a whole lot of sprites (hence the suggestions after that are geared toward them), where it isn't only very important to use batches, but it's also very easy (and makes the code neater if I do say).
raidho36 wrote:Also, since 0.9, timer becomes microsecond accurate, therefore you can rely on it in your accurate computations. And as of 0.8 you can use getMicroTime anyway; if you're not ok with default love.run (simple and reliable enough), you can override it. The problem with your suggestion is that you can't really get your logic to run at a constant rate, since OS is not guaranteed to wake up a thread at sleep timeout instantly, and it will set a thread to halt forcibly if it won't call OS functions for long enough, again to the undetermined amount of time. In fact, it will set thread to halt whenever it thinks another thread deserves some CPU time right now. With deltatime approach, in case of lack of CPU time, game will experience visual FPS drop or lags, but it will run more or less consistently, which makes gameplay less fluid but overall it's still playable, more or less. But with constant rate approach it will have slow-motion thing and have it very inconsistent, like this moment time is half as fast and next moment it's normal and another moment it's almost froze and then it's normal again, it'll break any kind of user action timing, which would ruing gameplay experience for good. You can only ensure that doesn't happen if you set your thread a realtime priority, which is bad practice always and all-around (unless you really need it to be realtime, like it's perfroms processing of a nuclear plant readings), and it doesn't save from trying to compute too much stuff at once, which will cause logic FPS drop neverheless.
On the contrary, that would happen with a variable dt.
Do any sort of movement over time, grab your window's title to force the window to pause (I believe only Windows does this), wait for a couple of seconds and let it run again.
Everything will teleport, since you suddenly get a massive dt, and you can say goodbye to collision detection, or any timed movements.
You can clamp the dt to some maximum of course, but that doesn't solve the problem, you just get smaller teleports.

With a constant dt, instead of getting a massive dt, you make more steps in that specific frame, so the simulation doesn't explode, instead it "catches up" with the new time.
Here clamping dt is done just so you wont suddenly get hundreds or thousands of updates, since that's usually not what a user expects when lag occurs (and you'd probably need to make an algorithm to split that amount of updates over a few frames to not get a spiral of death).

For more information on this, here's a very good read (he calls it "semi-fixed" delta time there).
Santos
Party member
Posts: 384
Joined: Sat Oct 22, 2011 7:37 am

Re: Rants + suggestions

Post by Santos »

Your texture atlas creator inspired me to make this. Given a path to a folder, it'll return a texture atlas Image and a table of Quads with file names as the keys. For example, if you have a file named rainbow.png in a folder named images, you could do something like this:

Code: Select all

atlas, quads = make_atlas('images')
love.graphics.drawq(atlas, quads.rainbow, 0, 0)
There's an issue with Quads where they bleed on the edges: https://bitbucket.org/rude/love/issue/3 ... n-on-float
So this adds a 1 pixel border around the images to prevent this.

Code: Select all

function make_atlas(directory)
	local geometries = {}

	local images = {}

	for _, filename in pairs(love.filesystem.enumerate(directory)) do
		if love.filesystem.isFile(directory..'/'..filename) then

			local name, ext = filename:match('(.+)%.(.+)$')
			ext = ext:lower()
			if ext == 'png' or ext == 'bmp' or ext == 'jpg' or ext == 'jpeg' or ext == 'gif' or ext == 'tga' then
				local imagedata = love.image.newImageData(directory..'/'..filename)
				local padded_imagedata = love.image.newImageData(imagedata:getWidth() + 2, imagedata:getHeight() + 2)

				padded_imagedata:paste(imagedata, 1, 1, 0, 0, imagedata:getWidth(), imagedata:getHeight())
				padded_imagedata:paste(imagedata, 0, 1, 0, 0, 1, imagedata:getHeight())
				padded_imagedata:paste(imagedata, padded_imagedata:getWidth()-1, 1, imagedata:getWidth()-1, 0, 1, imagedata:getHeight())
				padded_imagedata:paste(imagedata, 1, 0, 0, 0, imagedata:getWidth(), 1)
				padded_imagedata:paste(imagedata, 1, padded_imagedata:getHeight()-1, 0, imagedata:getHeight()-1, imagedata:getWidth(), 1)
				padded_imagedata:setPixel(0, 0, imagedata:getPixel(0, 0))
				padded_imagedata:setPixel(padded_imagedata:getWidth()-1, 0, imagedata:getPixel(imagedata:getWidth()-1, 0))
				padded_imagedata:setPixel(padded_imagedata:getWidth()-1, padded_imagedata:getHeight()-1, imagedata:getPixel(imagedata:getWidth()-1, imagedata:getHeight()-1))
				padded_imagedata:setPixel(0, padded_imagedata:getHeight()-1, imagedata:getPixel(0, imagedata:getHeight()-1))

				table.insert(images, {
					name = name,
					imagedata = padded_imagedata,
					width = padded_imagedata:getWidth(),
					height = padded_imagedata:getHeight(),
				})
			end
		end
	end

	table.sort(images, function(a, b) return a.height > b.height end)

	local total = 0
	local max_width = 0

	for i, image in ipairs(images) do
		total = total + image.width * image.height
		if image.width > max_width then
			max_width = image.width
		end
	end

	local lets_say_the_atlas_width_is_this

	if max_width > math.sqrt(total) then
		lets_say_the_atlas_width_is_this = max_width
	else
		lets_say_the_atlas_width_is_this = math.ceil(math.sqrt(total))
	end

	local this_height = 0
	local this_width = 0
	local width_of_atlas = 0
	local first_height = images[1] and images[1].height

	for i, image in ipairs(images) do
		if (this_width + image.width) > lets_say_the_atlas_width_is_this then
			this_width = 0
			this_height = this_height + first_height
			first_height = image.height
		end

		images[i].x = this_width
		images[i].y = this_height
		this_width = this_width + image.width

		if this_width > width_of_atlas then
			width_of_atlas = this_width
		end
	end

	local height_of_atlas = images[#images].y + first_height

	local atlas = love.image.newImageData(width_of_atlas, height_of_atlas)

	local geometries = {}

	for i, image in ipairs(images) do
		atlas:paste(image.imagedata, image.x, image.y, 0, 0, image.width, image.height)
		geometries[image.name] = love.graphics.newQuad(image.x+1, image.y+1, image.width-2, image.height-2, atlas:getWidth(), atlas:getHeight())
	end

	return love.graphics.newImage(atlas), geometries
end

function love.load()
	atlas, geometries = make_atlas("images")
end

function love.draw()
	love.graphics.drawq(atlas, geometries.grass, 10, 10)
end
Last edited by Santos on Sat Jun 29, 2013 5:35 pm, edited 2 times in total.
User avatar
Ref
Party member
Posts: 702
Joined: Wed May 02, 2012 11:05 pm

Re: Rants + suggestions

Post by Ref »

Santos wrote:Your texture atlas creator inspired me to make this. Given a path to a folder, it'll return a texture atlas Image and a table of Quads with file names as the keys. For example, if you have a file named rainbow.png ...
Tried it but had problems:
1. Put my images in 'images' directory and changed line 90 to
atlas, geometries = make_atlas("images'')
and got:
Error: main.lua:76: attempt to index field '?' (a nil value)
2. Put images in directory with main.lua and got past previous error but now got:
Error: main.lua:93: Incorrect parameter type: expected userdata.
3. Replaced line 93
love.graphics.drawq(atlas, geometries['10'], 10, 10)
with
love.graphics.drawq(atlas, quads.pic, 0, 0)
where pic is one of the pictures (pic.jpg)
I now got:
Error: main.lua:94: attempt to index global 'quads' (a nil value)

A little help would be appreciated as your approach is very interesting.
Obviously overlooking something!
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Rants + suggestions

Post by raidho36 »

you can say goodbye to collision detection
Instant no to you here, any proper collision uses sweep test which leaves no place for undetected intercollisions. But overall it's a valid point, and I am completely aware of it. Most high quality games do it this way nevertheless, since having everything just teleported in big jumps but at a constant time rate has better feeling than sudden slo-mo matrix style cutscenes, or even worse, fast-mo rapid moments.
it "catches up" with the new time
That said, if there were several events across the timespan, they will all happen simultaneously wtih no user interaction going on, so it's the same as with deltatime, no difference in practice: it will teleport anyway. Practice also shows that simulation doesn't explodes from that either, if you designed it properly. Bullet Physics simulation doesn't explode if you stall it for a second and then request update world for this whole second time span.

Overall deltatime is an optimal solution all-around.
Santos
Party member
Posts: 384
Joined: Sat Oct 22, 2011 7:37 am

Re: Rants + suggestions

Post by Santos »

Oops, sorry about that!

What happened was, when I was testing it, I was using the same directory as main.lua, so I just put "" as the directory to look through, but when it came to going through the files in the folder and loading them, I forgot to prepend the directory path to the file name, so it was like...

Code: Select all

love.image.newImageData(filename)
Instead of...

Code: Select all

love.image.newImageData(directory..'/'..filename)
But, because I was using the game's folder as the directory, it worked, but it didn't work when images were in another folder. :oops:

Thanks for testing it out and letting me know! :)

I'll edit the post now, and here's a .love:
test.love
(11.04 KiB) Downloaded 285 times
Last edited by Santos on Sat Jun 29, 2013 5:36 pm, edited 1 time in total.
spectralcanine
Citizen
Posts: 65
Joined: Sat Dec 22, 2012 8:17 am

Re: Rants + suggestions

Post by spectralcanine »

raidho36 wrote:
you can say goodbye to collision detection
Instant no to you here, any proper collision uses sweep test which leaves no place for undetected intercollisions. But overall it's a valid point, and I am completely aware of it. Most high quality games do it this way nevertheless, since having everything just teleported in big jumps but at a constant time rate has better feeling than sudden slo-mo matrix style cutscenes, or even worse, fast-mo rapid moments.
it "catches up" with the new time
That said, if there were several events across the timespan, they will all happen simultaneously wtih no user interaction going on, so it's the same as with deltatime, no difference in practice: it will teleport anyway. Practice also shows that simulation doesn't explodes from that either, if you designed it properly. Bullet Physics simulation doesn't explode if you stall it for a second and then request update world for this whole second time span.

Overall deltatime is an optimal solution all-around.
I don't quite understand what you're talking about.

Any proper code would use continuous collision tests, but let's face it, I am assuming everyone in this forum uses discrete tests.

What are you talking about when you mean slo-mo or fast-mo, everything runs at constant speed, that was my point in this rant.

Bullet doesn't explode (nor should any other proper physics system), because it uses continuous tests, but yes, if you give varying delta times to physics engines, they destabilize over time. In fact, I am pretty sure this is even written in the Box2D manual.

In any case, by your reply I am not sure you understood what I mean by "constant dt", so please read that article. He explains it better than I ever could.

The example case I wrote is another side effect of varying delta times, by the way. I have a timer that accumulates the delta times until it gets to some defined value where an action should happen, except because of these tiny 1 millisecond fluctuations, it sometimes runs one frame too long. This one tiny frame, caused by tiny one millisecond changes, completely ruin my game.
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Rants + suggestions

Post by raidho36 »

Well first off, you can't assue this kind of things, you always need to measure (this is especially true for code performance). Box2D uses sweep test all right, and anyone who uses built-in physics therefore uses sweep tests.
What are you talking about when you mean slo-mo or fast-mo, everything runs at constant speed
Everything does not runs at a constant speed due to many many factors. Given that code can't always run fast enough to keep in "constant rate", logic FPS will drop. If logic assumes running at a constant rate, overall logic would run at real fp / design fps speed, i.e. in slow motion. Then if you use "catch up" thing then it could all of the sudden perform several steps at once, i.e. in fast forward. Worst case is when both is applied, it will run slower than should yet it will jump through frames at undefined rate.

Deltatime is the optimal way to deal with it, it's robust and simple, the only drawback is that you'd have to take into account that timespan may be large.
This one tiny frame, caused by tiny one millisecond changes, completely ruin my game.
I don't know just how many baseless assumptions your code must have had to fail this hard on such a minor fluctiation. Speaking of your example code specifically, it's just a way to perform several steps in one go, and not exactly effecient. You may improve it though by splitting your accumulated time into some particular amount of equal frames, and passing this frame time to update function every time.

Code: Select all

frames = math.ceil ( accum / fps )
frametime = accum / frames
for i = 0, frames do love.update ( frametime ) end
Also, before 0.9, you should use getMicroTime ( ), because system has two timers, one high resolution timer and one regular, with ≈15 msec granulation (under Windows, every OS may have it's own exact value). getMicroTime access the former (hence the name) and another one, getTime, access regular "low-resolution" timer.
Last edited by raidho36 on Sat Jun 29, 2013 6:20 pm, edited 1 time in total.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot] and 1 guest