Trouble getting steady 30 FPS in a simple Labyrinth maze gam

Official forum for the Chipmunk2D Physics Library.
Post Reply
omerv
Posts: 2
Joined: Wed Jan 06, 2010 3:27 am
Contact:

Trouble getting steady 30 FPS in a simple Labyrinth maze gam

Post by omerv »

Hi,

I'm finishing the chipmunk part of my Labyrinth maze style game, and I'm trying to optimize it to get 30 FPS from all 4 difficulty levels.
The different difficulty levels have different sizes of mazes in them, meaning the higher the difficulty level the more cpSegmentShapeNew and cpSpaceAddStaticShape I use, hence more collision tests are being called and the game runs slower.
In each maze there is one (active) ball, 4 big walls for the outer borders, and lots of segments making the "inside" of the maze.
I count the total number of static shapes (for every one of them I do: cpSegmentShapeNew and then cpSpaceAddStaticShape) and I have about:
100 for maze size "S"
125 for maze size "M"
165 for maze size "L"
200 for maze size "XL"
My problem is mainly with the "XL" sized maze, having around 200 maze segments, running at 17.5 to 22.5 FPS.
I tried optimizing using cpSpaceResizeActiveHash and cpSpaceResizeStaticHash, maybe my numbers are wrong, or there might be some other problem I'm not aware of.

How do I fix this problem? I know using this exact code works on the smaller sized mazes, giving a steady 30 FPS for the "S" and "M" sizes now, but the "L" size is dropping to 25 FPS and the "XL" is even lower, as stated above, and is the most important topic I have left to fix.
Maybe I'm doing something wrong with the way I am setting this up, or maybe it does have something to do with values I use with my cpSpaceResizeActiveHash and cpSpaceResizeStaticHash?

Here's my code: basically I have an array I create that represents the maze:

Code: Select all

                  array = [[NSArray alloc] initWithObjects:
                               @"XXXXXXXXXXXXXXXXXXXXX",
                               @"X X    X   X   X    X",
                               @"X X XX X X X X X XX X",
                               @"X X X  X X   X   X  X",
                               @"X X X XX XXXXXXXXX XX",
                               @"X   X    X         XX",
                               @"XXXXXXXXXX XXXXXXXXXX",
                               @"XX         X    X   X",
                               @"XX XXXXXXXXX XX X X X",
                               @"X  X   X   X X  X X X",
                               @"X XX X X X X X XX X X",
                               @"X    X   X   X    X X",
                               @"XXXXXXXXXXXXXXXXXXXXX",nil];
 

And my code (only the relevant values for the "XL" maze) for the GameLayer.m is:

Code: Select all

