Need to fix a problem with instantiated gameObjects - c#

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

Related

Unity Tilemaps, Single Tile Collision detection for highlighting Tiles/Cells, strange behaviour

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!

Why is my 3D text gameobject in Unity becoming clear instead of changing color?

I am trying to get my 3d text gameobject in Unity to change to a random color when the ChangeColor() function is called but it is instead becoming clear. I used the same code on a cube and it worked but it is not working with this text. Thank you for any help!
Here is my code:
public class ColorChanger1 : MonoBehaviour
{
public Color[] shapeColor = new Color[5];
// Start is called before the first frame update
void Start()
{
//GetComponent<Renderer>().material.color = shapeColor[1]; //this was an attempt to just set it to one of the colors in my color array but it also turned the text clear
ChangeColor();
}
// Update is called once per frame
void Update()
{
}
void ChangeColor() {
var textRenderer = GetComponent<Renderer>();
if (shapeColor.Length > 0)
{
int newColor = Random.Range(0, shapeColor.Length);
textRenderer.material.color = shapeColor[newColor];
}
}
To change a TextMesh's color, you need to modify the TextMesh component's color, not the generic Renderer's material color.
var textMesh = GetComponent<TextMesh>();
if (shapeColor.Length > 0)
{
int newColor = Random.Range(0, shapeColor.Length);
textMesh.color = shapeColor[newColor];
}

How to spawn random 2D platforms that do not overlap in Unity?

I'm looking for a way to randomly spawn platforms that do not overlap with each other. All the tutorials I found on this topic are for an endless runner game type, and my project is not like that. So far I know how to spawn my platforms but I need those gaps between them. I'm a total begginer in Unity and C# so I'm looking for a simple code if possible.
My code now:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameStateManager: MonoBehaviour {
public GameObject YellowPrefab;
public int howManyYellow;
void Start()
{ GameObject tmpYellow;
for (int i = 0; i < howManyYellow; i++)
{
tmpYellow = Instantiate(YellowPrefab, new
Vector3(Random.Range(-50, 50), Random.Range(-
40, -17), 0), Quaternion.identity);
}
}
My platforms have box colliders used by platform effector, if that information is needed.
Edit:
If possible, it would be nice to be able to set max distance between the random platforms, but if it's something hard to do with that kind of code then it's OK without that:)
Look into Physics2D.OverlapBox() or related functions, depending on your needs.
With this you can check if your object's collider is overlapping with any other collider, like so:
for (int i = 0; i < howManyYellow; i++)
{
tmpYellow = Instantiate(YellowPrefab,
new Vector3(Random.Range(-50, 50),
Random.Range(-40, -17), 0),
Quaternion.identity);
BoxCollider2D tmpYCollider = tmpYellow.GetComponent<BoxCollider2D>();
tmpyCollider.enabled = false; // Disable object's own collider to prevent detecting itself
// while collider overlaps, move your object somewhere else (e.g. 17 units up)
while (Physics2D.OverlapBox(tmpYCollider.bounds.center, tmpYCollider.size, 0) != null)
{
tmpYellow.transform.Translate(new Vector3(0, 17));
// or do something else
}
tmpyCollider.enabled = true; // enable the collider again
}
From what I understand, you want to create a bunch of platforms randomly in the area of (-50,-40) -> (50,-17).
If you do not want a platform to be created in the same spot you need to keep track of the location that all of the platforms were placed in.
public GameObject YellowPrefab;
public int howManyYellow;
private List<Vector3> locations;
void Start()
locations = new List<Vector3>();
{
GameObject tmpYellow;
for (int i = 0; i < howManyYellow; i++)
{
bool hasItem = false;
Vector3 tempLocation;
do{
tempLocation = new Vector3(Random.Range(-50, 50), Random.Range(-40, -17), 0);
foreach (Vector3 item in locations)
{
if(tempLocation == item){
hasItem = true;
}
}
} while(hasItem);
locations.Add(tempLocation);
tmpYellow = Instantiate(YellowPrefab, tempLocation, Quaternion.identity);
}
}

Debug.DrawLine not showing in the GameView

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

Changing an object change all the instantiated objects

I know that the topic of the question has been already asked and some answer have been given to it, but i'm asking it again because i fail misserably when i try to apply the answers from the other topics.
The problem that i have is that, i have a prefab (quad) 'Star' with a material into it and an script that makes it change color after some time:
using UnityEngine;
public class Star: MonoBehaviour {
public enum StarType {
BRIGHT,
DARK,
F,
K,
L,
P
}
private Ship ship;
private StarType starType;
private float timeLimit = .30f, timer;
private System.Random rand;
public void Awake() {
ship = FindObjectOfType<Ship>();
timer = .0f;
rand = new System.Random();
}
public void Start() {
refactor();
}
public void Update() {
timer += Time.deltaTime;
if(timer > timeLimit) {
refactor();
timer = .0f;
}
}
public void LateUpdate() {
transform.eulerAngles = new Vector3(ship.transform.eulerAngles.x,
ship.transform.eulerAngles.y,
ship.transform.eulerAngles.z);
}
public void refactor() {
starType = (StarType) rand.Next(0,
System.Enum.GetValues(typeof(StarType)).Length);
switch(starType ) {
case StarType.BRIGHT:
renderer.material.color = Color.white;
break;
case StarType.DARK:
renderer.material.color = Color.black;
break;
case StarType.F:
renderer.material.color = Color.red;
break;
case StarType.K:
renderer.material.color = Color.cyan;
break;
case StarType.L:
renderer.material.color = Color.green;
break;
case StarType.P:
renderer.material.color = Color.magenta;
break;
}
}
public StarType getStarType() {
return starType;
}
}
And this stars being instantiated in a Chunk class:
public class Chunk : MonoBehaviour {
public Star star;
private IList<Star> stars;
public void Awake() {
stars = new List<Star>();
Star auxStar = (Star) Instantiate(star, transform.position,
Quaternion.identity);
auxStar.transform.SetParent(transform);
stars.Add(auxStar);
}
}
I was assuming that each star would change the color independently, but instead of that they all change to the same color.
I've tried using sharedMaterial instead of material as i read it on some asnwers, though the result and behaviour seems the same?, and i tried giving random init colors to the stars, but the most that i get is having a few of them changing to a color different from the rest, though still the same between them (And i'm trying to change colors all the time, not only upon creation).
I've read too creating a material and asigning to each one at instantiating, but i had no luck:
auxStar.renderer.material = (Material)
Instantiate(star.renderer.material);
Does someone know how can the problem be handled?
According to Unity API reference, you are doing the right thing.
The issue in your code is that you are instantiating a MonoBehaviour
public Star star;
...
Star auxStar = (Star) Instantiate(star, transform.position, Quaternion.identity);
where star is a MonoBehaviour, you are not instantiating the actual GameObject with the Mesh Render and/or Material. Instead make sure you are instantiating the right GameObject Prefab, not just the component, like this:
public GameObject starPrefab;
...
GameObject starClone = (GameObject) Instantiate (starPrefab);
Star auxStar = starClone.GetComponent<Star> ();
In that way you have control of the GameObject, if you need, and every component associated with the Cloned Object, in this case Star. You don't have to instantiate the Prefab Material cause it will be already instantiated.
Here is a simple Example:
using UnityEngine;
using System.Collections;
public class SpawnStar : MonoBehaviour {
public GameObject StarPrefab;
public bool InstantiateStar = false;
void Update () {
if (InstantiateStar) {
GameObject starClone = (GameObject)Instantiate (StarPrefab);
float r = Random.Range (0f, 1f);
float g = Random.Range (0f, 1f);
float b = Random.Range (0f, 1f);
float a = Random.Range (0f, 1f);
starClone.renderer.material.color = new Color (r, g, b, a);
InstantiateStar = false;
}
}
}
Side Note: Make sure to not modify the Prefab during gameplay since is the source of all other cloned objects.
Try using Random.Range() As in System.Random is generated by system time. As we know that Time is not Really Random. It still depends on mil sec times and generate the the logic of Random as we can't really tell which mil sec the random will run.
Since System.Random runs on 1 timeline which your scripts are using. Random.Range() is a bet you can consider since it uses SystemTime and independent time On Run of the platform. Shares with Time.DeltaTime.

Categories