I’m trying to make an infinite tile map that spawns tiles around the player and deletes the tiles that aren’t around the player, but the script I wrote is not only extremely laggy, it also doesn’t even fully work yet and for some reason it’s deleting every tile including the ones that are in the tilesToKeep list.
How do I optimize and fix this script?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InfiniteWheatField : MonoBehaviour
{
public GameObject tile;
public Transform player;
public int offset, radius;
private List<GameObject> tiles = new List<GameObject>();
private void Update()
{
Collider[] hitColliders = Physics.OverlapSphere(player.position, radius);
List<GameObject> tilesToKeep = new List<GameObject>();
foreach (var hitCollider in hitColliders)
{
if ( hitCollider.CompareTag("Tile") )
{
tilesToKeep.Add(hitCollider.gameObject);
TrySpawnTile(hitCollider.transform, new Vector3(0, 0, 5));
TrySpawnTile(hitCollider.transform, new Vector3(5, 0, 0));
TrySpawnTile(hitCollider.transform, new Vector3(0, 0, -5));
TrySpawnTile(hitCollider.transform, new Vector3(-5, 0, 0));
}
}
foreach ( GameObject t in tiles )
{
if ( !tilesToKeep.Contains(t) )
{
Destroy(t);
}
}
}
void TrySpawnTile(Transform origin, Vector3 offset)
{
Vector3 spawnPos = origin.position + offset;
if ( !Physics.CheckSphere(spawnPos, 1) )
{
tiles.Add(Instantiate(tile, spawnPos, Quaternion.identity));
}
}
}
Object are passed by reference, the list contain reference, you put tiles references in try to keep, but they remain in the list of tiles too, so if you delete in tile list, the engine go to the reference and delete it, which mean there is no longer something to reference in both list.
If you don’t understand, think of it like a ticket, the object put it’s ticket in the list, when you want to do something with the object, you use the ticket to find the right object. Since the list only contain ticket, copying or passing the ticket don’t affect the object, but if you use a list to kill an object, the object is lost for all ticket and therefore for all list.
What you need to do, is to remove the reference/ticket in the tile list when you put in try to keep. But IMHO it’s better to create a “to destroy” list instead, that would be cleaner and faster.
Aka rename and change if ( !tilesToKeep.Contains(t) )
to if ( tilesToDestroy.Contains(t) )
I think neoshaman has you in the right direction for fixing the functionality, so I’ll focus on reducing lag:
First thing I would do to improve lagging is to use object pooling. Rather than constantly destroying and creating tiles, it would turn them on or off and only create when needed. Instantiation causes lag, so hiding and showing is better. Here is my own object pooling package (designed to be an easy drop-in replacement).
Second thing is, physics is super wasteful for this. Since each tile is going to be at a known coordinate and since you have a complete list of tiles, you can just check the list of tiles for their coordinates directly and bypass physics.
For TrySpawnTile, use Mathf.RoundToInt to avoid floating point comparison issues, but after that you can just check that the new position doesn’t equal the position of any existing tile. This will be much faster than querying the physics system. (You could further optimize this with a hash-based system like a Dictionary, but by this point in optimization you’ll definitely have another performance bottleneck to focus on)
For the main Update loop, you can loop through your tiles and just check their position, either with a distance check or by comparing the X and Z positions separately (which will leave you with a rectangular area of tiles, which may be better if the goal is to keep your screen filled with tiles).
1 Like