How should I implement collision detection in a wrapping 3D world?

(Edit: Adjusted the question to clarify it, based on helpful feedback below)

Hi there,

I’m working on a 3D game. The world is a flat square world. I want it so that when the player flies north, they will appear on the south side of the map; when they fly too far south they will appear on the north. And the same situation for east/west.

For example, when the player flies directly east, she reappears on the west side of the map, at the same north/south position.

The world will be roughly 5000 metres by 5000 metres. But sparsely populated, mostly by flying things.

Assume the game is single-player. Assume the game doesn’t need PhysX rigidbody collisions.

Ok, here’s my understanding of how to implement the wrapping world. When the player flies from one terrain to another, the terrains (and all their GameObjects) are moved from behind the player, to in front of the player:

…and probably, all the terrains should be moved so that #5 is always centred at Vector.zero – so that the floats representing position don’t grow too large.

This seems like a perfectly functional wrapping world to me.

So, now for my issue.

In my game, a fireball consists of two parts.

The round part is yellow and burning and pretty – when it collides with a dragon, the dragon takes damage.

The big schnoz in front of it doesn’t have a MeshRenderer; it’s invisible. It’s a trigger collider. Call it the FireballNotifier. It’s about 20 metres long. When it collides with a dragon, 2 things happen:

  • The dragon realises there’s a fireball coming, and starts doing evasive maneuvers/counterattacks.
  • The fireball starts homing in toward the dragon.

So basically, it’s just a way for both the fireball and the dragon to detect the case that: there’s a fireball getting really close to its target.

From a collision-detection perspective, the dragon is just a sphere. Maybe a 10 metre diameter.

So, suppose an NPC wizard is fighting a dragon, at the edge of the map. The wizard is on the eastern side of terrain #3; the dragon is on the western side of terrain #1.

If we assume the world is wrapped, then that wizard and dragon are about 50 metres away from one another – really close.

But in Unity engine, they’re really far apart – they’re on opposite sides of the world.

If the wizard shoots a fireball to the east, that fireball will wrap around to the other side of the world. It’s going to suddenly appear next to the dragon, who won’t have time to properly react. This is bad. (And just one example of AI/spell problems that will arise with this wrapping.)

So.

The solution as I understand it, and which seems to be the one both Fattie and Matt Downey are suggesting, is to make “shadow copies” of all the objects near the world boundary. So for example:

The white squares are the “real” world. The grey ones are a copy of the world.

