Monday, September 13, 2010

Fixing bugs, brutal way

It's a good thing to wait few hours before releasing.

Portaball is based on Chimpunk, open source, free, fast and amazing physics engine. In the game it's mostly used for collisions detection and as a source for physics objects (walls, ball etc.).
Today however, I noticed that, under certain conditions, it's impossible to complete levels that contain, as I refer to it, "cut-walls". These come in 5 flavors:



They weren't originally intended to be included in game. Having ran out of ideas for new levels I decided to give it a try. Calculating the correct position for wall on a grid is trivial, school math basically. Here's the line of the code:

cutShape = cpSegmentShapeNew(body, cpv(-kGridSize/2,-kGridSize/2+kBallRadius*M_SQRT2),
cpv(+kGridSize/2-kBallRadius*M_SQRT2,+kGridSize/2), 0.0f);

(kGridSize is constant containing dimension of grid and is equal to 40, kBallRadius is self-explanatory)

As a result we obtain a wall of width 0 and position in game's space in this way:



So far everything seemed perfect. Having it tested many times in game it appeared it's not that simple. After bouncing off ball was slightly misaligned, not a big deal after one bounce, sad truth is this minor offset was cumulative. Therefore after springing back enough times the misalignment was not only visible, but also it prevented ball from triggering mini-buttons.

There were two  main reasons for this. First one is limited precision. Real life sqrt(2) is far more precise than M_SQRT2, however, this difference should be insignificant. The other and hopefully main reason was they way in which collisions are being calculated. In Portaball, physics engine is stimulated every 1/60th of a second. As a result: if on a given step ball is just before "cut-wall" on next step it might overlap with wall. The collision is calculated and Chipmunk is, if I've understood documentation correctly, trying to push ball of way to prevent this overlapping. I figured this was my trouble. 

Sure, for a typical physics based game it's not a problem. Objects are flying around and their position has fractional part, but in Portaball everything is designed to follow strict guidelines. Ball always moves in straight lines and crosses center of a given grid. I had a few ideas how to force ball to move by MY rules. The first and obviously naive way was to round ball's position every step. This method not only would interfere too much with Chipmunk, but also it would cause even more troubles.

The solution I went along with was rounding ball's position and velocity just on collision with "cut-wall". Simple rounding wasn't enough. The problematic offset would often be larger than 1. Here's the final implementation:

float x,y,vx,vy;
x=ball->body->v.x;
vvy=ball->body->v.y;
ball->body->p.y;
x=ball->body->p.x; y =vx=round(vx); vy=round(vy);
idSize); y=kGridSize*0.5*round(y*2/kG
x=kGridSize*0.5*round(x*2/kG rridSize); ball->body->v.x=vx; ball->body->v.y=vy;
ball->body->p.x=x;
ball->body->p.y=y;

Ball must be positioned in the middle of a grid. To achieve this several steps must be made. Current misaligned position of ball (for instance 222.34) is divided by kGridSize=40. The result (5.5585) is doubled (11.117). If it wasn't for doubling, ball couldn't be properly positioned in the middle of a grid. Rounded result (11) is again multiplied by kGridSize. As a result we obtain proper (220) positioning of ball (middle of 6th grid in given row/column). Velocities are also rounded, otherwise after bounce they often would have very slightly changed values: 80.00007 instead of 80.0. Better safe than sorry.

This code provides satisfactory results and moreover, it simply works.

No comments:

Post a Comment