Faster alternative to iterating over a Lua table?

I am working with Lua on Windows. I am working creating a border that isn't square. I have all the pixel coordinates saved in a table:

local borderCoords = {
   {x = 100, y = 200},
   {x = 101, y = 201}
   -- plus a lot more coordinates
}

This table has almost 300 coordinates in it. I am then looping through each item in the table and reading the x and y coords in order to create a collider at that point:

for i=1,#borderCoords do
	Boundry(borderCoords[i].x, borderCoords[i].y, 1, 1):add()
end

This is working on PC, but it does take a second to load. When I try it on my device the frame rate is EXTREMELY slow. Is there a more efficient way to do this? I haven't worked with Lua a whole lot so I know I'm missing something, just wanted to give it a try the only way I knew how.

Boundry is class I created just to put a pixel-sized sprite with a pixel sized collider rect.

Heres a screen shot of it working on pc:
image

Edit: I managed to increase the frame rate a bit by removing coordinates from the table after they have been used, but the frame rate is still to slow to even be playable.

borderCoords[i][#borderCoords[i]] = nil;

If it takes a second on PC something is suboptimal.

Where exactly is your slowness? Use the profiler window to see which lines of code are responsible for the most slowdown.

300 sprites is probably too much for Playdate.
Use your own lighter weight objects.

Having one table of X and another if Y is faster than accessing the X and Y keys of the table borderCoords table.

In my latest game I have 100 stars moving, 100 particles moving, all using plain tables not sprites. Plus physics going on. 40-50fps.

Can you construct your border from lines and use the SDK functions to check if your collisions are on those lines? Maybe half a dozen small lines for each of your paths.

1 Like

I poked around in the profiler a little but I dont really know how to narrow that down. But with troubleshooting and adding/removing things, the slowness is gone once this line is removed:

Boundry(borderCoords[i].x, borderCoords[i].y, 1, 1):add()

I thought about using lines, but I'm almost positive that is not going to be a solution in the long run since Im going to need to layout more collisions for other objects that aren't square, or square collisions just aren't going to cut it.

I also tried an xcoord array and a ycoord array and looped through both. This showed no visual improvement. And what would be a good alternative to a sprite with a collisionRect?

there is a lot of overhead from the sprite system, maybe you don't need it or maybe you do. but the alternative is a simple object containing x,y,w,h and then you do rectangle checks yourself using simple comparison.

regarding your slow line of code, with the profiler (check the "separate by line" option, sample for a while, then stop sampling and click the > arrow on an item which will open that up and show you more details).

for you the slow code should be within your add() method?

eg.
image

1 Like

Okay I decided to go with drawing lines and have lines drawn to the screen but now I think I remembered why I went the direct I did. Im not sure how the collision detection works with drawn objects. I only thought collisions were for sprites?

For lines you can use pointOnLine, closestPointOnLine, intersectsLineSegment. You're taking care of collision by yourself.

But it depends on your goal. What is happening in your game?

1 Like

For lines you can use pointOnLine, closestPointOnLine, intersectsLineSegment. You're taking care of collision by yourself.

Okay I think thats what I was looking for! Im working on a hockey game and this needs to be the ice rink border, keeping in the players and puck.

Perhaps a better approach for you would be to manage all the collisions in simple rectangles and circles. You could even debug draw everything as top down.

Then when you do your proper/final draw step, do it with a simple perspective transformation.

This will be much easier than doing your drawing and your colliding in perspective.

Do you see what I mean?

1 Like

I had done it top down originally, but prefer the look, I didn't even think about changing the perspective. But Ill have to do some digging on that. Now that I have lines drawn Ill try to make my checks that way and then try this if that fails:

Then when you do your proper/final draw step, do it with a simple perspective transformation.

The important thing to note is that you're heading for hard work transforming co-ordinates at every place in your code. Lots of maths.

On the other hand, working with simple shapes in a 2D space (you can add fake third dimension of height) is very easy using simple maths. The transform to a tilted view is done during the drawing, away from your game logic.

Personally, I would code the game on a playfield the size of the screen. And then transform all coordinates only during drawing. You could then, if you wanted, offer two options: overhead view or perspective view, or use the overhead view for tactics, replays, etc.

The transform from 2D to tilt it into perspective is very easy: rink.zip (1.6 KB)

You could expand on this to transform the Y coordinate also. To bring the screen size down to the trapezoid we see in the screenshots.

ps: I love ice hockey games :slight_smile:

import "CoreLibs/graphics"
local gfx = playdate.graphics

local SCREEN_WIDTH <const> = playdate.display.getWidth()
local SCREEN_HEIGHT <const> = playdate.display.getHeight()

-- Define a function to transform the x-coordinate
function transformXCoordinate(x, y)
    -- Calculate the scaling factor based on the y-coordinate
    local scaleFactor = 1 - (y / SCREEN_HEIGHT)
    
    -- An extra modifier so we can stretch or squish the scaling
    local modifierFactor = 0.6

    -- Apply the scaling factor to the x-coordinate
    local newX = x + (SCREEN_WIDTH//2-x) * scaleFactor * modifierFactor
    
    return newX, y
end

-- Original 2D coordinates
local x1, y1 = 0, 125
local x2, y2 = 400, 125
local x3, y3 = 400, 240
local x4, y4 = 0, 240

-- Apply our perspective transformation to the coordinates
tx1, ty1 = transformXCoordinate(x1, y1)
tx2, ty2 = transformXCoordinate(x2, y2)
tx3, ty3 = transformXCoordinate(x3, y3)
tx4, ty4 = transformXCoordinate(x4, y4)

-- Now, you can draw lines or polygons using the transformed coordinates
function playdate.update()
    gfx.setColor(gfx.kColorBlack)
    gfx.fillPolygon(tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4)
end

1 Like

Wow thanks! Lots to unpack there! I’ll give that a shot asap! Thanks for all the help! And I love hockey games too! First thing I wanted to make when I got the console!

1 Like

So I tried a lot of different things and I found that simplifying the boundaries and check lines if they are being crossed and then adjusting the velocity of the player/puck accordingly. Thanks so much, I'm learning so much about this SDK in the short months I've played around with it. :slight_smile: (and it works great on the device!)

collisionDemo

1 Like

This looks so cool! :ice_hockey::goal_net:

Puck is very small. :laughing:

If you're not transforming coordinates, then technically it will be quicker to skate the length of the rink at the top shorter edge. Something to keep in mind.

1 Like