Now, while the FireballNotifier doesn’t collide with the real dragon, it is colliding with the shadow copy of the dragon (which exists in the northeastern copy of terrain #1).

Each of the grey zones contains a copy of all the GameObjects within that terrain, or at least the ones closest to the north & west borders.

The shadow copies of GameObjects are dumb – they are simply objects that have the same collision shape as their source object, with no AI or behaviour other than to set their position 5000m to the east (or south, or southeast) of their source object in every FixedUpdate.

Any time a shadow object collides with a real object, the game simply reacts as though both the real objects collided. But shadow/shadow collisions will all be silently ignored.

These shadow copies will also help with a million other operations, especially in the AI. For example: when a wizard asks, “where is the nearest dragon relative to my current position?” – this will allow an answer to be generated.

Is this the solution being suggested?

It seems… like a lot of work. But probably effective.

When I started this project, I never would have guessed that world wrapping would be such a major issue… I’m still hoping that someone is going to call me an idiot and show me a much, much simpler solution :stuck_out_tongue:

The old question is below, but probably obsolete now

Hi there,

I’m working on a 3D game that features a torus world – if you keep walking in one direction you’ll arrive back at the place you started at.

(Eventually I’ll change it to a spherical world, like Magic Carpet, but torus will be simpler while I’m sorting out issues like this one)

The game will feature multiplayer.

What are some ways to implement the torus 3D world?

Some ideas I’ve considered:

1: Create the world as a 5x5 grid of terrain objects, then whenever the player steps onto one terrain object, pick up all the other terrain objects and place them so that the player is always on the centre of the 5x5 grid.

Problems: This should work pretty well for terrains. But when it’s done for all the monsters in the world too, problems arise – two monsters on adjacent terrains will not necessarily be “near” each other. This causes AI and collision issues.

2: Make Portals at all the world boundaries; so that when the player moves near the northwestern corner of the world, they will be able to see a projection of the southeastern corner of the world – and they won’t realise it’s a projection. When they step into the projection they’ll be transported to the other side of the world.

Problems: Same as #1.

Should I make “copies” of the monsters when they move near terrain boundaries, and have the collision detection/AI routines detect those copies as though they were the real monsters? It seems messy and error-prone, but I’m not sure what other methods exist.

I’m doing the exact same thing in my game.

I’ve used a 3x3 grid and added darkness (fog) of war so the player cannot see copies of herself/himself.

Here’s the old 1. I’ve since resolved a paradox (with different code than what you’ll see later in this answer).

For the most part, it’s just Asteroids (1979). You just subtract the height of the map if the player is too far up, add the map height if they are too far down, and similarly for the width.

Copying objects is a must:

  1. Never render copies of the player (you might want to keep the object for AI).
  2. Render only the nearest enemy (and make that enemy fade in and out whenever rendering a new instance of that enemy).
  3. Always render copies of non-static objects (cars, breakables)
  4. Always render bullet trails and decals.

The collisions should be calculated only on the original map (and not the extraneous ones).

AI is a really hard question, and you will likely have to use calculations based on all of the copies of the AI, not just the original (since there will likely be more than one instance). I’m lucky, I don’t have to deal with AI.

#pragma strict

var trans : Transform;
var delta : float = 0.01;

function Start ()
	{
	var obj : GameObject = GameObject.Find("/Character");
	trans = obj.GetComponent(Transform);
	}

function Update ()
	{
	Warp();
	}

function Warp ()
	{
	if(trans.position.x > 100)
		{
		trans.position.x -= 200;
		Switch();
		}
	else if(trans.position.x < -100)
		{
		trans.position.x += 200;
		Switch();
		}
	if(trans.position.z > 100)
		{
		trans.position.z -= 200;
		Switch();
		}
	else if(trans.position.z < -100)
		{
		trans.position.z += 200;
		Switch();
		}
	}

function Switch ()
	{
    trans.position.y += delta;
	//if(trans.position.y > 0) trans.position.y -= (2000 - delta);
	//else                     trans.position.y += (2000 + delta);
	}

The problem with my answer is it is highly entangled with the rest of the design of my FPS, so ask questions.

[edit:]

Sorry on the extremely late response.

You seem to have hit the nail on the head on the solution. Sure it can be a little difficult to debug things with the 3x3 world, but for the most part any issue can be resolved.

I’ve added nice editor script to my game that tiles stuff procedurally. I actually catered the code to the project you are making since my project involves two worlds.

Simply add the script to your game and notice that under the edit tab you have a new tile button on the very bottom.

Simply select all the objects you want to duplicate and hit edit–>tile and they will be recreated without any redundancies (basically I don’t overlap the original with a new copy that’s exactly the same).

class TileWorld extends Editor
    	{
    	@MenuItem("Edit/Tile")
        static function Tile(command : MenuCommand)
        	{
        	var tileLength : int = 3;
    		var mapHeight : float = 5000;
    		var mapWidth : float = 5000;
    		
        	//Make sure the center tile (1) will have evenly distributed tiles on each side (2n) aka [2n + 1] aka ODD 
        	if(tileLength % 2 == 0 || tileLength < 1)
        	{
        		Debug.LogError("Currently an even/zero/negative number of tiles, try an odd/non-zero/positve number");
        		return; //Important!
        	}
        	
        	//This makes EVERYTHING//
            for(var c : int = 0; c < Selection.transforms.Length; c++) //cycle through all selected gameobjects.
            	{
            	//Debug.Log(c);
                var obj : GameObject;
                obj = Selection.gameObjects
;
                    
                    var normal : Transform; //The transform being manipulated
                	normal = Instantiate(obj.transform);
                    
                    //find the max distance away from the center segment
                    var x : float;
                    var z : float;
                    
                    x = mapWidth*-1.0f*(tileLength - 1)/2; //start in South-West corner of the map (-x,-z as viewed from the sky)
                    z = mapHeight*-1.0f*(tileLength - 1)/2;
                    
                    var tx : float;
                    var tz : float;
                    
                    tx = x;
                    
                    //Start//
                    for(var i : int = 0; i < tileLength; i++)
                    	{
                    	tz = z; 
                    	for(var j : int = 0; j < tileLength; j++)
                    		{
                    		if(i == tileLength - (tileLength - 1)/2 - 1)
               				{
                    			if(j == i)
                    			{
                    				//Debug.Log(i + " " + j);
                    				tz += mapHeight;
                    				continue;
                    			}
                    		}
                    		Instantiate(obj,normal.position + Vector3(tx,0,tz),normal.rotation);
                    		
                    		tz += mapHeight;
                    		}
                    	tx += mapWidth;
                    	}
                    
                    //Clean Up//
                    obj = null;
                    DestroyImmediate(normal.gameObject);
                    //END//
                    }
            	}
            }