Thursday, August 8, 2019

xna - How to create a 2D region where sprites are automatically wrapped?



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.




Answer



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.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...