-(void)setupChipmunk{
     
#define kBallElasticity 0.60 
#define kBallFriction 0.04
#define kObstacleElasticity 0.80
#define kObstacleFriction 0.35
#define kObstacleRadius 1
 
      // Start chipmunk 
      cpInitChipmunk();
     
      // Create a space object 
      space = cpSpaceNew();
     
      // Define a gravity vector 
      space->gravity = cpv(0,0);
     
      [self schedule: @selector(tick:) interval: 1.0f/50.0f];
     
      int count;
 
      int rowCount = [array count];
      int columnCount = [[array objectAtIndex:0] length];
      int startXpos = (480 - columnCount*tileSize)/2;
      int startYpos = (320 - rowCount*tileSize)/2;
 
      cpBody* ballBody;
     
      ballBody = cpBodyNew(85.0, cpMomentForCircle(85.0, 0.0, 9, cpvzero));
 
      space->iterations = 2;
      space->elasticIterations = 3;
     
      cpSpaceResizeActiveHash(space, 9, 4);
      cpSpaceResizeStaticHash(space, 11, 600);
     
      // position ball:
      ballBody->p = cpv(startXpos + 1.5*tileSize, startYpos + 1.5*tileSize);
     
      // add the ball body to the space
      cpSpaceAddBody(space, ballBody);
 
      // create the ball's shape using the ball body
      cpShape* ballShape = cpCircleShapeNew(ballBody, 9, cpvzero);
      ballShape->e = kBallElasticity;
      ballShape->u = kBallFriction;
      ballShape->data = ballSprite;
      ballShape->collision_type = 1;            // 1=ball, 0=walls
 
      // add the regular (non-static) shape to the space
      cpSpaceAddShape(space, ballShape);
 
      // start wallBody
      wallBody = cpBodyNew(INFINITY, INFINITY);
      wallBody->p = cpv(0,0);
 
      // set the maze's outer borders:
 
      cpShape* outerWallShape;
 
      int x = startXpos;
      int y = startYpos;
 
      // top border
      outerWallShape = cpSegmentShapeNew(wallBody, cpv(x,y+(rowCount-2)*size), cpv(x+(columnCount-2)*size,y+(rowCount-2)*size), kObstacleRadius);
      outerWallShape->e = kObstacleElasticity;
      outerWallShape->u = kObstacleFriction;
      outerWallShape->collision_type = 0;
      cpSpaceAddStaticShape(space, outerWallShape);
      counter++;
     
      // bottom border
      outerWallShape = cpSegmentShapeNew(wallBody, cpv(x,y), cpv(x+(columnCount-2)*size,y), kObstacleRadius);
      outerWallShape->e = kObstacleElasticity;
      outerWallShape->u = kObstacleFriction;
      outerWallShape->collision_type = 0;
      cpSpaceAddStaticShape(space, outerWallShape);
      counter++;
     
      // left border
      outerWallShape = cpSegmentShapeNew(wallBody, cpv(x,y), cpv(x,y+(rowCount-2)*size), kObstacleRadius);
      outerWallShape->e = kObstacleElasticity;
      outerWallShape->u = kObstacleFriction;
      outerWallShape->collision_type = 0;
      cpSpaceAddStaticShape(space, outerWallShape);
      counter++;
     
      // right border
      outerWallShape = cpSegmentShapeNew(wallBody, cpv(x+(columnCount-2)*size,y), cpv(x+(columnCount-2)*size,y+(rowCount-2)*size), kObstacleRadius);
      outerWallShape->e = kObstacleElasticity;
      outerWallShape->u = kObstacleFriction;
      outerWallShape->collision_type = 0;
      cpSpaceAddStaticShape(space, outerWallShape);
      counter++;
}
 
      for (int row = 1; row < numOfRows-1; row++)
      {
            NSString * tmpRow = [array objectAtIndex:row];
           
            NSString * tmpBelowTheRow = [array objectAtIndex:row-1];
            NSString * tmpAboveTheRow = [array objectAtIndex:row+1];
           
            for (int column = 1; column < numOfColumns-1; column++)
            {
                  // if the current square is 'X' (=88)
                  if([tmpRow characterAtIndex:column] == 88)
                  {
                        // set a visual tile for the correct position
                        [self setTileForX:startXpos+column*tileSize forY:startYpos+row*tileSize forSize:tileSize];
 
                        // if there's no wall above set collision border at the top of the square
                        if([tmpAboveTheRow characterAtIndex:column] != 88)
                              [self setCollisionForDirection:@"UP" forX:startXpos+column*tileSize forY:startYpos+row*tileSize forSize:tileSize];
                       
                        // if there's no wall below set collision border at the bottom of the square
                        if([tmpBelowTheRow characterAtIndex:column] != 88)
                              [self setCollisionForDirection:@"DOWN" forX:startXpos+column*tileSize forY:startYpos+row*tileSize forSize:tileSize];
 
                        // if there's no wall to the left set collision border to the left of the square
                        if((column>0) && [tmpRow characterAtIndex:column-1] != 88)
                              [self setCollisionForDirection:@"LEFT" forX:startXpos+column*tileSize forY:startYpos+row*tileSize forSize:tileSize];
                       
                        // if there's no wall to the right set collision border to the right of the square
                        if((column<numOfColumns-1) && [tmpRow characterAtIndex:column+1] != 88)
                              [self setCollisionForDirection:@"RIGHT" forX:startXpos+column*tileSize forY:startYpos+row*tileSize forSize:tileSize];
                  }
            }
      }
     
      [array release];
     
      [spriteName release];
      [spriteShadowName release];
     
      // add collision pair functions:
      // for exit flag:
      cpSpaceAddCollisionPairFunc(space, 1, 2, &collFunc, nil);
}
 
-(void)setCollisionForDirection:(NSString *)direction forX:(int)x forY:(int)y forSize:(int)size
{
      cpShape *wallShape;
     
      if(direction==@"UP")
            wallShape = cpSegmentShapeNew(wallBody, cpv(x,y+tileSize), cpv(x+tileSize,y+tileSize), kObstacleRadius);
      else
            if(direction==@"DOWN")
                  wallShape = cpSegmentShapeNew(wallBody, cpv(x,y), cpv(x+tileSize,y), kObstacleRadius);
            else
                  if(direction==@"LEFT")
                        wallShape = cpSegmentShapeNew(wallBody, cpv(x,y), cpv(x,y+tileSize), kObstacleRadius);
                  else
                        if(direction==@"RIGHT")
                              wallShape = cpSegmentShapeNew(wallBody, cpv(x+tileSize,y), cpv(x+tileSize,y+tileSize), kObstacleRadius);
     
      wallShape->e = kObstacleElasticity;
      wallShape->u = kObstacleFriction;
      wallShape->collision_type = 0;
      cpSpaceAddStaticShape(space, wallShape);
     
      counter++;
}
 
-(void)dealloc{
 
      cpBodyFree(wallBody);
     
      cpSpaceFreeChildren(space);
      cpSpaceFree(space);
 
      [objectsToRemove release];
 
      [super dealloc];
}
Thank you very much for your help, I found reading this forum very very helpful during the work on my game.

