I've written an article about a general purpose solution that lets you scroll an image infinitely in every direction even if your camera is transformed. But it's done in XNA with HLSL and relies on the texture addressing mode being set to wrap, so I'm not sure if it could be translated to Silverlight. Here's the link:
For a less general purpose solution, that doesn't use a vertex shader you could try the following link where I describe two other ways to solve it (the first should work anywhere since you only need the ability to choose a source rectangle when drawing):
To give a general description of the techniques linked above...
Basically, your way of scrolling the background by drawing it twice and moving these two pieces around when the player moves outside a certain range, is the way I first implemented texture scrolling.
But soon I figured there were several limitations to that approach, and throughout the years found about a few other ways to do this that are much more flexible and general.
For instance, first I wanted to have the background scroll diagonally instead of horizontally, and so the original technique broke. Later on I found a way to achieve the same thing in a much easier way so I changed to that. Eventually I wanted to be able to scale and rotate the camera as well so had to go even further.
Here's a summary of all three that I described on my blog (code samples are XNA/HLSL). If possible, I really recommend the first one, but of course it depends on your platform's limitations.
- Create a fullscreen textured quad (a SpriteBatch.Draw with destination rectangle set to the entire screen will do just fine) and map the texture you want to scroll to it.
- Set the texture addressing mode to wrap so that it automatically wraps whenever the texture coordinates leave the normalized [0,1] range.
- Use a vertex shader to generate new texture coordinates for your quad's vertices based on your camera's transform. This transform is pretty much the inverse of your view matrix, but with translations being done in texture space (i.e. dividing the translation amount by the texture size).
I use this vertex shader (where 99% of it is boilerplate needed for SpriteBatch to work - only the last line is new):
sampler TextureSampler : register(s0);
void SpriteVertexShader(inout float4 color : COLOR0, inout float2 texCoord : TEXCOORD0, inout float4 position : POSITION0)
// Half pixel offset for correct texel centering.
position.xy -= 0.5;
// Viewport adjustment.
position.xy = position.xy / ViewportSize;
position.xy *= float2(2, -2);
position.xy -= float2(1, -1);
// Transform our texture coordinates to account for camera
texCoord = mul(float4(texCoord.xy, 0, 1), ScrollMatrix).xy;
And pass it this matrix, which is calculated using the properties of my camera:
public Matrix GetScrollMatrix(Vector2 textureSize)
return Matrix.CreateTranslation(new Vector3(-Origin / textureSize, 0.0f)) *
Matrix.CreateScale(1f / Zoom) *
Matrix.CreateTranslation(new Vector3(Origin / textureSize, 0.0f)) *
Matrix.CreateTranslation(new Vector3(Position / textureSize, 0.0f));
- Pros: Single quad being drawn to cover the entire screen. Works perfectly with a zoomed and rotated camera too.
- Cons: Needs vertex shader and texture address set to wrap.
- Same as Technique 1 Step 1
- Same as Technique 1 Step 2
- Play around with SpriteBatch.Draw source rectangle by setting the rectangle's X/Y to be the camera's position and the rectangle's Width/Height to be the screen's size.
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
spriteBatch.Draw(texture, position, new Rectangle(-scrollX, -scrollY, texture.Width, texture.Height), Color.White);
- Pros: Simplest method. Doable in 2/3 lines of code. No need for vertex shader either.
- Cons: Still needs texture wrap addressing mode. You can still move the camera around in every direction but you can't rotate it. You can however zoom the camera with just a little trick: just scale the source rectangle's size along inversely to the camera's zoom (i.e. the larger the camera zoom, the smaller the rectangle - just divide the original rectangle size by the zoom factor).
This method doesn't need anything other than being able to specify a source rectangle on drawing.
- It's a variation of your idea of drawing the background twice depending on the player's position, but enlarged to all four directions (so that it also repeats up down), and instead of moving the pieces around, you move their source rectangles (so the pieces stay still but the content you see changes).
- Basically draw the texture four times like in the image below, and depending on the camera's position you draw more, or less of each of them to give the impression that the camera is moving.
Rectangle source1 = new Rectangle(0, 0, texture.Width - scrollX, texture.Height - scrollY);
Rectangle source2 = new Rectangle(texture.Width - scrollX, 0, scrollX, texture.Height - scrollY);
Rectangle source3 = new Rectangle(0, texture.Height - scrollY, texture.Width - scrollX, scrollY);
Rectangle source4 = new Rectangle(texture.Width - scrollX, texture.Height - scrollY, scrollX, scrollY);
Vector2 position1 = new Vector2(position.X + scrollX, position.Y + scrollY);
Vector2 position2 = new Vector2(position.X, position.Y + scrollY);
Vector2 position3 = new Vector2(position.X + scrollX, position.Y);
Vector2 position4 = new Vector2(position.X, position.Y);
spriteBatch.Draw(texture, position1, source1, Color.White);
spriteBatch.Draw(texture, position2, source2, Color.White);
spriteBatch.Draw(texture, position3, source3, Color.White);
spriteBatch.Draw(texture, position4, source4, Color.White);
And you also need to be careful and wrap the scrollX and scrollY values around so that they don't leave the texture.
- Pros: Doesn't need many features to implement.
- Cons: Can only move camera around, no other transformations.