I am making a grid based game. For it I need to debug certain tiles, for example where there is a collision and where the player is aiming. I have made a debugger class that draws a square mesh at a position using a color.
public class GridDebugger : MonoBehaviour {
[SerializeField] private float alpha;
private Mesh mesh;
private Material mat;
void Start() {
mat = new Material(Shader.Find("Sprites/Default"));
mesh = new Mesh();
mesh.vertices = new Vector3[] { new Vector3(.5f, .5f, 0), new Vector3(.5f, -.5f), new Vector3(-.5f, -.5f), new Vector3(-.5f, .5f) };
mesh.triangles = new int[] { 0, 1, 2, 2, 3, 0 };
}
public void Debug(Vector3 position, Color color) {
color.a = alpha;
mat.color = color;
Graphics.DrawMesh(mesh, position, Quaternion.identity, mat, 0);
}
}
Currently the class has two users:
[RequireComponent(typeof(GridDebugger))]
public class CollisionSystem : GridSystem {
private List<Node>[,] grid;
private GridDebugger debugger;
void Awake() {
debugger = GetComponent<GridDebugger>();
}
//Logic....
void Update() {
if (grid != null) {
for (int x = 0; x < grid.GetLength(0); x++) {
for (int y = 0; y < grid.GetLength(1); y++) {
if (grid[x, y] != null) {
for (int i = 0; i < grid[x, y].Count; i++) {
if (grid[x,y][i].Parent.Debug) {
Vector3 worldPos = GridToWorldPos(new Vector2Int(x, y), grid.GetLength(0), grid.GetLength(1));
worldPos.z = -0.0001f;
debugger.Debug(worldPos, Color.Red);
}
}
}
}
}
}
}
}
And
public class GridMeleeWeapon : MonoBehaviour {
private GridDebugger debugger;
void Awake() {
debugger = FindObjectOfType<GridDebugger>();
}
public void Aim(Vector2 dir) {
Vector3 position = transform.position + dir;
debugger.Debug(position, Color.blue);
}
}
In CollisionSystem I loop every collider and draw a red square at every position.
In GridMeleeWeapon I just draw a blue square where the player aims.
The issue is that every square is drawn with the color used in CollisionSystem i.e red. When drawing the blue squares in GridMeleeWeapon they turn out red. If I remove debugger.Debug(worldPos, Color.Red) in CollisionSystem the squares I draw from GridMeleeWeapon are displayed blue.
With the current code I can't seem to draw meshes of different colors. What I think is happening is that the color specified by the first caller are used for consecutive calls, even though the color of the material is changed but why I have no idea.
How do I set the color correctly?
Material in Unity 3D is a reference type.
Your code in GridMeleeWeapon and CollisionSystem use the same instance of GridDebugger and material. What happens is since you change the material color OnUpdate() it will change the color from blue to red instantly (every frame) on each object using this material.
Instead of passing a color to your debugger().Debug() method, pass a material with a different color. For instance create a material with color red and another material with color blue. Use these materials to set the color.
Related
Ive been working for a few days on this problem, trying to create a square grid of tiles, using Unity Tilemaps seems to be the most efficient way, and it has a lot built in already.
I want to highlight the tiles around the player so that he will see what the legal moves are.
I use a second Tilemap for this, on top of the basemap(ground/grass).
The second Tilemap, the highlightsMap is made up of invisible Tiles, which will turn into highlighted Tiles when a Physics.SphereOverlap occurs, or a Physics2D.CircleAll
To detect every Tile, I have added a box collider on an empty object and I made a grid of these colliders on top of the Tilemap grid, so that:
Box Collider at Position (2,4,0) is exactly on top of Tile at Position(2,4,0)
This should be the most straightforward way to handle this, as you can change a Tile, using Tilemap.SetTile(Vector3Int Pos, Tile tile)
The problem is very strange however. The colliders have the correct positional values to be able to reference the tiles exactly underneath them, just through that position data. As explained above, and I have double checked this, the collider empties have the exact same position as the tiles underneath them, no conversion is needed.
The problem is that the tiles are not highlighting around the player as expected, instead a few next to the player are highlighted and others arent, the Physics.OverlapSphere I am using only works on 3D Colliders, which is why I added them as another grid on top of everything.
Using Physics2D.CircleAll, unfortunately does not detect any 2D colliders on the tiles themselves(Sprite, or Grid), not sure if that is intended to work like that anyway.
Collision Grid Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class scr_ColliderGrid : MonoBehaviour
{
public GameObject eCollider;
public Tile invisibleTile;
public Tile highlightTile;
public Tilemap highlightMap;
public int xSize, ySize;
private Vector3Int[] gridArray; // Tilemaps use Vector3Int BUT only use (X, Y, 0) !!!!!!!
private int xC = 0, yC = 0, i = 0;
private Tile[] tiles;
private Vector3Int previous;
private void Start()
{
gridArray = new Vector3Int[xSize * ySize];
tiles = new Tile[xSize * ySize];
GenerateCollisionGrid();
}
private void GenerateCollisionGrid()
{
for (xC = 0; xC < xSize; xC++)
{
for (yC = 0; yC < ySize; yC++)
{
Vector3Int newPos = new Vector3Int(xC, yC, 0); // 2, 4, 0
Vector3Int newColPos = new Vector3Int(xC, yC, 0); // 2, 4, 0 //This used to be different values, but now they are exactly the same.
if (invisibleTile != null)
{
Tile tile = Instantiate(invisibleTile, newPos, Quaternion.identity, transform);
tiles[i] = tile;
GameObject col = Instantiate(eCollider, newColPos, Quaternion.identity, transform);
}
gridArray[i] = newPos;
i++;
}
}
highlightMap.SetTiles(gridArray, tiles);
highlightMap.SetTile(new Vector3Int(2,4,0), highlightTile); // 2,4,0 //Test to see if positions are the same. (collider and tiles)
}
Player Highlight Legal Moves Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class scr_TankMoves : MonoBehaviour
{
public Tile highlightTile;
public TileBase[] highlightTiles;
public Tilemap highlightMap;
public int maxMoveTiles;
public bool highlight;
private Vector3Int previous, previousLeft, previousRight, previousForward, previousAft, previousVect;
private Vector3Int[] previousVectors;
void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
transform.localPosition = new Vector3(transform.localPosition.x,
transform.localPosition.y + 1,
transform.localPosition.z );
}
if (Input.GetKeyDown(KeyCode.S))
{
transform.localPosition = new Vector3(transform.localPosition.x,
transform.localPosition.y - 1,
transform.localPosition.z );
}
if (Input.GetKeyDown(KeyCode.D))
{
transform.localPosition = new Vector3(transform.localPosition.x + 1,
transform.localPosition.y,
transform.localPosition.z);
}
if (Input.GetKeyDown(KeyCode.A))
{
transform.localPosition = new Vector3(transform.localPosition.x - 1,
transform.localPosition.y,
transform.localPosition.z);
}
}
void LateUpdate()
{
if (highlight)
HighlightMoves();
else EraseHighlights();
}
private void HighlightMoves()
{
previousVectors = new Vector3Int[1];
highlightMap.SetTiles(previousVectors, null);
int tMax = 0;
Collider[] legalTiles = Physics.OverlapSphere(transform.position, maxMoveTiles/2);
previousVectors = new Vector3Int[legalTiles.Length];
foreach (Collider col in legalTiles)
{
//Vector3 conversionVector = new Vector3(col.transform.localPosition.x, col.transform.localPosition.y, col.transform.localPosition.z);
Vector3Int tileVector = Vector3Int.FloorToInt(col.transform.position);
previousVectors[tMax] = tileVector;
tMax++;
}
highlightMap.SetTiles(previousVectors, highlightTiles);
}
private void EraseHighlights()
{
//highlightMap.SetTile(previousForward, null);
//highlightMap.SetTile(previousAft, null);
//highlightMap.SetTile(previous, null);
//highlightMap.SetTile(previousRight, null);
//highlightMap.SetTile(previousLeft, null);
highlightMap.SetTiles(previousVectors, null);
}
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(transform.position, maxMoveTiles/2);
}
}
}
If you open a new 3D Project in Unity and setup a grid with a Tilemap, called highlightMap in my code example, you should be able to recreate this exactly, using the 2 scripts.
Everything is oriented in 2D, this means x+ is Right y+ is forward and z+ is up/dephth (unused by tilemaps).
The empty Collider prefab I have is an empty GO with pos 0,0,0. it has another empty GO Child, which has the Box Collider Component, and the transform value of this child is 0.5,0.5,0.5, so that the Collider is centered on top of each tile.
This is after 0 moves, so just pressed Play:
This is after 1 move forward, (y+1):
Instead of going by colliders at all afaik you could also just use GridLayout.WorldToCell like e.g.
Tilemap yourTileMap;
// see https://docs.unity3d.com/ScriptReference/Tilemaps.Tilemap-layoutGrid.html
var grid = yourTileMap.layoutGrid;
// The "Grid" implements "GridLayout"
// see https://docs.unity3d.com/ScriptReference/Grid.html
var currentCellPos = grid.WorldToCell(transform.position);
var currentCell = yourTileMap.GetTile(currentCellPos);
Then if you need to get multiple tiles I would probably go the "lazy/stupid" way and just do something like e.g.
var currentCellPos = grid.WorldToCell(transform.position);
var currentCell = yourTileMap.GetTile(currentCellPos);
var leftCellPos = grid.WorldToCell(transform.position - transform.right * someRange);
var leftCell = yourTileMap.GetTile(leftCellPos);
var rightCellPos = grid.WorldToCell(transform.position + transform.right * someRange);
var rightCell = yourTileMap.GetTile(rightCellPos);
var frontCellPos = grid.WorldToCell(transform.position + transform.forward * someRange);
var frontCell = yourTileMap.GetTile(frontCellPos);
var backCellPos = grid.WorldToCell(transform.position - transform.forward * someRange);
var backCell = yourTileMap.GetTile(backCellPos);
highlightTiles[tMax] = Instantiate(highlightTile, col.transform.parent.position, Quaternion.identity, transform);
I dont know why, but apparently I didnt instantiate the tiles themselves into an Array of tiles, so everytime I was ''placing'' them on the grid, it was actually placing an empty array.
Now its fixed! Just need to find a nice way to erase all after a move and im set!
i am new here. I was studing procedural terrain with meshes. And i started a make a procedural terrain like Minecraft. And i don't know how i can remove the hidden vertices and triangles in the hidden blocks.
The hidden vertices and triangles
My code:
Voxel.cs:
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(MeshRenderer))]
public class Voxel : MonoBehaviour {
public Vector3[] vertices;
public int[] triangles;
public Vector3 pos;
public GameObject chunk;
Mesh mesh;
public bool blockSolid;
public Voxel(Vector3 position, GameObject obj)
{
pos = position;
chunk = obj;
}
void Start ()
{
mesh = new Mesh();
//Cube(0,0,0);
}
public void Cube(int x, int y, int z)
{
GameObject cube = new GameObject("Cubo");
cube.AddComponent(typeof(MeshFilter));
cube.AddComponent(typeof(MeshCollider));
cube.AddComponent(typeof(MeshRenderer));
cube.transform.parent = chunk.transform;
mesh = cube.GetComponent<MeshFilter>().mesh;
//cube.transform.position = pos;
vertices = new Vector3[]
{
new Vector3(x,y,z), // 0
new Vector3(x,y+1,z), // 1
new Vector3(x+1,y+1,z), // 2
new Vector3(x+1,y,z), // 3
new Vector3(x+1,y,z+1), // 4
new Vector3(x+1,y+1,z+1), // 5
new Vector3(x,y+1,z+1), // 6
new Vector3(x,y,z+1) // 7
};
triangles = new int[]
{
0,1,2, 0,2,3, // Face frontal
3,2,5, 3,5,4, // Face direita
0,7,6, 0,6,1, // Face esquerda
7,4,5, 7,5,6, // Face traseira
1,6,5, 1,5,2, // Face superior
0,3,4, 0,4,7 // Face inferior
};
UpdateMesh();
}
void UpdateMesh()
{
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
}
}
Chunk.cs:
using System.Collections.Generic;
using UnityEngine;
public class Chunk : MonoBehaviour {
public int tx, ty, tz;
public Vector3 pos;
IEnumerator BuildChunk()
{
for (int x = 0; x < tx; x++)
{
for (int y = 0; y < ty; y++)
{
for (int z = 0; z < tz; z++)
{
pos = new Vector3(x,y,z);
Voxel block = new Voxel(pos, this.gameObject);
block.Cube(x,y,z);
yield return null;
}
}
}
}
void Start ()
{
StartCoroutine(BuildChunk());
}
}
I just need to remove the hidden vertices and triangles, but i don't know how i gonna make this.
The trick is to generate only the triangles that are visible to the player. So you shouldn't be generating cubes for every voxel, but a subset of the faces of a cube.
The way you go about this, is to check all 6 sides of each voxel. If a given side borders another voxel, then ignore that side and move on. If there is no voxel bordering the side, then add the corresponding cube face to that side. This will result in a monolithic mesh encompassing your entire terrain, or the terrain you loaded in.
I have this problem and i don't know how to solve it,it's very confusing.I want that every time i instantiate the gameobject it gets a random color, but these clones get the same color from previous one.Like the first prefab spawn and gets random colors, now the next one spawn and olso gets random colors but the first one gets it's color,it's very confusing i know!
enter image description here
Here is the code of the gate :
public Material[] material;
public Color[] colors;
public void ColorChange()
{
colors = new[] {
Color.blue,
Color.black,
Color.red,
Color.green,
Color.yellow,
Color.white,
Color.magenta,
Color.cyan,
Color.grey };
var rnd = new System.Random();
var randomColors = colors.OrderBy(x => rnd.Next()).ToArray();
for (var i = 0; i < material.Length; i++)
{
material[i].color = randomColors[i];
}
}
private void Start()
{
ColorChange();
}
And the script of the spawner:
public GameObject Gate; // the gameobject
private float timeBtwSpawn;
public float startTimeBtwSpawn;
public float decreaseTime;
public float minTime = 0.65f;
void Start()
{
}
void Update()
{
if (timeBtwSpawn <= 0)
{
Instantiate(Gate, transform.position, Quaternion.identity);
timeBtwSpawn = startTimeBtwSpawn;
if (startTimeBtwSpawn > minTime)
{
startTimeBtwSpawn -= decreaseTime;
}
}
else
{
timeBtwSpawn -= Time.deltaTime;
}
}
Materials are shared between GameObjects, so if you change the color of a material - all objects using that material will get the new color.
Here's a few approaches (see the last one for my suggestion):
MaterialPropertyBlock
(Will not work in your case)
You could check out MaterialPropertyBlock:
Use it in situations where you want to draw multiple objects with the same material, but slightly different properties. For example, if you want to slightly change the color of each mesh drawn.
Will not work because you're re-using the materials.
Pre-created set of materials
Work around it by either creating a number of materials and chosing a random one.
(Which is what you have done, but will not work since you're reusing the materials)
New materials in runtime
Or you could create new materials in runtime.
(Should work, but next one is easier)
GameObject's Renderer.material
But I believe the easiest way should be to do:
mySpawnedGameObject.GetComponent<Renderer>().material.color = randomColor;
as opposed to using material.sharedMaterial. This should only apply to the one GameObject you choose: Renderer.material
I have been trying to make my own level generator, which generates a prefab with the correct sprite based on the color that is match in the small map that I made.
More specifically, I made this small map.
If one of the colors matches one of the colors that are found in a color array, it will instantiate a prefab with the correct sprite and so on for the other colors. So far it instantiates the objects with the correct sprites.
However this is what it looks like when I play the game.
And this is what it's suppose to look like
I don't know why it is doing this because based on my code,
public Texture2D needColors;
public Texture2D tiles;
public Texture2D level;
public GameObject tile;
public Transform parent;
public Color32[] colors;
public Sprite[] sprites;
void Start(){
for(int x = 0; x < level.width; x++){
for(int y = 0; y < level.height; y++){
GenerateLevel(x, y);
}
}
}
void GenerateLevel(int x, int y){
Color32 pixelColor = level.GetPixel(x, y);
if(pixelColor.a == 0){
return;
}
for(int i = 0; i < colors.Length; i++){
if(pixelColor.Equals(colors[i])){
Vector2 position = new Vector2(x, y);
Instantiate(tile, position, Quaternion.identity, parent);
tile.GetComponent<SpriteRenderer>().sprite = sprites[i];
}
}
}
it should be skipping the transparent pixels.
How can I do this correctly?
I'm working on a 2D Unity app and I'm encountering some weird behavior.
This code works just fine.
Debug.DrawLine(button1.transform.position, button2.transform.position, Color.green);
When I run the app, I see a green line in the Scene view.
But nothing appears in the Game view when I have the following line.
Physics2D.Linecast(button1.transform.position, button2.transform.position);
I'm confused as to how Unity is able to draw a line between these two buttons, but for some reason, it's just not doing it in the Game view.
Any idea how I'd troubleshoot this?
Just line Serlite said, Physics2D.Linecast is not used to draw a line but to detect if there is an object in the middle of two objects with raycast. It has nothing to do with drawing lines.
As you already know, Debug.DrawLine will only work in the Scene view unless Gizmos is enabled. You can just LineRenderer or the GL functions to draw lines which will work without enabling Gizmos and will also work in a build.
Here is a helper class for drawing line in the Game and Scene view.
public struct LineDrawer
{
private LineRenderer lineRenderer;
private float lineSize;
public LineDrawer(float lineSize = 0.2f)
{
GameObject lineObj = new GameObject("LineObj");
lineRenderer = lineObj.AddComponent<LineRenderer>();
//Particles/Additive
lineRenderer.material = new Material(Shader.Find("Hidden/Internal-Colored"));
this.lineSize = lineSize;
}
private void init(float lineSize = 0.2f)
{
if (lineRenderer == null)
{
GameObject lineObj = new GameObject("LineObj");
lineRenderer = lineObj.AddComponent<LineRenderer>();
//Particles/Additive
lineRenderer.material = new Material(Shader.Find("Hidden/Internal-Colored"));
this.lineSize = lineSize;
}
}
//Draws lines through the provided vertices
public void DrawLineInGameView(Vector3 start, Vector3 end, Color color)
{
if (lineRenderer == null)
{
init(0.2f);
}
//Set color
lineRenderer.startColor = color;
lineRenderer.endColor = color;
//Set width
lineRenderer.startWidth = lineSize;
lineRenderer.endWidth = lineSize;
//Set line count which is 2
lineRenderer.positionCount = 2;
//Set the postion of both two lines
lineRenderer.SetPosition(0, start);
lineRenderer.SetPosition(1, end);
}
public void Destroy()
{
if (lineRenderer != null)
{
UnityEngine.Object.Destroy(lineRenderer.gameObject);
}
}
}
Usage:
LineDrawer lineDrawer;
void Start()
{
lineDrawer = new LineDrawer();
}
void Update()
{
lineDrawer.DrawLineInGameView(Vector3.zero, new Vector3(0, 40, 0f), Color.blue);
}
When done, you can call lineDrawer.Destroy();.
Debug.DrawLine renders a line gizmo into the Scene View.
If you want a line rendered into the Game View, use a Line Renderer Component.
Line Renderer Docs