6
\$\begingroup\$

Edit

Finally managed to get this to work, after two separate questions! Here's a video demonstration:

http://www.youtube.com/watch?v=nZ7e0jegvs0

Will write an article about it later when I have a chance since there's a lot of little tricks involved. Thanks to everyone who helped figure this out. Meanwhile, the solution for the rendering part is described on this question, while for the physics part of the solution, check this other question.


Disclaimer

I'm looking for intelligent solutions that encapsulate this behavior in a way that is transparent to the user. Don't simply answer "draw the sprite four times". For instance, something that could be packed up in a class like:

class Sprite
{
    Vector2 Position;
    float Rotation;
    float Scale;

    Texture2D Texture;

    Rectangle? WrapRegion;

    void Draw(SpriteBatch spriteBatch);
    bool IsColliding(Sprite other);
}

By the way, there's no need to post any code, just a description of how to solve each of the problems will do.

Problem

1 - By wrapped I mean that when they leave the borders of that region, they continuously appear on the opposite side, i.e:

enter image description here

So simply changing the sprite's position clearly is not enough. The sprite might have to be drawn either 2 or 4 times depending on its position in relation to the region.

And despite needing to draw it several times, I should still be able to do, for instance:

sprite.Position += Vector2.One; // Every frame

And the sprite's Position would automatically wrap within the area.

2 - Another problem would be how to handle collisions between wrapped sprites properly. For instance:

enter image description here

This should trigger only one collision (okay, consider that they are overlapping a bit although the drawing might not suggest it).

Checking for the collision manually shouldn't be too hard.

But the bigger problem arises when using this together with a physics engine (which is what I tried to do before and failed). How would I coerce the physics engine into handling this problem correctly, since there's usually a 1 to 1 correspondence between the physics body and the sprite representation?

Edit

You can see the solution to this particular point here. I create 9 bodies attached to each other using the joint described in that question, like so:

enter image description here

3 - Finally, and being more specific about XNA in this case, if the wrap region happens to be smaller than the Viewport, what would be the best way to clip the sprite so that it does't show outside the region anywhere?

Take into consideration that the sprite may be rotated or scaled too.

\$\endgroup\$
2
  • \$\begingroup\$ What's supposed to happen in a sprite is larger than the 'wrapBox'? Especially if the two new 'ends' aren't the same dimensions? \$\endgroup\$ Commented Dec 15, 2011 at 18:51
  • \$\begingroup\$ Good point, hadn't thought about that case, but let's just consider that when the sprite is larger than the wrap box, it will behave like a normal sprite without wrapping, and ignore that possibility. \$\endgroup\$ Commented Dec 15, 2011 at 19:07

1 Answer 1

3
\$\begingroup\$

Problem 1: I solved this by using a render target that was larger than the area. It should be able to encompass the halfwidth/halfheight of your biggest sprite. In my example, I just decided to use a size three times as high and wide as the game render area. I cut the render target into nine pieces, and render them all on top of each other. Here's the code:

private const Int32 WIDTH = 320;
private const Int32 HEIGHT = 240;
private RenderTarget2D wrapTarget;

protected override void LoadContent()
{
    this.wrapTarget = new RenderTarget2D(this.GraphicsDevice, WIDTH * 3, HEIGHT * 3);
}

protected override void Draw(GameTime gameTime)
{
    this.GraphicsDevice.SetRenderTarget(this.wrapTarget);
    this.GraphicsDevice.Clear(Color.Transparent); // has to be transparent, to allow all segments to overlap properly.

    // the translation matrix saves you from offsetting all coordinates, since we want to draw at the center segment of the wrapTarget rendertarget.
    this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, Matrix.CreateTranslation(WIDTH, HEIGHT, 0.00f));

    this.spriteBatch.Draw(this.circle, this.circlePosition, null, Color.White, 0.00f, new Vector2(this.circle.Width / 2.00f, this.circle.Height / 2.00f), Vector2.One, SpriteEffects.None, 0);
    this.spriteBatch.Draw(this.square, this.squarePosition, null, Color.White, 0.00f, new Vector2(this.square.Width / 2.00f, this.square.Height / 2.00f), Vector2.One, SpriteEffects.None, 0);

    this.spriteBatch.End();


    this.GraphicsDevice.SetRenderTarget(null);
    this.GraphicsDevice.Clear(Color.LightYellow); // this is the actual background color you want.

    this.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);

    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(0, 0, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(WIDTH, 0, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(WIDTH * 2, 0, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(0, HEIGHT, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(WIDTH, HEIGHT, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(WIDTH * 2, HEIGHT, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(0, HEIGHT * 2, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(WIDTH, HEIGHT * 2, WIDTH, HEIGHT), Color.White);
    this.spriteBatch.Draw(this.wrapTarget, Vector2.Zero, new Rectangle(WIDTH * 2, HEIGHT * 2, WIDTH, HEIGHT), Color.White);

    this.spriteBatch.End();


    base.Draw(gameTime);
}

Problem 2: This I have no idea how to solve.

Problem 3: This method automatically does that. You can replace the Vector2.Zero in the nine SpriteBatch.Draw inside the second SpriteBatch.Begin/End block to draw it wherever you'd like.

\$\endgroup\$
6
  • \$\begingroup\$ I'm glad I asked this question. This is the sort of answer that went completely outside the box and nailed the problem. And best of all, the processing is done at the layer level, not at the sprite level. That's even better, thanks! Now I wonder if someone will come up with something equally brilliant for handling physics. :-) \$\endgroup\$ Commented Dec 16, 2011 at 3:28
  • \$\begingroup\$ @DavidGouveia, I'm glad you like the answer. I figured this solution was perfect namely because it avoids any per sprite work, and instead works very naturally. It's almost the plug'n'play of programming. Oh, and I'm looking forward to seeing a tutorial about 2D render wrapping on your blog. ;) \$\endgroup\$
    – William
    Commented Dec 16, 2011 at 4:08
  • \$\begingroup\$ I was indeed thinking of writing about it, since I haven't updated the blog for a while. I've also just had an idea that might solve the physics problem, just need to give it a test run! Do you have a personal website that I could link to in order to give you credit for the idea? \$\endgroup\$ Commented Dec 16, 2011 at 4:12
  • \$\begingroup\$ @DavidGouveia, I don't have a personal website yet, but thanks for the thought. I'm just glad I could help you out. :) Make sure to let us know if you solve the physics issue. \$\endgroup\$
    – William
    Commented Dec 16, 2011 at 4:15
  • \$\begingroup\$ Of course! I'll download Farseer now and give my idea a try. \$\endgroup\$ Commented Dec 16, 2011 at 4:17

You must log in to answer this question.

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