OmerV
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Trouble getting steady 30 FPS in a simple Labyrinth maze gam

Post by slembcke »

cpSpaceResizeStaticHash(space, 11, 600);
For one, you can use a much larger number for the hash size. (2000 instead of 600 maybe?) As for the cell size (11) that depends on the size of your ball and tileSize. Before doing anything else, try using the ball diameter or tileSize and see if that helps.

Are you compiling Chipmunk yourself or using a pre-compiled static library from somewhere? If it's pre-compiled, where did you get it from?
If you are compiling it yourself for the iPhone, you should:
  • Have thumb compilation disabled
  • Make sure you set the floating point type to floats and not doubles (check chipmunk_types.h)
  • Make sure you are not compiling as "debug" which disables optimizations by using -O0
  • Compile using -O2 or better
  • Add -ffast-math to the "Other C Flags" build variable.
Once, you've done these things. Use Shark to profile your game and find out where the CPU time is being spent.

http://www.youtube.com/watch?v=LuOrC95K26E This game had a little about 1000 static line segment shapes, and usually 8 or so active shapes colliding with the terrain at any given time. With the terrain rendering disabled, it could run at 60fps. So there is hope.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
omerv
Posts: 2
Joined: Wed Jan 06, 2010 3:27 am
Contact:

Re: Trouble getting steady 30 FPS in a simple Labyrinth maze gam

Post by omerv »

Thank you very much for all your help, Scott.

I am implementing all of your suggestions:

1. I am now using:

Code: Select all

cpSpaceResizeStaticHash(space, tileSize, numOfStaticObjects*10);
and it does seem to help, thank you!

2. I am compiling Chipmunk myself, not using a pre-compiled static library.

3. After seeing this advice on many posts, I've disabled thumb compilation (pre-posting my question).

4.
[*]Make sure you set the floating point type to floats and not doubles (check chipmunk_types.h)
I don't have a "chipmunk_types.h" file in my project, but in "chipmunk.h" I found:

Code: Select all

typedef float cpFloat;
Is this what you meant?

5.
[*]Make sure you are not compiling as "debug" which disables optimizations by using -O0
[*]Compile using -O2 or better
I was compiling "release" and now changed to -O3 (from the default -Os), thanks!

6.
[*]Add -ffast-math to the "Other C Flags" build variable.
I've seen this advice on many of the posts regarding the FPS issue.
I've tried several ways of doing this, and finally got it to work with "-ffast-math"
I was trying:
-ffast -math (space separated)
--ffast-math (that I saw you advised here: http://www.idevgames.com/forum/showthread.php?t=15488 )

Question: Is there a problem with me taking out the "$(OTHER_CFLAGS)" value from the "Other C++ Flags" (right under "Other C Flags"), leaving it blank? I believe this somehow helps.

This looks better now, in the "XL" maze I'm starting with 18-22.5 FPS that slowly builds up to 22.5-27.5.

Finally - I would really like to understand what you wrote about your game:
With the terrain rendering disabled, it could run at 60fps.
I never get FPS that is above 30, not even from the "S" sized maze.
I believe my game is a lot simpler, all my small wall segments are either fully horizontal or vertical. I have no scroll, only one static screen made up entirely of the maze.
Is my "terrain" rendering enabled and things might slow down drastically because of that?
I also believe the only time I'm doing anything with the images is here:

Code: Select all

-(void)setTileForX:(int)x forY:(int)y forSize:(int)size
{
	Sprite * tileSprite = [Sprite spriteWithFile:spriteName];
	tileSprite.position = ccp(x + (float)size/2, y + (float)size/2);
	[self addChild:tileSprite z:2];		
}
This is called from inside the loop I have running within setupChipmunk.

I am going to use Shark now (for the first time)

Thanks again for all your wonderful help,


OmerV
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Trouble getting steady 30 FPS in a simple Labyrinth maze gam

Post by slembcke »

Rendering on the iPhone is always VBL synced at 60 fps. If your normal rendering code cannot run in less than 16ms then you will end up dropping frames. If it consistently takes between 16ms and 32ms to run your game loop, you will end up running at 30fps exactly. Are you using Cocos2D? I seem to recall people saying that there was code in Cocos 2D that would limit the framerate to 30fps if you couldn't keep 60fps presumably to prioritize smoothness over speed.

I suppose I should be more clear what I meant by "terrain rendering". In that game, each line segment had several fins extruded off of it in order to draw the grass or rock border. We never got far enough to optimize that and just drew all of the terrain every frame. Just drawing a simple line for the terrain instead of several thousand triangles got us from 30-40fps up to a solid 60fps.

One thing to note about using shark is that it tells you where you are spending the CPU time, but not how much time your program spends waiting for things like VBL sync, sleeping threads or IO. You should also look at the total CPU load to figure that out. If your program isn't using a high percentage of the CPU time, then it's doing a lot of waiting.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
Post Reply

Who is online

Users browsing this forum: No registered users and 19 guests