I am creating a simple 2D tile game. To approach the task of creating lots of tiles at runtime I have decided to create a mesh and apply a texture created procedurally over top. To accomplish this I have a reference tileMap which contains every sort of tile. I then assign the UVs to the map. This method works fairly well but a gap appears between certain tiles. Any idea why?
Below I inserted 2 functions, buildTexture is creating the texture itself and chopTileMap returns a 2D array with Vector2s which are unique to each tile. This array can then be used such that the array of Vector2s and index 0 will return the 4 coordinates of the bottom left tile in my tile map.
void buildTexture()
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector2[][] tileMap = chopTileMap();
List<Vector2> uvList = new List<Vector2>();
mesh.GetUVs(0, uvList);
Vector2[] uvs = uvList.ToArray();
//To set a UV it has to be given the fraction regarding to the tile map out of 1 (for a tile map 3 wide the vectors would be 0,1/2,2/3,1)
//tileMap is set as tileMap[tileID(index)][Point on tile (0-3)]
//for each UV, assign the set of 4 to points on the tile map
for (int r = 0; r < rowCount; r++)
{
for (int c = 0; c < colCount; c++)
{
//int tileIndex = Random.Range(0, tileMap.Length-1);
int tileIndex = Random.Range(0,4);
uvs[((r * colCount + c) * 4) + 0] = tileMap[tileIndex][0];
uvs[((r * colCount + c) * 4) + 1] = tileMap[tileIndex][1];
uvs[((r * colCount + c) * 4) + 2] = tileMap[tileIndex][2];
uvs[((r * colCount + c) * 4) + 3] = tileMap[tileIndex][3];
}
}
textureMap.Apply();
mesh.uv = uvs;
MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
meshRenderer.materials[0].mainTexture = textureMap;
}
Vector2[][] chopTileMap()
{
int numColsOfMap = textureMap.width / tileResolution;
int numRowsOfMap = textureMap.height / tileResolution;
Vector2[][] tiles = new Vector2[numColsOfMap * numRowsOfMap][];
for (int r = 0; r < numRowsOfMap; r++){
for (int c = 0; c < numColsOfMap; c++){
tiles[r * numColsOfMap + c] = new Vector2[4];
tiles[r * numColsOfMap + c][0] = new Vector2((float)(c) / numColsOfMap, (float)(r) / numRowsOfMap);
tiles[r * numColsOfMap + c][1] = new Vector2((float)(c+1) / numColsOfMap, (float)(r) / numRowsOfMap);
tiles[r * numColsOfMap + c][2] = new Vector2((float)(c) / numColsOfMap, (float)(r+1) / numRowsOfMap);
tiles[r * numColsOfMap + c][3] = new Vector2((float)(c+1) / numColsOfMap, (float)(r+1) / numRowsOfMap);
}
}
return tiles;
}
In this picture thin blue lines can be seen between the tiles which is the background coming through.
The amount off by is less than a pixel as noted when trying to adjust the UV to compensate for this and as a result is hard to see but is defintly noticeable at runtime. The most predominant line in the image below is the thin blue one running vertically next to the player on the left.
Related
I am trying to generate a grid across my map and add nodes depending on the perlin noise value. Depending on the value obtained from the perlin noise at a location, I will add a new Node which will be of a certain type e.g. Mountain, Water etc to represent terrian. Here I am trying to make it so that if the value is > 0.5, this mean it's only mountains and so a black coloured cubes should surround the mountain areas, However, my black cubes do not match the mountain areas from the perlin noise and I cannot seem to figure out why I am going wrong. Would appreciate any insight into how I could go about achieving this.
private void LocateWalkableCells()
{
for(int z = 0; z < Height; z++)
{
for(int x = 0; x < Width; x++)
{
noise = GetNoiseValue(x, z);
if(noise > 0.5) {
grid[x,z] = new Node(new Vector3(x, 0, z), TerrainType.Mountain, 1);
}
else {
grid[x,z] = new Node(new Vector3(x, 0, z), TerrainType.Grass, 1);
}
}
}
}
private float GetNoiseValue(int x, int z)
{
int pos = (x * Width) + z;
return Mathf.Round(terrainGenerator.noiseArray[pos] * 10) / 10;
}
// Draw gizmos to visualize colour
void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(Width, 1, Height));
if(grid != null)
{
foreach(Node n in grid)
{
if(n.TerrainType == TerrainType.Grass)
{
Gizmos.color = Color.green;
}
else if(n.TerrainType == TerrainType.Mountain)
{
Gizmos.color = Color.black;
}
Gizmos.DrawCube(n.Position, Vector3.one * (nodeDiameter - .1f));
}
}
}
noiseArray is used for the vertices of the terrain in the following code:
vertices = new Vector3[(Width + 1) * (Depth + 1)];
noiseArray = PerlinNoise();
int i = 0;
for(int z = 0; z <= Depth; z++)
{
for(int x = 0; x <= Width; x++)
{
var currentHeight = noiseArray[i];
if(currentHeight > HeightThreshold)
{
currentHeight *= HeightMultiplier;
}
vertices[i] = new Vector3(x, currentHeight, z);
i++;
}
}
Output
Result from suggested answer
Still seems to miss some mountain areas, colouring green instead of black.
It think the issue is in
var pos = (x * Width) + z;
since x is you index on the width of the grid you would probably rather want
var pos = z * Width + x;
in other words you want to
skip z rows
each row has Width elements
then from there take the xth element
assuming your terrain is laid out row-wise.
Or if it is laid out column-wise (which is rather unusual but possible)
var pos = x * Height + z;
or in other words
skip x columns
each column has Height elements
then from there take the zth element
See also Converting index of one dimensional array into two dimensional array i. e. row and column
Update
Now that you have showed the terrain generation code it needs to be
var pos = z * (Width + 1) + x;
since the terrain array has actually Width + 1 elements per row.
I'm trying to make a chess game in Augmented Reality. I wrote a script which places chessboard on the Plane in AR. Then I created mesh with 64 squares which match chessboard tiles. I have a problem placing mesh to match my chessboard(screenshots). I think I should rotate mesh by Y axis, but I wasn't able to do that.
placing chessboard:
GameObject placedObject = Instantiate(objectToPlace, placementPose.position, placementPose.rotation * Quaternion.Euler(-90f, 0f, 0f));
script that creates and places mesh:
private float yAdjust = 0F;
private Vector3 boardCenter = GameObject.Find("Interaction").GetComponent<TapToPlaceObject>().placementPose.position;
private void GenerateSquares(float squareSize)
{
adjust = new Vector3(-4 * squareSize, 0, -4 * squareSize) + boardCenter;
squares = new GameObject[8,8];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
squares[i, j] = CreateSquare(squareSize,i,j);
}
}
}
private GameObject CreateSquare(float squareSize, int i, int j)
{
GameObject square = new GameObject(string.Format("{0},{1}", i, j));
square.transform.parent = transform;
Vector3[] vertices = new Vector3[4];
vertices[0] = new Vector3(i * squareSize, yAdjust, j * squareSize) + adjust;
vertices[1] = new Vector3(i * squareSize, yAdjust, (j + 1) * squareSize) + adjust;
vertices[2] = new Vector3((i + 1) * squareSize, yAdjust, j * squareSize) + adjust;
vertices[3] = new Vector3((i + 1) * squareSize, yAdjust, (j + 1) * squareSize) + adjust;
int[] triangles = new int[] { 0, 1, 2, 1, 3, 2 };
Mesh mesh = new Mesh();
square.AddComponent<MeshFilter>().mesh = mesh;
square.AddComponent<MeshRenderer>().material = squareMaterial;
//square.transform.rotation = Quaternion.Euler(new Vector3(0, boardRotation.eulerAngles.y, 0));
//square.transform.rotation = boardRotation;
mesh.vertices = vertices;
mesh.triangles = triangles;
square.AddComponent<BoxCollider>();
return square;
}
screenshot of my problem
You could probably try and just add another
* Quaternion.Euler(0f, 45f, 0f)
In general though for your squares there is Transform.SetParent which allows you to pass the optional parameter worldPositionStays as false which is probably what you would want to do. By setting
transform.parent = parent
equals calling
transform.SetParent(parent);
which equals calling
transform.SetParent(parent, true);
so the objects keep their original world space orientation and position.
However, I would actually rather recommend you already create that board once in edit mode, make the entire board a prefab and now when you spawn the board you only need to take care of placing and rotating one single object and all children will already be their correctly placed and rotated within the board.
I'm using unity, c#, and I cannot figure out how I can generate a landscape with all different kinds of height colors. For instance, I want sand near the water and snow on mountain peaks. But I cannot manage to make this happen in code where the mesh is being generated.
Just to make clear, The mesh generates perfectly fine, but I cannot manage to change the colors of each separate triangle.
void CreateMesh()
{
planeMesh = new Mesh();
List<Vector3> vertices = new List<Vector3>();
List<Vector2> uvs = new List<Vector2>();
List<int> indices = new List<int>();
List<Color> colors = new List<Color>();
for (int x = 0; x < gridSize + 1; x++)
{
for (int y = 0; y < gridSize + 1; y++)
{
float height = Mathf.PerlinNoise(y / (float)gridSize, x / (float)gridSize) * 15.0f;
vertices.Add(new Vector3(y, height, x));
if(height > 12)//mountain tops
{
uvs.Add(new Vector2(0, 1/3));//down to up, x first
colors.Add(Color.white);
}
else if(height > 3 && height < 13)//grassy fields
{
uvs.Add(new Vector2(1/3,2/3));
colors.Add(Color.green);
}
else if(height > 0 && height < 4)//sand
{
uvs.Add(new Vector2(2/3, 2/3));
colors.Add(Color.yellow);
}
}
}
//making the indices
for (int b = 0; b < gridSize; b++)
{
for (int c = 0; c < gridSize; c++)
{
indices.Add(b + ((gridSize + 1) * c));
indices.Add(b + gridSize + 1 + ((gridSize + 1) * c));
indices.Add(b + 1 + ((gridSize + 1) * c));
indices.Add(b + gridSize + 1 + ((gridSize + 1) * c));
indices.Add(b + gridSize + 2 + ((gridSize + 1) * c));
indices.Add(b + 1 + ((gridSize + 1) * c));
}
}
planeMesh.SetVertices(vertices);
planeMesh.SetIndices(indices.ToArray(), MeshTopology.Triangles, 0);
planeMesh.SetUVs(0, uvs);
planeMesh.RecalculateNormals();
planeMesh.colors = colors.ToArray();
GetComponent<MeshFilter>().mesh = planeMesh;
GetComponent<MeshFilter>().mesh.name = "Environment";
}
Could you guys help me with that?
Solved it! It seems the numbers I was using for the uvs were being rounded down to 0! I had to make them floats to use them properly.
I am using Unity 5 to create an isometric game. I have generated a grid of tiles and it works well. However, when I use two different tiles to fill in the grid (their image sizes are slightly different), I get gaps in between the tiles. The obvious solution would be to create the tiles so that they are all the same image size, but this would prevent me from creating anything on a tile that is larger than the size of a tile (eg. a tree).
Here are some images to demonstrate:
With only one type of tile:
With two types of tile:
This is the code I use to create the map:
private void CreateMap() {
float tileWidth;
float tileHeight;
int orderInLayer = 0;
SpriteRenderer r = floorTiles [0].GetComponent<SpriteRenderer> ();
tileWidth = r.bounds.max.x - r.bounds.min.x;
tileHeight = r.bounds.max.y - r.bounds.min.y;
for (int i = 0; i < map.GetLength(0); i++) {
orderInLayer += 1;
for (int j = 0; j < map.GetLength (1); j++) {
Vector2 position = new Vector2 ((j * tileWidth / 2) + (i * tileWidth / 2) + (tileWidth / 2), (j * tileHeight / 2) - (i * tileHeight / 2) + (tileHeight/ 2));
r = map[i,j].GetComponent<SpriteRenderer>();
r.sortingOrder = orderInLayer;
Instantiate(map[i, j], position, Quaternion.identity);
}
}
}
Any help would be greatly appreciated, I cannot seem to fix it!
You appear to be calculating a position for each of your tiles from scratch every time you create one. If you have 2 different sized tiles, then your calculation comes out different, hence the gaps in your tiles. This is because you're only using the width/height of the current tile, failing to take into account any previous tiles that may be a shorter/longer height/width.
Given you have varying heights AND widths you'll need a way to calculate the correct position for both to prevent gaps in the X and Y direction. I've mocked up something here, but it's untested. More of a concept(?) I guess.
float tileHeight = 0;
float tileWidth = 0;
Vector2 position = new Vector2(0,0);
Dictionary<int, float> HeightMap = new Dictionary<int, float>();
for (int iRow = 0; iRow < map.GetLength(0); iRow++)
{
position.x = 0;
orderInLayer += 1;
for (int jColumn = 0; jColumn < map.GetLength (1); jColumn++)
{
position.y = HeightMap[jColumn];
r = map[iRow, jColumn].GetComponent<SpriteRenderer>();
tileWidth = r.bounds.max.x - r.bounds.min.x;
tileHeight = r.bounds.max.y - r.bounds.min.y;
r.sortingOrder = orderInLayer;
position.x += tileWidth / 2;
position.y += tileHeight / 2;
Instantiate(map[iRow, jColumn], position, Quaternion.identity);
HeightMap[jColumn] = position.y;
}
}
I leave the best way of storing the height, or instantiating the contents of the HeightMap dictionary to however you see fit.
In another thread on XNA, Callum Rogers wrote some code which creates a texture with the outline of a circle, but I'm trying to create a circle filled with a color. What I have to modify on this code to fill the circle with color?
public Texture2D CreateCircle(int radius)
{
int outerRadius = radius*2 + 2; // So circle doesn't go out of bounds
Texture2D texture = new Texture2D(GraphicsDevice, outerRadius, outerRadius);
Color[] data = new Color[outerRadius * outerRadius];
// Colour the entire texture transparent first.
for (int i = 0; i < data.Length; i++)
data[i] = Color.Transparent;
// Work out the minimum step necessary using trigonometry + sine approximation.
double angleStep = 1f/radius;
for (double angle = 0; angle < Math.PI*2; angle += angleStep)
{
// Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
int x = (int)Math.Round(radius + radius * Math.Cos(angle));
int y = (int)Math.Round(radius + radius * Math.Sin(angle));
data[y * outerRadius + x + 1] = Color.White;
}
texture.SetData(data);
return texture;
}
Don't use a texture for stuff like this (especially for things being in one single color!) - also don't try to do it pixel by pixel. You've got 3D acceleration for a reason.
Just draw the circle similar to a pie using a triangle fan. You'll need the following vertices.
Center of the circle
x points on the circle's border.
The first two points will define a line between the center of the circle and its border. The third vertex will define the first polygon. Vertices 1, 3 and 4 will then define the second polygon, etc.
To get the points on the circle's border use the formulas from your example. The first angle will be 0°, the following ones multiples of (360° / points on circle). To get a full circle you'll need one additional point that matches the second point (the first point on the border).
Depending on the number of vertices on the circle you'll get different n-gons. The more vertices you use the rounder the shape will look (at some performance cost):
(Less than 2 vertices aren't possible as a polygon requires at least 3 vertices to be drawn.)
Total of 4 points (3 points on circle) will result in a triangle.
Total of 5 points (4 point on circle) will result in a square.
Total of 6 points (5 points on circle) will result in a pentagon
...
Actually the XNA example for drawing primites show how to draw a circle (or n-gon) using a triangle fan.
well for anyone who wants to do it pixel by pixel ... i made a solution based on the information given. In your 2d texture method add the following code to fill the circle. I'm making a game and wanted to be able to make circles different colors and sizes. So inside CreateCircle(int radius) method, add the following code after the outline has been created :
bool finished = false;
int firstSkip = 0;
int lastSkip = 0;
for (int i = 0; i <= data.Length - 1; i++)
{
if (finished == false)
{
//T = transparent W = White;
//Find the First Batch of Colors TTTTWWWTTTT The top of the circle
if ((data[i] == Color.White) && (firstSkip == 0))
{
while (data[i + 1] == Color.White)
{
i++;
}
firstSkip = 1;
i++;
}
//Now Start Filling TTTTTTTTWWTTTTTTTT
//circle in Between TTTTTTW--->WTTTTTT
//transaparent blancks TTTTTWW--->WWTTTTT
// TTTTTTW--->WTTTTTT
// TTTTTTTTWWTTTTTTTT
if (firstSkip == 1)
{
if (data[i] == Color.White && data[i + 1] != Color.White)
{
i++;
while (data[i] != Color.White)
{
//Loop to check if its the last row of pixels
//We need to check this because of the
//int outerRadius = radius * 2 + -->'2'<--;
for (int j = 1; j <= outerRadius; j++)
{
if (data[i + j] != Color.White)
{
lastSkip++;
}
}
//If its the last line of pixels, end drawing
if (lastSkip == outerRadius)
{
break;
finished = true;
}
else
{
data[i] = Color.White;
i++;
lastSkip = 0;
}
}
while (data[i] == Color.White)
{
i++;
}
i--;
}
}
}
}
// Set the data when finished
//-- don't need to paste this part, already given up above
texture.SetData(data);
return texture;
If you need to do it from scratch (though I'm guessing there are easier ways), change the way you perform the rendering. Instead of iterating through angles and plotting pixels, iterate through pixels and determine where they are relative to the circle. If they are <R, draw as fill color. If they are ~= R, draw as border color.
I know that I'm a little late, but I modified your code to fill in the center
public static Texture2D CreateCircle(GraphicsDevice importedGraphicsDevice, int radius)
{
int outerRadius = radius * 2 + 2; // So circle doesn't go out of bounds
Texture2D texture = new Texture2D(importedGraphicsDevice, outerRadius, outerRadius);
Color[] data = new Color[outerRadius * outerRadius];
// Colour the entire texture transparent first.
for (int i = 0; i < data.Length; i++)
data[i] = Color.Transparent;
// Work out the minimum step necessary using trigonometry + sine approximation.
double angleStep = 1f / radius;
for (double angle = 0; angle < Math.PI * 2; angle += angleStep)
{
// Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
int x = (int)Math.Round(radius + radius * Math.Cos(angle));
int y = (int)Math.Round(radius + radius * Math.Sin(angle));
data[y * outerRadius + x + 1] = Color.White;
}
//width
for (int i = 0; i < outerRadius; i++)
{
int yStart = -1;
int yEnd = -1;
//loop through height to find start and end to fill
for (int j = 0; j < outerRadius; j++)
{
if (yStart == -1)
{
if (j == outerRadius - 1)
{
//last row so there is no row below to compare to
break;
}
//start is indicated by Color followed by Transparent
if (data[i + (j * outerRadius)] == Color.White && data[i + ((j + 1) * outerRadius)] == Color.Transparent)
{
yStart = j + 1;
continue;
}
}
else if (data[i + (j * outerRadius)] == Color.White)
{
yEnd = j;
break;
}
}
//if we found a valid start and end position
if (yStart != -1 && yEnd != -1)
{
//height
for (int j = yStart; j < yEnd; j++)
{
data[i + (j * outerRadius)] = new Color(10, 10, 10, 10);
}
}
}
texture.SetData(data);
return texture;
}