Creating GameObjects programmatically from the Unity3d Inspector
I always have been curious about game development, but I never had the time to get into it. A few weeks ago I started learning unity and the related stuff. Now I decided to do a small board game (mostly for learning purposes).
I decided to use unity because it gives you a lot of things done. From a designer's point of view, I really love the Scene view, but I am mostly a programmer, so I prefer to write scripts for getting things done. In Unity as far as I know, the idea of the script system is, mostly, to be executed during the game execution, so if you want to keep your game objects generic and you like to parametrize things, you will lose the Scene view, so you’re doing your life, as a designer, worse. But not all is lost, actually you can keep mixing your programming and designing skills.
Let’s say that we’re going to do the board of our board game. When I thought about it, I realised that I could do it using prefabs for the squares and putting them together from a script attached to a GameObject called Board. The problem with this approach is that it isn’t flexible to change. E.g. supposing that we have a board of 5x5 and we want a board of 4x6, we will have to change it manually. Besides, we can’t use it on future games or give our users the possibility to change the board size. To solve this, we can do the board via a script, something like this:
public class BoardBuilder : MonoBehaviour {
public GameObject square;
GameObject[,] squares;
public int NumberOfColumns;
public int NumberOfRows;
public Vector2 InitPosition;
void Start () {
squares = new GameObject[NumberOfColumns,NumberOfRows];
for (var c = 0; c < NumberOfColumns; c++) {
for (var r = 0; r < NumberOfRows; r++) {
var pos = new Vector3 ( (c+1) * square.transform.localScale.x + InitPosition.x ,
(r+1) * square.transform.localScale.y + InitPosition.y , .0f);
var sq = Instantiate (square, pos, Quaternion.identity) as GameObject;
squares[c,r] = sq;
}
}
}
Notice that we expose the properties of our components by making the vars visible (public) to unity`s editor. I won’t go further into this, but the square instance is just the prefab that we will drag from the editor.
But this time we lose the benefits of the Scene view because our code will run when the game starts.
Luckily, there is a way to execute the scripts when the game is not running, by calling the method OnValidate (which is designed to validate our objects, not for this purpose. BTW I think that it’s better to write tests rather than do nasty stuff in this method, although I don’t know how it is used in practice). So we just need to change the signature of the method Start to OnValidate and voila! We are able to modify our board in design time!
But not so fast, the objects keep recreating every time we change a value. So we have to delete the old squares. Here is the final code:
public class BoardBuilder : MonoBehaviour {
public GameObject square;
GameObject[,] squares;
public int NumberOfColumns;
public int NumberOfRows;
public Vector2 InitPosition;
void DestroyFromEditor(GameObject obj){
EditorApplication.delayCall += () => DestroyImmediate (obj);
}
void OnValidate () {
if (squares!=null)
squares.GetEnumerator().ToList().ForEach(DestroyFromEditor);
squares = new GameObject[NumberOfColumns,NumberOfRows];
for (var c = 0; c < NumberOfColumns; c++) {
for (var r = 0; r < NumberOfRows; r++) {
var pos = new Vector3 ( (c+1) * square.transform.localScale.x + InitPosition.x ,
(r+1) * square.transform.localScale.y + InitPosition.y , .0f);
var sq = Instantiate (square, pos, Quaternion.identity) as GameObject;
squares[c,r] = sq;
}
}
}
It's worth mentioning that in the method DestroyFromEditor, we have to subscribe to delayCall and we have to do a wrapper here to avoid destroying only the last object.