I am trying to generate a simple map like this. Everything works as I want, but I am new to programing. Is it a good way to do something like this? And if not, could someone explain to me where and why it is bad to do something like this? And how bad is it on performance?
public GameObject cube;
public GameObject cube1;
public GameObject cube2;
Vector3 here;
void Start () {
for (int y = 0; y < 50; y++) {
for (int x = 0; x < 10; x++) {
here = new Vector3(x,y,0);
int change = (int)Random.Range(0,19);
switch (change){
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10:
Instantiate(cube,here,Quaternion.identity);
break;
case 11: case 12: case 13: case 14: case 15: case 16:
Instantiate(cube1,here,Quaternion.identity);
break;
case 17: case 18:
Instantiate(cube2,here,Quaternion.identity);
break;
}
}
}
Running this code generates something random like this:
Cropped screenshot of a rectangle containing red, green, and blue blocks
Instantiate
takes 3 things (GameObject
you want to make, coordinates where to make it, and rotation), and it makes a copy of GameObject
- prefab in those coordinates.
Cube
- RedCube1
- GreenCube2
- Blue
The reason for all of this is that I want to learn to make something random likea map area of the game.
3 Answers 3
As a first step, I think this is cleaner than the switch
if (change <= 10)
{
Instantiate(cube, here, Quaternion.identity);
}
else if (change <= 16)
{
Instantiate(cube1, here, Quaternion.identity);
}
else
{
Instantiate(cube2, here, Quaternion.identity);
}
But there's still repetition with the calls to Instantiate
. I'd suggest creating a new method like this
private GameObject GetRandomCube()
{
var change = (int)Random.Range(0, 19);
if (change <= 10)
{
return cube;
}
if (change <= 16)
{
return cube1;
}
return cube2;
}
And then use it like this in your loop
Instantiate(GetRandomCube(), here, Quaternion.identity);
If here
isn't being used elsewhere in the class, move its declaration inside the inner loop, or just use it in the function call. Now your method looks like this
void Start()
{
for (var y = 0; y < 50; y++)
{
for (var x = 0; x < 10; x++)
{
Instantiate(GetRandomCube(), new Vector3(x, y, 0), Quaternion.identity);
}
}
}
-
\$\begingroup\$ Isn't this just as in-efficient a the original code? Calling instantiate in a loop like this for a map is most definitely a bad idea ... Why do people keep up voting this? The performance of this must be horrific if you try to scale it. \$\endgroup\$War– War2015年04月18日 20:48:19 +00:00Commented Apr 18, 2015 at 20:48
Is it really necessary to create an object of each tile?
In many 2d map-based games, the tiles are not objects, but each tile is only an int
in a 2d array.
I don't know Unity, but I feel that it should be possible to render a 2d map in a better way than creating a GameObject
for each tile.
If possible, I would make your code something more like this:
int[][] map = new int[50][10];
for (int y = 0; y < map.Length(1); y++) {
for (int x = 0; x < map.Length(2); x++) {
int change = (int)Random.Range(0,19);
int type = 0;
if (change <= 10)
{
type = 1;
}
else if (change <= 16)
{
type = 2;
}
else
{
type = 3;
}
map[y][x] = type;
}
}
-
\$\begingroup\$ thanks trying this method as well. My question would be, how better for performance is using If instead of switch? \$\endgroup\$user2351722– user23517222015年03月19日 13:48:52 +00:00Commented Mar 19, 2015 at 13:48
-
1\$\begingroup\$ Logically when the compiler gets hold of this it'll basically turn out the same when compiled .. this offers literally no performance improvement whatsoever. all it does is compact the switch conditions in to 3 if / else conditions, ok I suppose if you only ever want 3 tile types in your map ... I guessed that this would likely not be the case in my answer. \$\endgroup\$War– War2015年04月18日 20:53:23 +00:00Commented Apr 18, 2015 at 20:53
Warning: this code sample may not work, its all from my head and is only meant to give a general idea of how to generate a basic 2d tile map.
You will want to take this further in to the 3d side for cubes but the process is the same just with the extra dimension.
God no please don't keep this up ...
It looks like you're creating a tile based map using cubes but lets start with just flat tiles so you can get your head round this. So lets see how we can do this much faster ...
Firstly start by generating a map as data, this should be as simple as generating a int[x,y] where the values are between 0 and some max.
You can do it the way you are doing above or use something like perlin noise (appears to be the most commonly used method these days) ...
int tileTypes = 20;
float tileStep = 1f / (float)tileTypes;
var map = new int[10,50];
for (int y = 0; y < 50; y++) {
for (int x = 0; x < 10; x++) {
map[x,y] = Random.Range(0, tileTypes - 1);
}
}
Once you have that data you can generate a mesh with 1 quad per tile ...
// generate the vertex info for your map mesh
var verts = new List<Vector3>();
// also generate uv's
var uvs = new List<Vector2>();
for (int y = 0; y < 50; y++) {
for (int x = 0; x < 10; x++) {
verts.AddRange(new [] {
new Vector3 { x,y,0 },
new Vector3 { x+1,y,0 },
new Vector3 { x,y+1,0 },
new Vector3 { x+1,y+1,0 }
});
// depends on atlas layout but lets assume a single line of textures
// but essentially this is where your map comes in ...
var type = map[x,y];
uvs.AddRange(new [] {
new Vector2 { tileStep * type, 0 },
new Vector2 { tileStep * (type+1), tileStep * type },
new Vector2 { tileStep * type, 1 },
new Vector2 { tileStep * (type+1), 1 }
});
}
}
// index the verts
// numbers of verts / verts per quad * indexes needed per quad
var indexes = new int[(verts.Count / 4) * 6];
var index = 0;
// setup index info for each face
for (int vert = 0; vert < verts.Count; vert += 4)
{
// tri 1
indexes[index++] = vert;
indexes[index++] = vert + 1;
indexes[index++] = vert + 2;
// tri 2
indexes[index++] = vert;
indexes[index++] = vert + 2;
indexes[index++] = vert + 3;
}
// create the mesh
var mesh = new Mesh {
vertices = verts.ToArray(),
uv = uv.ToArray()
};
mesh.SetTriangles(indexes.ToArray(), 0);
/// and finally ... draw!
GetComponent<Renderer> ().material.mainTexture = someTileAtlas;
GetComponent<MeshFilter>().sharedMesh = mesh;
-
\$\begingroup\$ i will be reading your code and learning what it does for next week :DD \$\endgroup\$user2351722– user23517222015年03月18日 23:36:53 +00:00Commented Mar 18, 2015 at 23:36
-
\$\begingroup\$ yeh sorry about the bad ass brain dump ... essentially it generates a map which it puts in an array, then generates a "tile" (quad made up of 2 triangles) for each map entry. You will need to get your head round texture atlases but the idea is that the tiles will pick a different texture from your atlas based on the map data so if map[x,y] = 0 you get the first texture in the atlas rendered on that tile. it then repeats for all other tiles. There may be some logical errors in there its a raw brain dump. \$\endgroup\$War– War2015年03月18日 23:42:08 +00:00Commented Mar 18, 2015 at 23:42
Instantiate
method do? What is the end result of this? Some more context about what kind of game we're dealing with here would make it a lot easier for us to come up with better answers. \$\endgroup\$GameObject
code and the code for theInstantiate
method as well? (don't worry about them being too long) \$\endgroup\$