When creating a map, we’d like to keep the players within bound. But it can also be nice to have the world actually wrap on itself: this can make for an interesting game mechanic, and it also gives an impression of increased freedom to the player.
In this article, we’ll show you a simple to implement this kind of behaviour for a 2D game.
Clamping Player Position in a N by M Grid Map
In most cases, a map for a 2D game is defined as a fully-filled N by M grid. In this case, the obvious solution to keep our player’s position within bounds is to check for its position relative to the defined bounds, and adjust accordingly.
This can be done with a simple monobehaviour component:
classPositionClamper: MonoBehaviour {publicint mapWidth;publicint mapHeight;voidLateUpdate() {Vector3 pos = transform.position;// assuming map starts at (0, 0) pos.x = Mathf.Max(Mathf.Min(pos.x, mapWidth), 0); pos.y = Mathf.Max(Mathf.Min(pos.y, mapHeight), 0);// setting the transform position. Consider using local position when possible transform.position = pos; }}
This will ensure the player’s position is clamped to the map dimensions.
One thing to note is that multiple game objects will be checking these boundaries, so you should consider rewriting this to use an external source of data such as a Scriptable object, or a Singleton. This will be way more convenient, and incredibly less error prone than specifying the fields directly in each game object.
Implementing Wrapping Coordinates
This is also fairly simple, we’ll just need to check whether the transform position is out of bounds, and adjust it accordingly.
We’ll also use a boolean to check whether we want the position to wrap or not.
classPositionClamper: MonoBehaviour {publicint mapWidth;publicint mapHeight;publicbool isWrapping;voidLateUpdate() {Vector3 pos = transform.position;if (bIsWrapping) {// if we are reach the x boundary of the map, wrap the position back to 0 // using the map dimensionif (pos.x > mapWidth) { pos.x -= mapWidth; } elseif (pos.x <0f) { pos.x += mapWidth; }// we do the same thing for yif (pos.y > mapHeight) { pos.y -= mapHeight; } elseif (pos.y <0f) { pos.y += mapHeight; } } else { pos.x = Mathf.Max(Mathf.Min(pos.x, mapWidth), 0); pos.y = Mathf.Max(Mathf.Min(pos.y, mapHeight), 0); }// setting the transform position. Consider using local position when possible transform.position = pos; }}
That way, when the player reaches the map boundaries, he will actually be teleported to the other of the map.
To note, there is actually a cleaner way to write the above code and avoid writing the ifs: use the modulo operator!
The main problem with the approach mentioned above is that the player will appear to “teleport”, when viewed by someone else.
Once again, we use a simple trick: we’ll just draw more sprites!
Using our current player game object as the center, we’ll need to add other game objects containing the same sprite but at different locations: (-mapWidth, 0), (mapWidth, 0), (0, -mapHeight), (0, mapHeight).
While simple, this approach will need you to share the current state of the sprite between the children and you’ll need to do this for all your game objects which have a sprite renderer.
Still, since you’re drawing the same sprites multiple time, they will be batched so impact on performance shouldn’t be too much of a problem.