Friday, May 27, 2016

Collisions for randomized enemies also [mostly] work!

It feels like right now I am getting a bit too close to the deep end of the pool. While in the last few days I have managed to make the randomly spawned balls move (pretty easy) and even collide with each other (really hard), I couldn't have done the later without the help from the Pico8 communities on Slack and IRC. Those guys helped me understand Lua syntax much more, and on more than one occasion simply gave me the answer. My next step, after fixing few bugs in this code, is to go back to my pong game and start implementing those enemies in. I feel like I need to take a step back from trying to understand more advanced topics and actually work on the game, mostly because programming the game and seeing how it all comes together is much more rewarding than trying to perfect a single side mechanic.

Anyway, let's see how this code works...


function update_bug(o)
--move the bug
--keep within world  
 if o.x+o.size>126
 or o.x-o.size<1 then
  o.xdir= (-o.xdir)
 if o.y+o.size>126 
 or o.y-o.size<1 then
  o.ydir= (-o.ydir)
--avoid collision with wall
 if o.x-o.size<=0 then o.x=1+o.size end
 if o.x+o.size>=127 then o.x=126-o.size end
 if o.y-o.size<=0 then o.y=1+o.size end
 if o.y+o.size>=127 then o.y=126-o.size end

function _update()
 foreach (bugs, update_bug)

This was the easier of the two tasks. Moving something (a shape, sprite, whatever) was covered pretty well in the Squashy tutorial. All we need is to change the X and Y values the object and then call this in the _update() function. The only difference here was instead of a single object, I had bunch of objects stored inside a table, each under its own number. This is where my lack of knowledge of Lua syntax, and of programming in general, slowed me down. On paper I knew exactly how to do it. Get a loop going that updates the X and Y values of each element inside the table. Now All I needed to get it going was to find a way to access stuff in that table. So I had spent some time looking for Pico8 carts that have random enemies and try to dissect their code. I have noticed few of them using the FOREACH statement in conjunction with a table and a function that had one parameter - o. This "got the ball rolling," so to speak.

At this point I thought that the naming of the parameter (o) had to be the same to the one used in creation of my table (which also was o). That's not the case. To my understanding FOREACH looks up the values from the table (in the case of bugs, it returns the table for each enemy), and plants it into the function. The function can now process each enemy one by one. Thanks to that it changes X and Y for enemy 1, then 2, than 3 and so on. The "o" parameter is simply a "placeholder" a local variable (as I said, I mostly understand tables )

After finding a way to update X and Y of every enemy I just added some IF statements to keep the balls on screen (bounce of the wall just like in Squashy) and another one to eliminate a bug where a ball would get stuck on one of the walls after collision. I guess I could have combined them into one function, but having them separated makes it more clear what each section does.

After I had the balls moving and bouncing of the walls I wanted them to bounce of each other. For that I needed...


--check for collision between bugs  
function collision_bugbug()
--cycle thru the table get one enemy
 for a = 1, #bugs do
--cycle thru again get second enemy 
  for b = a+1, #bugs do
--formula for distance between circles  
  local distance=sqrt((bugs[a].x-bugs[b].x)^2+(bugs[a].y-bugs[b].y)^2)
--if distance is smaller than combined sizes
--of both enemies, it means they collide
    if distance <= bugs[a].size + bugs[b].size +1 then
--bounce the balls back on collision
      bugs[b].xdir= -bugs[b].xdir
      bugs[b].ydir= -bugs[b].ydir
      bugs[a].xdir= -bugs[a].xdir
      bugs[a].ydir= -bugs[a].ydir
--just for testing how many collision there are      
--system functions
function _update()
 foreach (bugs, update_bug)

This was the tricky syntax part that I couldn't figure out on my own. It felt like it was within my grasp after figuring all the math and learning about FOREACH, but it felt like a strange blind spot in my brain whenever I was trying to create syntax to compare two different enemies from inside the table. I spent a day or so trying to find a solution, but again, documentation simply wasn't there (I really hope that Pico-8 manual will eventually become a little noob friendly). This is where the awesome community of this fantasy console stepped in.

This is the second version of the collision code. The original way was working fine, but used over twice as much CPU clock. It was based around FOR x in ALL(table) instead of FOR x=1,#table (length of the table) which meant that the code would check enemy 1 vs enemy 2, but also check enemy 2 vs enemy 1, which was not necessary, as we already checked if they collided. I am not really on the level of proficiency to optimize my code, but when there was an opportunity to do so, I jumped on it. Here's a comparison how the old (left) vs current (right) works:

Easiest way (at least for me) to understand it is to treat every unique number as an unique enemy. Each number holds a table for one enemy. So Bugs[1] holds X,Y and other attributes of the first enemy, Bugs[2] the second and so on. As you can see, the current version does "less work" as shown by the white spaces. Later on I added to the formula, so B starts at A+1 instead just A, so the function won't check collisions against against the same enemy. This saved a little more CPU time and a token, as I didn't have to add A != B (A does not equal to B) to the IF statement. Anyway, let's break down the current code...

function collision_bugbug() initiates the function, so we can call it later in _update(). Next two lines (for x in...) cycle through the enemies' table twice and generate pairs of numbers (A and B) which we can compare.
Next we need to find out the distance between the circles to see if they are overlapping. This is where Pythagorean Theorem comes in handy yet again (honestly I never thought I will get that much millage out of something I have learnt in primary school). We basically find the distance between two points (centers of the two enemies) and put it in a variable ( local distance=sqrt((bugs[a].x-bugs[b].x)^2+(bugs[a].y-bugs[b].y)^2) ) for clarity. Then we check if this distance is equal or smaller then the sum of the sizes (radiuses) of the circles. If it is, it means that the circles are touching / colliding. 
THEN we put whatever we need upon collision, in this test I just reverse the directions at which the circles travel (also add one to test variable, just for troubleshooting). 

That's it. When it is broken down like that it doesn't seem that difficult, but it took me a long time to get there, and would probably take me much, much longer without the community's help. 
If you want to check out the whole code for the spawner, feel free to download the cartridge to the left and mess with it. Right now there are still some bugs (circles can spawn on top of one another or can get stuck if multiple circles collide at the same time) that I will try to fix in the near future (well, basically as soon as I can figure out the syntax for it). My next step is to transplant this into the game code proper and do some tests with squares, sprites and maybe even pixel perfect collision (which maybe way beyond my skill level, but there is a Pixel Perfect Collision Demo cart that I plan on digging in). 

No comments:

Post a Comment