3
\$\begingroup\$

I have a sprite that I use as a sprite sheet, where regions of the image represent various UI elements (window corner, border, background, etc.).

I want to use a certain region of that sprite as a texture that I can tile over a larger destination rectangle.

Setting the SamplerState to LinearWrap and then calling SpriteBatch.Draw with destination rect and source rect ends up stretching that region of the image, not tiling it.

All the examples I've seen that implement tiling use the entire sprite - I only want to use a region of it, and tile that region.

Is there any way to do this?

Edit for clarity:

Here's an example spritesheet

enter image description here

where I want that colored region. I have a rectangle that represents a window border, that is larger than the region in the source image.

Here's what I want to end up with

enter image description here

Here's the code I've been using.

spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
spriteBatch.Draw(sourceTexture, windowTopBorderRect, sourceTextureRegionRect, Color.White, 0, Vector2.Zero, SpriteEffects.None, 0);

And what I get is the image stretched across the window border rectangle, instead of tiling the source region within that rectangle.

enter image description here

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

I don't think using LinearWrap will work in this case, the reason being that LinearWrap takes place automatically when the texture coordinates fall beyond the edges of the texture, but you're using a source rectangle that falls entirely within the texture, so it's not even doing anything.

I can think of three solutions (in the order I'd probably choose):

  1. Create a new Texture2D and copy only that portion of the texture into the new one, or do this offline in your image editor. Then draw it with a source rectangle that has the same size as the destination rectangle. Since this source rectangle will be larger than the texture, wrapping will take place. And since they both have the same size, there won't be any stretching.

    To be more specific what I mean is something like:

    Rectangle windowTopBorderRect = /* wherever you want it to draw */
    Rectangle sourceTextureRegionRect = new Rectangle(0, 0, windowTopBorderRect.Width, windowTopBorderRect.Height);
    

    You could also use the position parameter instead of the destinationRectangle and let the sourceRectangle determine the size.

  2. Alternatively do the tiling manually with multiple SpriteBatch.Draw calls. Basically calculate how many times you need to draw the texture to fill that space, and draw them in the loop. In the case of an horizontal strip like your example, the last draw call will probably need a source rectangle, to crop only the remaining portion that fits that space.

  3. You could also probably draw using a special pixel shader effect that calculates a new texture coordinate that wraps around the original source rectangle without stretching, but this might be tricky.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you. I think your first option is likely what I'll go with. I'd prefer a single spritesheet for organization reasons, but if it's just the UI layer it's not a huge deal. It doesn't seem like what I'm asking for is all that arcane; I guess the team just didn't think of it. \$\endgroup\$ Commented Jul 29, 2012 at 0:21
  • 1
    \$\begingroup\$ @Superstringcheese Note that the primary reason for using sprite sheets is performance (avoiding texture changes). If you're just going to pull the data into separate textures again, then there are advantages to simply using multiple textures in the first place. \$\endgroup\$ Commented Jul 29, 2012 at 7:27
1
\$\begingroup\$

Pass the SpriteBatch a rectangle instead of null for the sourceRectangle parameter. It is defined as a nullable type in the method signature so it is optional and defaults to the whole texture.

Edit: You will also need to change your destination rect with each draw call. Sorry I left that out.

\$\endgroup\$
5
  • \$\begingroup\$ I said in my OP that I'm passing both the source and destination rectangle. I added an example with the method signature I'm using. \$\endgroup\$ Commented Jul 28, 2012 at 15:55
  • \$\begingroup\$ Sorry about that I just reread my answer and fixed it. It is the destination rect that is the problem. \$\endgroup\$
    – Sinthia V
    Commented Jul 28, 2012 at 15:58
  • \$\begingroup\$ Could you possibly work this up locally and verify the outcome, then post your code here? \$\endgroup\$ Commented Jul 28, 2012 at 16:00
  • \$\begingroup\$ No time, sorry. You'll have to do the math yourself. You have to alter the width and position of your destination rect to reflect the width of your source. Modify the position so you don't actually draw it on top of itself. \$\endgroup\$
    – Sinthia V
    Commented Jul 28, 2012 at 16:08
  • \$\begingroup\$ Thanks, but I don't think this is a math problem. It's becoming clear that the Draw method just doesn't support this scenario. \$\endgroup\$ Commented Jul 29, 2012 at 0:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .