Repeating an array of tiles on all sides

Hello all,
I’m sorry if the topic is a little vague as I’m not sure exactly how to word it. I only post on forums maybe twice a year, haha!
Anyway, I have a problem. I know what I want to happen, but I dunno how to approach it with my current set up and I’m pretty sure I’ve searched out every possible topic on it that I can think of.

I’m working on a “world map” of sorts and am using a sprite sheet with each tile a 64 x 64 image.
I’m loading the rectangles into a dictionary and calling them based on a number found in the int[,] array, or grid if you will.

I draw the map and all is well and perfect, but then, like any good world map, I want it to wrap from all sides, or repeat to be able to “circle the world” so to speak. I cannot for the life of me fin any documentation on this. All that I find is just what I’ve already accomplished.

I’ll show some code here to show HOW I’ve drawn the tiles. It’s better than my trying to type it haha.
Here’s the Draw


spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null, null, transformMatrix: camera.GetViewMatrix());
            
int spriteX = 0;
            int spriteY = 0;
            Rectangle texture;

            for (int x = 0; x < worldLayout.GetLength(0); x++)
            {
                for (int y = 0; y < worldLayout.GetLength(1); y++)
                {
                    int tileNumber = worldLayout[x,y];

                    texture = worldMap[tileNumber];

                    spriteBatch.Draw(worldMapSpriteSheet, new Vector2(spriteX, spriteY), texture, Color.White, 0, new Vector2(0, 0), 1.0f, SpriteEffects.None, 0);
                    spriteX += texture.Width;
                }
                spriteX = 0;
                spriteY += 64;
            }

            spriteBatch.End();

Here’s the result and what I’d LIKE to happen

Don’t mind the numbers on the images, that’s my debug. The numbers match the dictionary entry to ensure I have the right tile is all.

As it is, I’m using the camera library built into .Extended and move the output around based on that matrix. Eventually I’ll have that camera snapped to a character, but for now, I’m focused on getting this small island to repeat to prepare for a much much bigger world!

With this set up, I understand that each tile is it’s own rectangle which is where I’m hung. How can I wrap this around with all of these rectangles and what will that do to the performance? Is there a better way to approach this?

Does any of this make sense?
Thanks for any feedback!

1 Like

This is your for loop for x now:

for (int x = 0; x < worldLayout.GetLength(0); x++)

You can change this to

for (int xShift = xStart; xShift < xStart + worldLayout.GetLength(0); xShift++) 
{
    int x = xShift % worldLayout.GetLength(0);

Now you can shift the world to the right by increasing xStart. The rest of your code can stay as it is. The % in the bottom line is called the modulus operator, it’s exactly what you need. Just look it up if you want to know more about it.

I think I remember that the c# modulus operator doesn’t work quite right for negative numbers, which means shifting to the left might not work properly. In this case just replace the % with your own modulus function, I think this will do it:

int Modulus(int val, int div)
{
     val %= div;
     if (val < 0)
         val = div + val;
     return val;
}

Hi Markus!
Thanks for the reply!
I implemented your code into mine and saw no change in results.
Debug is off, and the tile map doesn’t repeat.

Here’s the code:



spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null, null, transformMatrix: camera.GetViewMatrix());

int spriteX = 0;
            int spriteY = 0;
            Rectangle texture;

            for (int xShift = spriteX; xShift < spriteX + worldLayout.GetLength(0); xShift++)
            {
                //int x = xShift % worldLayout.GetLength(0);
                int x = Modulus(xShift, worldLayout.GetLength(0));

                for (int y = 0; y < worldLayout.GetLength(1); y++)
                {
                    int tileNumber = worldLayout[x,y];

                    texture = worldMap[tileNumber];

                    spriteBatch.Draw(worldMapSpriteSheet, new Vector2(spriteX, spriteY), texture, Color.White, 0, new Vector2(0, 0), 1.0f, SpriteEffects.None, 0);
                    spriteX += texture.Width;
                }
                spriteX = 0;
                spriteY += 64;
            }
        }

        int Modulus(int val, int div)
        {
            val %= div;
            if (val < 0)
                val = div + val;
            return val;
        }
            spriteBatch.End();


I tried with and without the Modulus method. I was stumped on the xStart var. This code assumes that was supposed to be spriteX.
With this implementation, the var “x” is only used to grab the item in the array at index “x,y”. Did I misunderstand what the new purpose of “x” was?
Also, I just realized, does it even matter if I have SamplerState.LinearWrap in the Begin? Doesn’t that imply that I want to “wrap” every individual image in each rectangle on screen right now?

I KNOW I’m probing a lot, but thanks for the answer and any future help!

The xStart variable is for shifting the world. I should have probaly called it mapShiftX or something. If set to zero everything will be exactly as it was. If you set it to 1 the world will shift by one tile to the right.

EDIT: and by “shift by one tile to the right” I actually mean left :smile:

The SamplerState wrapping only matters when your texture coordinates go below 0 or above 1.

Okay so now I understand the code and the effect, unfortunately it’s not the effect I’m going for. Or maybe it is? And something needs to be done to my camera?

It does repeat, but it doesn’t repeat on all sides or scroll as intended. The best thing I know to do is upload a quick video showing the output, here’s the link.

As you can see, it is wrapping but within the grid and the grid does not scroll. I’ll post any code that you need, just let me know.

The idea is that this should fill the screen and wrap as if you were traveling around the world. Think Final Fantasy, Evoland, any 8-bit Dragon Quest game. Games like that. I apologize for not being able to explain this properly. :frowning:
To be clear, I think I broke it. Hahaha

I just wanted to show you the basic concept of how to make a wrapping world, and as I’m seeing in the video it’s doing that already.
You still need to link this to your camera. The easy solution is to just have the camera move around and draw a much bigger world:

int drawSize = worldLayout.GetLength(0) * 4;
for (int xShift = xStart; xShift < xStart + drawSize; xShift++)

This should draw 4 world lengths. Can you try if that gives you the effect you are looking for?
Of course it’s wasteful to draw that much world. Ideally you only draw what’s visible on screen, but that’s an optimization for later. For now I’d like to know if that’s what you want.

And if you want to extend the world in the negative direction as well then xStart just need to be negative. I didn’t expect your camera view to be bigger than the entire world. I hope you get the idea now.

Ok yep! I plugged that in and got 4 full images of my world map, but no more after that. It’s on that right path, but I’d like for it to repeat forever. You know how we accomplish parallax? It’ll go on forever!

Like:


public void UpdateUIParallaxBG()
        {
            // Scroll UI BG Layer
            UIScrollX += 0.4f;
            UIScrollY += 0.5f;
        }

        public void DrawUIParallaxBG(SpriteBatch spriteBatch, Texture2D texture)
        {       
            spriteBatch.Draw(texture, Vector2.Zero, new Rectangle((int)-UIScrollX, (int)-UIScrollY, 1920, 1080), Color.White);
        }

Couldn’t something to this effect be used to repeat the world map when a player presses a directional button?
Or am I just off the deep end here?

Of course this can be repeated forever, it’s just a matter of calculating proper values for xStart and drawSize, they have to match your camera. xStart marks the beginning of the draw area and drawSize has to be big enough to cover the screen. Something like this:

drawSize = (int) (cameraViewWidth / tileWidth);
xStart = (int) (-cameraPos.X / tileWidth - drawSize / 2);

cameraViewWidth is how much world your camera sees. You could calculate that from your camera field of view and distance, or just find the right value by trial and error if distance and FOV is constant. tileWidth is the size of one tile.

EDIT: You are probably not using a perspective camera, right? For an orthographic camera cameraViewWidth is just the width of your orthographic camera.

EDIT2: oh, this won’t work with your current method of calculating spriteX and spriteY. It has to be

spriteX = x * tileWidth;
spriteY = y * tileHeight;

Yeah, in the end, the view will be 1080p. so the viewport is 1920 X 1080.
The camera is the baked in Camera2D model that is in .Extended. I’ve not needed a camera until this point so I’m learning on that front as of last night.

In a nearby class, it’s basic code looks like this:


private void UpdateWorldMovement(GameTime gameTime)
        {
            Vector2 movementSpeed = Vector2.Zero;

            if (Control.IsKeyPressed(Keys.Up))
            {
                movementSpeed = new Vector2(0, -1);
            }

            if (Control.IsKeyPressed(Keys.Down))
            {
                movementSpeed = new Vector2(0, 1);
            }

            if (Control.IsKeyPressed(Keys.Left))
            {
                movementSpeed = new Vector2(-1, 0);
            }

            if (Control.IsKeyPressed(Keys.Right))
            {
                movementSpeed = new Vector2(1, 0);
            }

            movementSpeed *= 5;

            camera.Move(movementSpeed);
        }

I haven’t adjusted anything else with the camera… yet haha
Not really sure what type of camera it is besides 2D… :frowning:

Oh ok, you’re working in pixels and you always want to draw the world fullscreen, you can get the drawSize like this

drawSize = screenResolution.X / tileWidth;

drawSize is simply the number of tiles you can fit next to each other on the screen.
I haven’t used the camera from .Extended, but I’m sure it has a position.

So, so after much trial and error and making this WAY more complicated for myself than it needed to be, I stepped away for a few days and a working solution hit me. Sometimes, you just gotta walk away.

I’m posting my solution here for anyone else that might need an answer.
It was so simple, don’t repeat, create a buffer layer around the “land” and then seamlessly whisk the player to the other side of the map at the proper x, y based on the x, y they hit!

Here’s the Player class METHOD to whisk the player sprite


private void EdgeDetection(MapTile[,] map)
        {
            int xLine = map.GetLength(0);
            int yLine = map.GetLength(1);

            if (positionX == map[0, 17].positionX)
            {
                positionX = map[0, yLine - 18].positionX;
            }

            if (positionX == map[0, yLine - 17].positionX)
            {
                positionX = map[0, 18].positionX;
            }

            if (positionY == map[10, 0].positionY)
            {
                positionY = map[xLine - 11, 0].positionY;
            }

            if (positionY == map[xLine - 10, 0].positionY)
            {
                positionY = map[11, 0].positionY;
            }
        }

The hard coded numbers in the map[x,y] are the tile buffers that I was speaking of.
With this setup, no matter how big my world map gets, it’ll always go for those specific indexes on all sides. That’s the buffer… err… “ocean”

The .Extended camera was decent, but it was much easier to just create my own and snap it to a player sprite. I had a little help with online searching to build it. The player sprite moves with the code above and then checks the edge positions with the method above. The camera sticks to the player no matter what. So when the player is magically whisked to the other side of the map, the camera goes with it!

Here’s the camera


public class Camera
    {
        public Matrix Transform { get; private set; }
        public int positionX;
        public int positionY;

        public void Follow(Sprite target)
        {
            var position = Matrix.CreateTranslation(
                        -target.positionX - (target.texture.Width / 2),
                        -target.positionY - (target.texture.Height),
                        0);

            var offset = Matrix.CreateTranslation(
                         StateManager.screenWidth / 2,
                         StateManager.screenHeight / 2,
                         0);

            positionX = (int)target.positionX;
            positionY = (int)target.positionY;

            Transform = position * offset;
        }
    }

I’m sure there are 50 better ways to do this and I’m also sure that my code will change as I progress, but for now, here’s a starter solution to my issue.

Here’s a video of the results

Don’t laugh at my character art! It’s SPECIAL!!! Haha

Thanks for all of the replies and help. I hope that this code helps someone else get started!

1 Like

Tell me if this is clearly said.

The world is made of a big tile map a array.

I define a Vector2 camera top left position into that world.

I define a Point tile size for the camera that looks at that world of tiles array, this size describes how many tiles i will draw from the tile map.
I define a tile Rectangle from these two that describes the area i see in tiles casting the vector2 to point.

When i move the camera i move the above vector2 position then update the rectangle.

When i draw
I draw from the top of the tile camera rectangle to the bottom and from the left to the right.
In a pair of for loops for(x = rectangle.Left to rectangle.Right ect…

In the loops i create a variable currentX and set the currentX index from the current for{} looped x value (similarly with y).

____________________________________
I compare x to the width of my tilemap while it is equal to or over then i subtract currentX = x - width;
I compare x to 0 if it is less then 0 the left side of my tilemap then i add that negative value to the width currentX = width + x;
____________________________________

I repeat this process for y and height in the other loop.

I take the source tile from the tilemap array at currentX and currentY as the tile to be drawn.

Before i draw a tile from the map to the screen at position x - rectangle.Left * tileWidth or position y - rectangle.Top * tileHeight.

I account for the little bit of fractional scrolling of the vector2 position allowed before i draw.
By taking the fractional portion of the Vector2 position multiplying it against the tile width and height i have a small pixel offset that i can subtract from that final screen drawing position of all the tiles i draw.

I draw the tiles.

To move thru the world and scroll as well as to loop around it.
I move the camera by changing the value of the vector2 position at the beginning of the reply.