- Home /
How do I create a grid-based map in the most efficient way?
I'm making a 2D farming (open-world type) game and i need a huge map for it...
Until now I was using the regular In-built Tilemap system in Unity. But, I needed to make it a grid-based map, i.e. I want to be able to select a tile of the grid to plant and break stuff (I think i know how to do this, using Raycasts)
Here is where the problem arises, To make the grid i started instantiating 'Tile' objects like :
for (int x = 0; x < GridArray.GetLength(0); x++)
for (int y = 0; y < GridArray.GetLength(1); y++)
{
GameObject Tile = Instantiate(TileObject, this.transform);
SpriteRenderer TileSP = Tile.GetComponent<SpriteRenderer>();
float Rand = Random.value;
if (Rand > 0.97f)
{
Name = "Flower";
}
else if (Rand > 0.8f)
{
Name = "Grass";
}
else
{
Name = "Plain";
}
switch(Name)
{
case "Grass" :
TileSP.sprite = TileObjectSprites[Random.Range(5, 6)];
Tile.tag = "Grass";
break;
case "Flower" :
TileSP.sprite = TileObjectSprites[Random.Range(1, 4)];
Tile.tag = "Flower";
break;
case "Plain" :
TileSP.sprite = TileObjectSprites[0];
Tile.tag = "Plain";
break;
default :
TileSP.sprite = TileObjectSprites[0];
Tile.tag = "Grass";
break;
}
Tile.transform.position = new Vector3(x, y, 0f);
Tile.name = "Tile" + NameNumber;
NameNumber++;
}
This code alone takes around 1 minute just to process and load into the game, But when i try to instantiate trees and other objects along with the tiles it can anywhere between 5-15 minutes just to process
So, i wanted to know if there was a more efficient and less time consuming method of doing this...
Thanks in advance
EDIT : I checked out some videos on YouTube, but none of them really fit my problem
Open up a Profiler
window and use it. It will tell you what is taking the most of that time.
My bet is on Instantiate
. There are creative ways to work around that.
Oh ok.. I shall try out a profiler in the morning... if you you don't mind me asking what other way of instantiating are available?
1000/1000 grid, and it's Instantiate
fault:
using UnityEngine;
public class LetsTestItOut : MonoBehaviour
{
[SerializeField][Min(1)] Vector2Int _numCells = new Vector2Int( 100 , 100 );
[SerializeField] Sprite[] _spriteFlowers = new Sprite[1];
[SerializeField] Sprite[] _spriteGrass = new Sprite[1];
[SerializeField] Sprite[] _spritePlain = new Sprite[1];
[SerializeField] GameObject _tilePrefab = null;
void Start ()
{
for( int x=0 ; x<_numCells.x ; x++ )
for( int y=0 ; y<_numCells.y ; y++ )
{
GameObject tile = Instantiate( _tilePrefab , transform );
tile.transform.position = new Vector3(x,y,0);
var sprite = tile.GetComponent<SpriteRenderer>();
float rando = Random.value;
if( rando>0.97f )
{
// tile.tag = "Flower";
sprite.sprite = _spriteFlowers[ Random.Range(0,_spriteFlowers.Length) ];
}
else if (rando > 0.8f)
{
sprite.sprite = _spriteGrass[ Random.Range(0,_spriteGrass.Length) ];
// tile.tag = "Grass";
}
else
{
sprite.sprite = _spritePlain[ Random.Range(0,_spritePlain.Length) ];
// tile.tag = "Plain";
}
}
}
}
Answer by bacon_nugget · Apr 01 at 10:22 AM
If you weren't aware, the tile assets that you use to paint a tilemap are ScriptableObjects
(link). I believe the tilemap uses these to achieve something similar to the 'level 3' suggestion from @andrew-lukasik.
You can extend the Tile
(link) or TileBase
classes to add custom behavior to your tiles. You wouldn't need to instantiate all of those objects because the unity engine already handles initialization for the tilemap. One important thing to know is that copies of the same tile on the map are not individual instances of a scriptable object. You would still need to make a list of objects that contain the data specific to each tile on the map. When you plant or break things on the farm, you can just grab a reference to the custom tile at that position and give it the data container tied to that location, maybe as a parameter to a method call. Methods in the custom tile can modify the data or use the data according to whatever your goals may be.
Obviously, your gameplay is going to require frequent visual changes. You can override GetTileData in order to define how a tile decides which sprite to use. An implementation of this method could fetch the data object for its location and choose a sprite with that information. If a tile needs to change behavior, you can switch it with a new type of custom tile.
Here is a code snippet I use in all of my ScriptableObject implementations. Line 9 adds a menu item under Assets > Create according to the path that you define (As shown in the example, it would be Assets > Create > Custom Tile). An instance of your scriptable object is created and saved into a ".asset" file. These files can be dragged and dropped into serialized fields in the inspector so that they can be referenced in your code. If you want to paint custom tiles onto your tilemap, just drag and drop the file into your tile palette and use the tile brushes as you normally would.
public class YourCustomTile : Tile
{
public void YourTileBehavior(YourTileDataClass data)
{
//do farming stuff
}
#if UNITY_EDITOR
[UnityEditor.MenuItem("Assets/Create/Custom Tile")]
public static void Create()
{
var name = "Default File Name";
var path = UnityEditor.EditorUtility.SaveFilePanelInProject(
$"Save {name}", name, "asset", $"Save {name}", "Assets/YourFilePath");
if (path == "") return;
UnityEditor.AssetDatabase.CreateAsset(CreateInstance<YourCustomTile>(), path);
}
#endif
}
Thats really helpful... Thanks a lot! Although I wanna try instantiating a smaller grid with perlin noise just as a learning experience... But, thanks a lot again this will probably be helpful for me in the future.
Of course! I'm working on my own 2D game at the moment and discovered this when I started working on creating the environment. I haven't invested a whole lot of effort in learning about DOTS just yet, but I get the impression that it might be overkill unless you're still having performance issues after attempting other solutions.
You can definitely still create your own custom tiles to use with your perlin noise experiment. The way tiles are represented and the way the map is generated are two independent things. Best of luck with it!
Thanks a lot mate! Good luck on your game too :)
+1 I think this a better answer. I'm not a 2D guy, so completely forgot that that Unity introduced this tile system that solves this issue for us (I hope). No idea how it performs through. We need to trust @bacon_nugget on that here.
@andrew-lukasik Thanks! Your answer gave a list of several good solutions, which is incredibly valuable for any beginner developers who might come across this question in the future. I only went into more detail on my favorite option :) This format really should be used more often on technical Q&A forums.
My game is pretty lightweight, but I'm reliably getting 250+ fps while using a couple thousand interactable tiles. It's obviously wise to always test things out and tailor the solution to your own project.
I just finished my Tilemap with Perlin and it looks really cool...
Although the game does run >100fps i still want to try your method as it seems better, but i'm not exactly sure how do edit tiles just yet. @bacon_nugget you know any good tutorials on how to get started?
Answer by andrew-lukasik · Mar 31 at 06:44 PM
Let me list few possible answers to your question in order of difficulty to implement them:
Level 1 difficulty:
Don't instantiate objects you can't see :T
Level 2 difficulty:
You can pre-instantiate all those objects ahead of time (ObjectPool or something like that)
Level 3 difficulty:
Split your game code into two separate layers:
DATA (actual game world state)
DATA VISUALIZATION (things you see on the screen)
This will allow you to run game world simulation cheaply on some kind of basic WorldCellData[]
that contains all the world grid cells with their data. And then, again, only spawn objects to visualize state of cells that be seen by the player and despawn things he can't.
Level 4 difficulty:
Go DOTS. 100% or just partially, i.e. player & enemies can stay being GameObject
s where the world is mostly entities.
I just went through all the topics and I think I have to use either DOTS or just make them inactive of they are far away from the player...
I don't object pooling will work as they get instantiated in the beginning and don't really get destroyed after that.
Your answer
Follow this Question
Related Questions
Texture grid displayed oddly when width =/= height 1 Answer
!URGENT! How to make a grid of clickable objects. 1 Answer
Isometric Tile Grid with 2D Sprite Assets 3 Answers
Have huge grid data in a way so a 2d range can be pulled effectively? 1 Answer
which one is more efficient on Android Tiles or a Terrain. 0 Answers