View 2d array of 'Tile' class in inspector? [help requested]
Hi there,
I'm trying to create a tilemap/an array of 'Tiles' (a custom class). At the moment, I create an instance of my 'Tile' class for every position that a unit could move (in this example I have a 5x5 board; 25 tiles in total.
I want to do this so that I can store variables such as a string 'tileName', int 'tileID', or gameObject 'unitOnTile' – so that I can track information about which gameobject is sat on what tile at any one point. I imagine this will let me detect collisions efficiently, amongst other things.
I have a script 'TileMaster' which is attached to the camera. My custom 'Tile' class is kept within TileMaster.cs. However, when inspecting the camera during runtime I can't see anything related to my custom class 'Tile' – making it impossible to debug or check if things are working properly.
Ideally I would be able to see each instance of my 'Tile' class along with their individual variables (tileName, tileInt, tilePosition, etc) in the Unity inspector so that I could check things are working during runtime.
Unfortunately, when inspecting the camera all I can see is my TileMaster script (and a couple of test variables I set up within the TileMaster class in order to check they would display in the inspector. I can't see anything relating to my custom 'tile' class.
Screenshot of my inspector and the TileMaster script here: http://imgur.com/a/ErCFG
Also my TileMaster script here:
[System.Serializable]
public class TileMaster : MonoBehaviour
{
public Tile [,] map;
private static int worldX = 5, worldY=5;
public int TestInt;
public string TestString;
void Awake()
{
Debug.Log("TileMaster Awake.");
//CREATE TILES
map = new Tile[worldX, worldY];
for (int x = 0; x < worldX; x++)
{
for (int y = 0; y < worldY; y++)
{
map[x,y] = new Tile("aTile", 0, new Vector2(x,y));
}
}
//ASSIGN STUFF
foreach(Tile currentTile in map)
{
if ((currentTile.tilePosition.x == worldX - 1 || currentTile.tilePosition.x == 0) ||
(currentTile.tilePosition.y == worldY - 1 || currentTile.tilePosition.y == 0))
{
currentTile.tileName = "Wall";
currentTile.tileID = 1;
}
else
{
currentTile.tileName = "Floor";
currentTile.tileID = 0;
}
Debug.Log(currentTile.tileName);
Debug.Log(currentTile.tileID);
Debug.Log(currentTile.tilePosition);
}
}
}
[System.Serializable]
public class Tile
{
public GameObject containedObject;
public string tileName;
public int tileID;
public Vector2 tilePosition;
public Tile(string aName, int anID, Vector2 aPosition)
{
tileName = aName;
tileID = anID;
tilePosition = aPosition;
Debug.Log("Hello, I'm a new tile.");
}
}
It's probably worth noting that the [System.Serializable] stuff was part of an attempt to make it viewable in the inspector (obviously it didn't work). Also I should note that whilst in this example my class 'Tile' is located in the TileMaster.cs script, it also works fine as its own separate script Tile.cs. Either way though, I can't see to view any instance of Tile in the inspector.
I'm aware that 2d arrays aren't viewable in the inspector (for example even if I had a 2d array of integers contained within TileMaster.cs I wouldn't be able to see it in the inspector – whereas if it was simply a one-dimensional array or a list I would be able to see it. It's just the fact that my Tile map is both a 2d array AND an array of custom classes that i'm completely lost...
If anyone can point me in the right direction I'd really really appreciate it.
Answer by AMU4u · Mar 02, 2017 at 05:05 PM
Put Tile Class outside of the tilemaster class brackets.
edit - Because think about it, you can't serialize a script ITSELF to be shown in the inspector like a class, and as you have it right now, Tile is a subclass of Tilemaster. That is why it works when you make it a seperate class.
In this and every instance of tiles I've ever implemented, you don't need it inside of your controller class.
edit 2- just for the sake of full summation, to get your script to work this way, (which I wouldn't do) you'd just have to attach it to a controller script onto your Camera and call it like regular. You'll have fully-viewable variables if you keep the scripts exactly as they are (I believe, I haven't syntax checked anything).
Hi Letitslide,
Thanks for the reply! I'm leaving work now but will text this out in a few hours and let you know how I get on...
In the meantime –
– If you wouldn't structure your scripts this way; in what was would you do it? (Do you mean you would simply have your tile class in a Tile.cs script rather than in Tile$$anonymous$$aster.cs)?
– This is the first time i've tried something like this and i'm kind of stumbling around in the dark trying to make it work. I'm struggling a little with the idea that Tile$$anonymous$$aster creates an array of Tiles and yet each resulting instance of Tile isn't actually attached to any gameObject. I've never done anything like this before so the lack of being able to check anything in inspector a bit daunting.
– If by any chance (long shot) you have an example of code when you've implemented tiles that I could use as a reference? If not no worries at all.
Thanks again.
The reason you are struggling is because that system is one that is heavily nuanced and argued upon in the coding community. Taking your memory objects and separating them from your visual layer can be a pretty daunting task, especially for beginners.
Yes, I put my tile class outside of my $$anonymous$$apController class, and make any controllers singletons via
public someclass : $$anonymous$$ono
{
public static someclass instance = null;
void OnEnable()
{
if(instance == null)
{
instance = this;
}
else if
(instance != this)
{
destroy(gameObject);
}
}
you can then access their now global variables. In your mapController, create a Dictionary array with tkey being the Tile in memory, and tvalue being the gameObject you want to associate it with. When you loop through your double for loops, add the game object to the tile after you create it in memory to the Dictionary map you create in the start function of your Controller script.
void Start()
{
DictionaryYouCreated = new Dictionary<Sprite, GameObject>();
}
for (int x = 0; x < current$$anonymous$$ap.Width; x++)
{
for (int y = 0; y < current$$anonymous$$ap.Height; y++)
{
GameObject tile_game_object = new GameObject();
Tile tile_in_memory = new Tile();
DictionaryYouCreated.Add(Tile_In_$$anonymous$$emory, tile_game_object);
}
If you don't create a new dictionary, you will get an error.
From here, all you have to do to grab the gameobject from the tile in memory is go
gameobject ReturnTileGameObject(Tile tile_In_$$anonymous$$emory)
{
GameObject tile_game_object = DictionaryYouCreated[tile_In_$$anonymous$$emory];
return tile_game_object;
}
Is that concise enough?
For some reason It wont let me post my reply here in depth so i've had to respond to my own question below (which was a couple of characters short of the limit due to the code :/ )
Answer by eddieion · Mar 03, 2017 at 12:14 AM
@letitslide Thanks for that! I think i've managed to implement it correctly...
This is what I have at the moment (TileMaster.cs)
public class TileMaster : MonoBehaviour
{
public Dictionary<Tile, GameObject> tileDictionary;
private static int worldX = 5, worldY=5;
public static TileMaster instance = null;
void OnEnable()
{
if(instance == null)
{
instance = this;
}
else if
(instance != this)
{
Destroy(gameObject);
}
}
void Awake()
{
tileDictionary = new Dictionary<Tile, GameObject>();
for (int x = 0; x < worldX; x++)
{
for (int y = 0; y < worldY; y++)
{
Tile tile_In_Memory = new Tile("aTile", 0, new Vector2(x,y));
GameObject tile_Game_Object = new GameObject("tileObject");
tile_Game_Object.transform.position = tile_In_Memory.tilePosition;
tileDictionary.Add(tile_In_Memory, tile_Game_Object);
Debug.Log(tileDictionary.Count);
Debug.Log(tile_In_Memory.tileName);
Debug.Log(tile_In_Memory.tilePosition);
}
}
}
GameObject ReturnTileGameObject(Tile tile_In_Memory)
{
GameObject tile_Game_Object = tileDictionary[tile_In_Memory];
return tile_Game_Object;
}
}
And Tile.cs
public class Tile
{
public GameObject containedObject;
public string tileName;
public int tileID;
public Vector2 tilePosition;
public Tile(string aName, int anID, Vector2 aPosition)
{
tileName = aName;
tileID = anID;
tilePosition = aPosition;
Debug.Log("Hello, I'm a new tile.");
}
}
Nothing is throwing up any errors. As it should (i think?): 25 instances of the Tile class (tile_In_Memory) and 25 gameObjects (tile_Game_Object) are created by the loops, which are then added (as a pair?) to the dictionary (tileDictionary). This is all great.
I also have my player (PlayerController.cs)
public class PlayerController : MonoBehaviour {
public Vector2 position;
public float xPos;
public float yPos;
void Update()
{
position = transform.position;
xPos = transform.position.x;
yPos = transform.position.y;
if (Input.GetKeyDown(KeyCode.W))
{
Vector3 position = this.transform.position;
position.y++;
transform.position = position;
}
if (Input.GetKeyDown(KeyCode.D))
{
Vector3 position = this.transform.position;
position.x++;
transform.position = position;
}
if (Input.GetKeyDown(KeyCode.S))
{
Vector3 position = this.transform.position;
position.y--;
transform.position = position;
}
if (Input.GetKeyDown(KeyCode.A))
{
Vector3 position = this.transform.position;
position.x--;
transform.position = position;
}
}
}
I just can't seem to get my head around how I can access the tiles north, south, etc adjacent to the player to check whether the player can move into that space. Or for that matter – how I could keep track of the players position (and the position of other units) using the tiles and dictionary that i've just set up so that players and other units will also block each other's movement.
In the past I've done this by using a list of all units in the scene. When the player (or another object) wanted to move, it was check every object's (in the Unit list) position against the position of the tile I wanted to move into – but this slowed the game down far too much once I had many objects checking to move (and hence also a longer Unit list). Obviously there should be a simpler way of doing this; which is why I imagined I'd be able to keep a reference of whatever object was in a specific Tile at any one point, so I could check directly:
if (tile north of the player = full/occupied)
{
don't move;
}
else if (tile north of the player = empty)
{
move into it;
}
Hopefully all of that makes sense. Not sure if I just fundamentally misunderstand what i'm trying to do or if i'm completely over thinking it.
In that case, what I PERSONALLY would do is create a map class where you can store the array of tiles positions you made, which I mean really is the end all be all of the whole system.
public class $$anonymous$$ap()
{
Tile[,] mapTiles;
public $$anonymous$$ap(int width, int height)
{
this.width = width;
this.height = height;
mapTiles = new Tile[width, height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
mapTiles[x, y] = new Tile(this, x, y);
}
}
public Tile GetTileAt(int x, int y)
{
return mapTiles[x, y];
}
}
Then, when you create a new map in some other class, you can ask that new map in memory exactly where a tile is.
Tile GetTileAt$$anonymous$$apCoordinate(Vector3 coord)
{
int x = $$anonymous$$athf.FloorToInt(coord.x);
int y = $$anonymous$$athf.FloorToInt(coord.y);
return $$anonymous$$apController.instance.current$$anonymous$$ap.GetTileAt(x, y);
}
that personally I think this system works best if you keep your x and y values positive. I've never tried it any other way, but why start from the center of a grid? Just a massive headache.
So as an example for your last question, we know;
x, y = us
x+1,y = one tile Right of us
x-1,y = one tile Left of us
x,y+1= one tile Above us
x,y-1= one tile Below us
Your answer
Follow this Question
Related Questions
Where to start for a tile grid for an android game? 2 Answers
How do you update a property's int value based on two (or more) other properties in the Inspector? 0 Answers
unity ui darker than actual assets 0 Answers
Preprocessor conditional code for cleaner inspector options 1 Answer
Creating game objects from inspector 0 Answers