making seamless repeating backgrounds using photoshop and cocos2d iPhone
I received a lot of inquiries about how the seamless repeating backgrounds were made for my Ninja Jumper iPhone game. So I figured it would be easier to share it here instead of typing a whole bunch of emails…
Ninja Jumper is built on the ever so awesome cocos2d iPhone game engine. My goal was to create beautiful painterly scrolling backgrounds that are very fast to draw. I had to add a new texture drawing function to achieve that, but let’s first look at how to create the art.
Creating repeating backgrounds (or any repeating texture) is very easy in Photoshop. The trick is to use the offset filter for getting rid of the seams.
To demonstrate the technique, I made a very simple background, 512 pixels wide, 256 pixels tall. When creating the background, make sure that the lines on the right edge somewhat match the lines on the left (where the red arrows point.) Of course you don’t need to make it perfect, we’ll fix that in the next step:
Now go to filter > other > offset:
The offset filter works on layers. If you have multiple layers, you should either merge them or offset all of them. Make sure that the wrap around option is active, and offset the image horizontally by half its width (in this case the image is 512 wide, so offset it 256 pixels):
The filter “scrolls” the image so that what used to be on the edges is now in the center. You just need to fix the seams in the middle (turquoise circle) and make sure that you don’t modify anything around the edges now (red areas):
Here’s the image with the seams fixed. The rubber stamp tool will be one of your best friends to do this:
Now offset the layer by half its width again to get the original image back:
Of course fixing the seams on a more complicated image will take a bit longer. Here’s an actual background from the Ninja Jumper game:
The background in Ninja Jumper is made up of several layers. The layers scroll at different speeds to give the illusion of depth. Each layer was created using the process described above and saved as separate images.
I found that PVR images draw a lot faster and are much more memory efficient, so all the textures are converted to 4 bit per pixel PVRs. (2 bits per pixel images are completely useless because of the compression artifacts.)
Let’s look at the drawing code now. This was the first draw method that I tried in Texture2D, but was slow as a 200 lbs chihuahua:
The slow method is:
- (void) drawInRect:(CGRect)rect texOffset:(CGPoint)texOffset stretch:(bool)stretch {
float xOffset = texOffset.x / rect.size.width;
float yOffset = texOffset.y / rect.size.height;
float maxT = _maxT;
float maxS = _maxS;
if (!stretch) {
maxT = rect.size.height / _size.height;
maxS = rect.size.width / _size.width;
}
GLfloat coordinates[] = {
0.0f + xOffset, maxT + yOffset,
maxS + xOffset, maxT + yOffset,
0.0f + xOffset, 0.0f + yOffset,
maxS + xOffset, 0.0f + yOffset
};
GLfloat vertices[] = { rect.origin.x, rect.origin.y,
rect.origin.x + rect.size.width, rect.origin.y,
rect.origin.x, rect.origin.y + rect.size.height,
rect.origin.x + rect.size.width, rect.origin.y + rect.size.height};
glBindTexture(GL_TEXTURE_2D, _name);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
It’s also unnecessarily complicated for our purpose, we really want to draw at a point and don’t care about stretching. Here’s the method I created for drawing a scrolling texture at a point:
- (void) drawAtPoint:(CGPoint)point texOffset:(CGPoint)texOffset
{
float xOffset = texOffset.x / _size.width;
float yOffset = texOffset.y / _size.height;
GLfloat coordinates[] = {
0.0f + xOffset, _maxT + yOffset,
_maxS + xOffset, _maxT + yOffset,
0.0f + xOffset, 0.0f + yOffset,
_maxS + xOffset, 0.0f + yOffset
};
GLfloat vertices[] = { point.x, point.y,
point.x + _size.width, point.y,
point.x, point.y + _size.height,
point.x + _size.width, point.y + _size.height};
glBindTexture(GL_TEXTURE_2D, _name);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
(Wouldn’t syntax highlighting be nice? Oh yeah. I tried several plugins but none seemed to work correctly. So sorry. I’ll try again some day.)
This method is super fast. Call it with the point you want to draw the texture at and the offset (the offset is also a point to allow vertical scrolling as well.)
For example to draw a texture at the middle of the screen, offset horizonally:
Texture2D *scrollingTexture; float horizontalOffset; [scrollingTexture drawAtPoint:CGPointZero texOffset:CGPointMake(horizontalOffset, 0)];
In Ninja Jumper I subclass Sprite and use this drawing method:
- (void) draw {
glEnableClientState( GL_VERTEX_ARRAY);
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
glEnable( GL_TEXTURE_2D);
glColor4ub( r, g, b, opacity);
[texture drawAtPoint:CGPointZero texOffset:CGPointMake(texOffset.x, texOffset.y)];
glColor4ub( 255, 255, 255, 255);
glDisable( GL_TEXTURE_2D);
glDisableClientState(GL_VERTEX_ARRAY );
glDisableClientState( GL_TEXTURE_COORD_ARRAY );
}
Here’s the method that does the scrolling calculation:
-(void)scroll:(float)offset {
texOffset = ccpAdd(texOffset, ccpMult(ccpForAngle(CC_DEGREES_TO_RADIAN(scrollAngle)), offset)); // scrollAngle is the scrolling direction in degrees
CGSize contentSize = texture_.contentSize;
if (texOffset.x > contentSize.width) texOffset.x -= contentSize.width;
if (texOffset.y > contentSize.height) texOffset.y -= contentSize.height;
if (texOffset.x < 0) texOffset.x += contentSize.width;
if (texOffset.y < 0) texOffset.y += contentSize.height;
}
Where drawSize = texture.contentSize and scollAngle is the angle of movement in degrees.
Happy scrolling! Please check out Ninja Jumper…








Where and how do you implement this code? Do you replace the current methods within Texture2D? if so, in both the .m and .h files?
@Exxx: I added the extra drawing method to the Texture2D class. It was the easiest solution, otherwise you have to subclass a whole bunch of classes. (The method is added to .m and the signature to .h.)
I created a subclass of Sprite and overrode the draw method to call - (void) drawAtPoint:(CGPoint)point texOffset:(CGPoint)texOffset instead of - (void) drawAtPoint:(CGPoint)point.
[...] Making Seamless Repeating Backgrounds is a helpful tutorial. [...]
What template are you using to do this. Open GL ES or making a cocos2d one? Also where are you putting all this code?
@Austin: This goes into Texture2D.m. I think there has been some improvements to the Texture2D class so you could actually subclass it instead of modifying it directly, but that way you won’t be able to use TextureMgr.
You know how we end up doing it?
So that there’s no blinking gap between your background
Sprites you could make one end with about 20px of gradient to transparency and put one over another a little bit
It solved it for us - we use simply 3 sprites and move the one that gos off screen to the top and again…
Hi,
I’ve literally spent days trying to get this code to work, but I’m completely failing. Where do you initialize the texture? and what exactly is the texture offset?
Thanks!
Ryan
@Ryan: I subclass the Sprite class and use the spriteWithFile: method to initialize it. That method loads the texture.
Thanks for the reply lajos. I’m just having serious trouble piecing everything togther. Were is the scroll:offset method called?
Thanks for the help in advance
Brilliant blog you have here by the way. Renamed my mac using your other post
I call the scroll method from the scheduled selector of the game (most likely called step: if you are using cocos2d examples).
Is there a way to eliminate the gaps in the background? There is always a small gap scrolling horizontally, and a larger one scrolling vertically.
@rpeck: maybe it’s caused by the texture filtering setting?