A common question I see on the cocos2d forums is ‘when I want to make my game scroll, do I move the camera or the layer?’ or some variant of that. I also got some more detailed questions about how to make a functioning 2D scroller, so I’m going to described how I got it to work.

First of all, the answer to the above question is you move the layer. If you follow the examples in the cocos2d download, you have a “GameScene” and a “GameLayer”. Well, everything that needs to be moved when your game scrolls should be added as a child to that GameLayer. If you are using a TileMap as a background, this includes that tilemap. The only thing that you don’t add as a child to the GameLayer is stuff that does not move with the scrolling view, such as your HUDLayer that has text that shows your characters health. Other than that, your character, the background, other characters, should all be added to the GameLayer.

You have to remember which objects are absolute (attached to your GameScene or other layers) versus those that are relative (a child of your GameLayer) when you set up your touch handling code. For my HUD that has buttons you can press at any time, say to pause the game, you want to add the touch handling object to your GameScene or HUDLayer class, since it doesn’t move. But if you want to be able to touch objects that scroll along with your view in the game itself, your touch handling code needs to be in an object that is a child of your GameLayer.

This might be a little confusing, so let’s see some code:

gameLayer = [GameLayer node]; [gameScene addChild:gameLayer z:zOrder_GameLayer];   hudLayer = [HUDLayer node]; [gameScene addChild:hudLayer z:zOrder_HudLayer];   tileMap = [BGTileMap node]; [gameLayer addChild:tileMap z:-1];   [gameScene addChild:[PauseGameButton node] z:zOrder_GameButtons]; [gameLayer addChild:[FireGunAtTouchPoint node]];

The pause game button is always on your screen, while the point at which your character fires the gun depends on how how far your view has been scrolled (by moving GameLayer).

Let’s see some of the code that actually moves this game layer:

- (void)setViewpointCenter:(CGPoint)point { CGPoint centerPoint = ccp(240, 160); viewPoint = ccpSub(centerPoint, point);   // dont scroll so far so we see anywhere outside the visible map which would show up as black bars if(point.x < centerPoint.x) viewPoint.x = 0; if(point.y < centerPoint.y) viewPoint.y = 0;   // while zoomed out, don't adjust the viewpoint if(!isZoomedOut) gameLayer.position = viewPoint; }

When do you call that method? Well, it depends on what you want, but generally these scrolling games follow around the movement of your ‘main’ character, right? So whatever character the you want to follow, add this to override the standard CocosNode setPosition method so you update your viewpoint whenever the character moves

- (void)setPosition:(CGPoint)point { [[StandardGameController sharedSingleton] setViewpointCenter:point]; [super setPosition:point]; }

Note that the StandardGameController is a construct of mine that I use to separate the game logic out from the display code. It doesn’t matter exactly how you do it, you just need a way to have your main character object call back to something that contains a reference to GameLayer so it can adjust the position of your GameLayer.

Now remember, for your background to scroll properly, you need to add your background tileMap as a child of your GameLayer that is being moved around.

That being said, I found that an important method was missing from the cocos2d tilemap that I need to use in order to detect collisions based on the types of tiles encountered. I created a subclass of TMXTiledMap and added in these methods:

- (CGPoint)coordinatesAtPosition:(CGPoint)point { return ccp((int)(point.x / self.tileSize.width), (int)(self.mapSize.height - (point.y / self.tileSize.height))); }   - (unsigned int)getGIDAtPosition:(CGPoint)point { return [layer tileGIDAt:[self coordinatesAtPosition:point]]; }

That way it’s easy to figure out what tile any individual object is colliding with. For example, in my main character object I can have code that runs in step: function with this:

BGTileMap* tileMap = [StandardGameController sharedSingleton].tileMap; CGPoint coordinate = [tileMap coordinatesAtPosition:self.position]; BBLog(@"Right now on tile %d",[tileMap.layer tileGIDAt:coordinate]);

Now I know what type of tile I am overlapping, and I can respond to the environment accordingly.

This is really all the code that you need to make a scrolling game view…I think some people overthink it and try adjusting the position of every object individually with some offset, but it’s not necessary since your objects can use relative positions with their parent.

I have one last bit of code to add, and this is something kind of fun. It’s not complete, but at least it’s a start. What this allows you to do is ‘zoom out’ so you can see your entire map with ALL the objects shrunk down, and then zoom back in to your character. The only tricky part is when you zoom back in, you have to slowly adjust your viewpoint in steps so the zoom in action is centered on your character, instead of jumping at the end.

#define ZOOM_BACK_IN_INTERVALS 10 #define ZOOM_OUT_RATE 0.3 // TODO need to refine this so for each step it uses the new viewpoint - (void)setZoom:(BOOL)zoomedIn { BBLog(@"Zooming in %d", zoomedIn);   // this scales it out so the whole height of the tilemap is in the screen float zoomScaleFactor = 320 / (tileMap.mapSize.height * tileMap.tileSize.height); if(zoomedIn) { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:1.0]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackIn:) interval:ZOOM_OUT_RATE]; // need this for the transistion } else { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:zoomScaleFactor]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:ccp(0,0)]]; isZoomedOut = YES; } }   // need this small correction at the end to account of the player is moving and the viewpoint has changed to avoid jitter #define ZOOM_OUT_CORRECTION_RATE 0.3 - (void)setZoomedBackIn:(ccTime)dt { [self unschedule:@selector(setZoomedBackIn:)]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_CORRECTION_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackInFinished:) interval:ZOOM_OUT_CORRECTION_RATE]; // need this for the transistion }   - (void)setZoomedBackInFinished:(ccTime)dt { [self unschedule:@selector(setZoomedBackInFinished:)]; isZoomedOut = NO; }

Now you see why my viewPoint variable was a class member…you’ll need it for these methods to work.

The reason this code isn’t complete is when it starts zooming in, it creates the actions to zoom in on the current viewpoint, but if the player is moving by the time the zoom animation is done, that viewpoint is changed and needs to ’snap’ to the new viewpoint. That is the reason for the setZoomedBackIn: method, which really shouldn’t have to move the gameLayer anymore. However, I haven’t yet written the code to continuously create smaller move actions to take into account a moving viewpoint as the animation continues, but doing so shouldn’t be that hard. If you want to see that bit when I finish, post in the comments and I’ll add it in.