Question

So I made a really simple iOS game that has multiple objects that scroll across the screen and check for collisions along the way. Currently to move them, I have an NSTimer that fires every 0.01 seconds or so and moves the frame by 2 pixels. It then checks the player's location for intersecting frames and acts accordingly.

This works really well and I have no issues with it on my own device, however I have noticed that there is sometimes a lag when there are more than 20 objects on the screen moving at the same time on older devices (like an iPad 3, for instance).


After profiling the app, it appears that there is about 70% of the CPU going to this one thread. Is this normal? Would it reduce the load on the CPU if I added each of the objects to a CALayer on a single stationary view and updated the positions on that layer, rather than having 25-30 separate UIImageViews on the screen moving all at the same time? Or is there a better way to do this?

I figured I'd ask before I go to all the trouble of converting all of my code to work with layers. Is there anyone who has experience writing a game like this without any lag on old devices?

I want to support as many devices as possible, but the older ones seem to have a hard time running this (which I find odd since the game is incredibly simple compared to some others I have made).

Additional Info:

Deployment Target: 5.0
iPad Device iOS: 7.0.6
iPhone 5 Device iOS: 7.0.6
XCode Version: 5.0.2


This is the code that runs when the timer is fired. CView is basically a UIImageView with a few additional int/bool properties.

for(CView* c in self.view.subviews)
{
    if(![c isKindOfClass: [CView class]])
    {
        continue;
    }
    
    [c setFrame:[CRect rect:c.frame withNewY:c.frame.origin.y+2]];
    
    if(fabsf(c.frame.origin.y + c.frame.size.height - (player.frame.origin.y+player.frame.size.height)) <= player.frame.size.height)
    {
        if([player collideWith:c.x])
            [self gameOver];
    }
    else if((c.frame.origin.y + c.frame.size.height) > (player.frame.origin.y + player.frame.size.height) && c.passed == NO)
    {
        c.passed = YES;
        score++;
    }
    
    if(c.frame.origin.y > [CRect height])
    {
        [self removeView:c];
    }
}

if(score != score2)
    [scoreLabel setText:[NSString stringWithFormat:@"%d",score]];

score2 = score;

reps++;
if(reps < 0)
    return;

NSArray* formation = [formations objectAtIndex:(int)[Global randomFloatBetween:0 andLarge:11.99]];

float max = 0;

for(int i = 0; i < formation.count; i++)
{
    if([[formation objectAtIndex:i] intValue] == -1)
        continue;
    
    if([[formation objectAtIndex:i] floatValue] > max)
        max = [[formation objectAtIndex:i] floatValue];
    
    float x = [CRect centerX:(player.laneWidth*5)+(player.lineWidth*4)] + ((i) * (player.xWidth+player.lineWidth)) + player.margin;
    float y = -101 + ([[formation objectAtIndex:i] floatValue] * (-130));
    
    CView* c = [[CView alloc] initWithFrame:CGRectMake(x, y, 49, 101)];
    [c setX:i+1];
    [c setImage:[images objectAtIndex:(int)[Global randomFloatBetween:0 andLarge:images.count-0.01]]];
    [self.view insertSubview:c belowSubview:adBanner];
}

reps = (-101 * (max+1)) - 202;
reps /= 1.75;

Could this have something to do with me creating CViews this way and just nullifying them when they go offscreen? If I were to use a fixed number and just re-use old CViews (just put them at the other end of the screen and re-run them when they finish crossing) would that improve performance? I don't know how often ARC collects garbage.

Was it helpful?

Solution

You should either consider using an existing game engine such as cocos-2d or take your time to refine your own engine using common patterns and best practices. If you decide to go with an existing engine there isn't much to say other than read their documentation and ask specific question if you get stuck. Don't ask questions about what engine to use. That kind of question isn't a good fit for Stack Overflow.

If you continue working on your own engine then I would still recommend that you study the documentation and programming guides for both cocos-2d and Sprite Kit. That will help you architecture your own engine. For example, this is an image from the Sprite Kit Programming Guide explaining the game loop.

enter image description here

If you are interested in writing a game engine using Core Animation then I would recommend that you read this series of blog posts by Matt Gallagher. It's a couple of years old but should still be mostly relevant.

When you have structured your game engine into these steps

  1. update (move things)
  2. action (respond to user interaction)
  3. simulate (collision detection)
  4. render

Then you can start to measure the performance and memory efficiency of the different steps and keep working on the heaviest piece until you reach acceptable performance.

Maybe it's worth reusing views when they move off screen, maybe your collision detection is too expensive and should be optimised, maybe moving to layers would be worth it. You have to measure first to know where to spend your time and effort.

However, I will tell you that you should drop the NSTimer(s) and use a single CADisplayLink for the game loop to keep the updates in sync with the display.


As a side note:

I don't know how often ARC collects garbage.

This sentence worries me. It shows that you have not understood what ARC is and how it works. ARC is not a garbage collector. I would advice you to go back and take a second look at memory management in Objective-C

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top