Changing materials properties during runtime - c#

I am trying to alter my HDRP Lit material's emissive color and emissive intensity but it does not get reflected on my gameObjects. I have even tried using "_EmissionColor" and still there was no change. However the material that is suppose to change, shows as (Instance). What am I doing wrong here?
public GameObject[] myGOs;
private void Start () {
for(int i = 0; i<myGOs.Length; i++){
myGOs[i].GetComponent<Renderer>().material.SetColor("_EmissiveColor", new Vector4(0.8196f,0.783f,0,1) * 3.0f);
}
}

First, check if your material has this property - material.HasProperty("_EmissiveColor")
If, so, there is a trick which is usually applied in such cases - enable the keyword:
material.EnableKeyword("_EMISSION");
For your material the keyword can differ. You can find it among all shader keywords using material.shaderKeywords

Related

grab grid tiles in unity

First of all: I'm quite new to unity and c#, so please be nice if I ask dumb questions.
I'm trying to make a game like minesweeper. So I want an easy grid with covered tiles and if you klick on one, it opens up.
I use a main script I create a grid like so:
private void CreateTieleSet(int width, int height){
_tiles = new Dictionary<Vector2, Tile>();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
var spawnedTile = Instantiate(_tilePrefab, new Vector3(x, y), Quaternion.identity);
spawnedTile.name = $"Tile {x} {y}";
spawnedTile.status = field[x, y];
_tiles[new Vector2(x, y)] = spawnedTile;
}
}
}
I use the function
public Tile GetTileAtPosition(Vector2 pos)
{
if(_tiles.TryGetValue(pos, out var tile))
{
return tile;
}
return null;
}
to get the tile at the position xy, but I can't use it since
Tile tempTile = GetTileAtPosition(new Vector2(x, y));
tempTile.ChangeSprite(field[x, y]); //example-funktion from tile-script
allways results in NullReferenceExeption-Error. I know the probleme, since I allways struggle with using scripts from other tiles. Bus usually I can use [SerializeField] Tile... and than dragdrop it onto it. In this case however I obvioulsy can't do that.
Btw: I realy tried to solve this problem with other solutions found online, but everyone has complete different ideas how to do it and nothing seems to work for me :/
Your best bet for this one is going to be using Unity's built-in Tilemaps - you'll want to take your sprites for Minesweeper and add them to a TilePalette, and paint them onto a GameObject with a Grid component, with children GameObjects holding a Tilemap component - one per layer. Then, when you want to change the sprite, you can do something like the following:
using UnityEngine;
using UnityEngine.Tilemaps;
class GridManager
{
[SerializeField] Tilemap gridDisplayLayer;
public void ChangeSpriteAtLocation(Vector3Int cellLocation, Tile tileToChangeTo)
{
gridDisplayLayer.SetTile(cellPosition, tile);
}
}
If you want the simplest answer, just add Tile members to the class above for swapping between them and call ChangeSpriteAtLocation during runtime. Might need to change the flags on the Tilemap to change the sprite and such, but I think that should be good enough to get you started :) When you generate the TilePalette, Unity will give you Tile assets to save that you can then reference for the above example method.
I'm still getting all the intricacies of Tilemaps myself, though, and I'm definitely more design minded, so I won't be surprised if someone has a more succinct or efficient solution.
Hope this helps get you started, though!
Vellv

Unity Script only works with long namespace name

I tried following this Unity ECS Entity Manager Tutorial from Turbo Makes Games.
I'm using Unity 2020.3.20f1.1086.
The Code works initially but I noticed, that Line 20 in SpawnEntitiesSystem.cs EntityManager.SetComponentData(newEntity, newPosition); stops working when I change or remove the namespace. After tinkering a bit with the code I can reproduce that the specific namespace is not important but the namespace has to be at least 18 characters long for the code to work as expected. If I change the namespace to 17 characters all entities are spawned at the same place. Other code after the faulty line 20 was executed but for readability I removed it from the code example. (Originally user input was also handled and in OnStartRunning a component was added to the new entitiy)
I only heard of similar problems in e.g. C++ when using pointer maths but I thought something like this shouldn't happen in C#. Does anyone have an idea what the core problem could be?
What I already tried without succes
restart Unity
delete Library, Obj and Logs folders
write code from scratch in new project
restart PC
SpawnEntitiesSystem.cs
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace ALongNamespaceABCD {
public class SpawnEntitiesSystem : SystemBase {
private int2 _entitySpacing;
protected override void OnStartRunning() {
var entitySpawnData = GetSingleton<EntitySpawnData>();
var gridSize = entitySpawnData.SpawnGrid;
_entitySpacing = entitySpawnData.EntitySpacing;
for (int x = 0; x < gridSize.x; x++) {
for (int y = 0; y < gridSize.y; y++) {
var newEntity = EntityManager.Instantiate(entitySpawnData.EntityPrefab);
var newPosition = new LocalToWorld { Value = CalculateTransform(x, y) };
EntityManager.SetComponentData(newEntity, newPosition);
}
}
}
private float4x4 CalculateTransform(int x, int y) {
return float4x4.Translate(new float3 {
x = x * _entitySpacing.x,
y = 1f,
z = y * _entitySpacing.y
});
}
protected override void OnUpdate() {}
}
}
EntitySpawnData
using Unity.Entities;
using Unity.Mathematics;
[GenerateAuthoringComponent]
public struct EntitySpawnData : IComponentData {
public Entity EntityPrefab;
public int2 SpawnGrid;
public int2 EntitySpacing;
}
Edit:
I tried around a bit more and found out that it seems that the correct position will be set correctly at first. But it will instantly be overwritten by the prefabs Transform component. So maybe it has to do with converting the Prefab to an entity (I use the default Convert to Entity component) or a problem with some internal multithreading occurs.
Well, we finally got a response from Unity. Here is what they said:
This is intended behavior and will therefore be set to "won't fix".
The reasons are this:
Changing the name (and namespace) of a system changes its default position in the frame, this is expected behavior.
The SpawnEntitiesSystem from the provided repro project is not using any explicit system ordering attribute like
UpdateAfter/UpdateBefore/UpdateInGroup, so it follows the default.
Because it is expected behavior but nonetheless surprising, we have a task in the backlog about making the default order not depend on the
name in the future.
The prefab being instantiated has a Translation component and a Rotation component in addition to the LocalToWorld component.
The transform system from entities updates LocalToWorld based on the values of Translation and Rotation.
A direct modification of LocalToWorld conflicts with the transform system, and that modification will be overwritten.
Because Translation and Rotation are still at their default values, the expected outcome is that the transform system will compute an
identity LocalToWorld and put the instances back to the origin.
Unfortunately there is a bug in that particular setup (where the system doing the modification does it in OnStartRunning and executes
immediately after the transform system, if the namespace is long
enough to put it after the transform system) that causes the newly
spawned instances to never be noticed by the transform system. And it
is because of that bug that the cubes would show in the intended grid
layout by accident.
Source: https://issuetracker.unity3d.com/issues/entity-localtoworld-position-is-overwritten-by-prefab-position-when-namespace-is-17-characters-long-or-shorter
So, apparently, this whole situation happens because the LocalToWorld is sometimes being overwritten due to operation ordering that the length of the namespace is able to change. But this won't be the case in a future version.
A workaround is to set the Translation instead of the LocalToWorld Data.
Working code:
EntityManager.SetComponentData(newEntity, new Translation {
Value = new float3(
10.0f,
1.0f,
10.0f
)
});

Unity color wont assign to ball or what it should be painting

private Color solveColor;
void Start()
{
Color[] colors = { Color.cyan, Color.red, Color.green, new Color(245, 195, 29), Color.yellow, Color.magenta };
int lengthOfColors = colors.Length;
int solveColor = UnityEngine.Random.Range(0, lengthOfColors);
}
private void start()
{
GetComponent<MeshRenderer>().material.color = solveColor;
}
private void FixedUpdate()
{
// Set the balls speed when it should travel
if (isTraveling) {
rb.velocity = travelDirection * speed;
}
// Paint the ground
Collider[] hitColliders = Physics.OverlapSphere(transform.position - (Vector3.up/2), .05f);
int i = 0;
while (i < hitColliders.Length)
{
GroundPiece ground = hitColliders[i].transform.GetComponent<GroundPiece>();
if (ground && !ground.isColored)
{
ground.Colored(solveColor);
}
The above code is supposed to pick one color from the colors array and assign it to both the ball and balls painting ability (whenever the ball collides with the ground it changes its color) however the paint the ball leaves is always black and the ball itself is always orange (pretty sure the ball color is coming from its default). I can't figure out why this is happening any help is very appreciated.
Thank you for your time
In the code you provided, nowhere do you set the material color of the ball again aside from Start. If you want to have the particles behind the ball leave different colors, you will need to instantiate a new instance of the material. The reason for this is because materials in Unity are default shared between all instances of that particular material.
All of this info and a bit more can be found on the Material docs page.
As you have a fixed size of colors you are using, I would instead create 6 new materials and make an array of materials instead. Now, instead of randomly picking a color, pick a material and assign it to the ball or your new instanced painting ability. I am also confused as to why you are placing your array of colors inside of your Start function. It would be localized to that function only then. You also appear to have two Start functions, which is odd. One being the Monobehaviour Start and another start. Unless that is intended, your second start will not be run unless you call it.
Now to get to the solution I was talking about.
// assign these in the inspector to your new materials
[SerializeField] private List<Material> materials = new List<Material>();
private MeshRenderer meshRender;
private void Start()
{
meshRenderer = GetComponent<MeshRenderer>();
// set our first random Material
SetNewMaterialColor();
}
private void SetNewMaterialColor()
{
meshRenderer.material = GrabNewMaterial();
}
private void FixedUpdate()
{
// Set the balls speed when it should travel
if (isTraveling) {
rb.velocity = travelDirection * speed;
}
// Paint the ground
Collider[] hitColliders = Physics.OverlapSphere(transform.position - (Vector3.up/2), .05f);
int i = 0;
while (i < hitColliders.Length)
{
GroundPiece ground = hitColliders[i].transform.GetComponent<GroundPiece>();
if (ground && !ground.isColored)
{
// change this from a color to a material instead
ground.Colored(meshRenderer.material);
// set a new material to your main object
SetNewMaterialColor();
}
}
}
private Material GrabNewMaterial()
{
return materials[UnityEngine.Random.Range(0, materials.Count)];
}
You will need to change your Colored function to take in a Material instead of a Color. If you want the implementation to be more dynamic, you can instead create an instance of your material and set the color dynamically, but as you have a fixed size of colors I do not think you need to do that.
Edit: The one other option which involves creating a new shader would be to utilize [PerRendererData] meaning each object for a property field is rendered individually. I would go with the previous option as either option using shaders or instanced materials is a bit more complex.
You would need to use a MaterialPropertyBlock and can then assign the color when you want. It would look something like
public void SetNewColor()
{
// create a new material property block
MaterialPropertyBlock tmpBlock = new MaterialPropertyBlock();
// grab the current block from our renderer
meshRender.GetPropertyBlock(tmpBlock);
// set our changes to the block
tmpBlock.SetColor("_Color", YourColorHere);
// now apply our changes
tmpRend.SetPropertyBlock(tmpBlock);
}
And you would need to create a new shader that laters the Main Color property by using the PerRendererData attribute.
Properties
{
[PerRendererData]_Color("Main Color", Color) = (1,1,1,1)
...
Also, one other question I have is why you are using Physics.OverlapSphere instead of just an OnCollisionEnter? Or if your game is 2D, then OnCollisionEnter2D and let the physics engine handle how collisions work, then just change the colors when the collision occurs?
Edit: Here are the answer to your questions - let me know if you have more.
In the line "[SerializeField] private List materials = new
List();" which section do I need to replace with the
materials and how?
The line as is is fine. By using [SerializeField] it exposes this list to the editor. You will want to create several new duplicate materials that use your 6 different colors. Instead of setting the colors, you will be setting materials now. What I mean by inspector and editor is you can find the object that has this script on it in Unity, select it (it must be a Prefab or in the scene), then a tab of the Unity editor will populate with information about this object. Find the script portion and find the field materials. There should be a drop-down arrow, click it and set the number to 6 (or however many material swaps you want). Now create 6 new materials with your colors and drag them into the boxes that appeared.
Would it be something like writing "./Materials/Ball 1" in the () for
example?
Nope! You would be assigning this data in the inspector, so the data would be stored in the list without referencing them in code.
And I'm not sure how to assign this to my ball using "[SerializeField]
private GameObject paintObject = null;"
Similarly, this would appear in the inspector. However, remove this line as I misunderstood your original question and accidentally left this in. I assumed that your paint object was a Prefab that you were spawning after the ball bounced, not the ground that you were changing the color of.
I get the error "Argument 1: cannot convert from
'UnityEngine.Material' to 'UnityEngine.Color'"
Yep! So as I mentioned in the comments, your function call to your paint object is most likely currently taking a Color parameter. As I changed your implementation to instead directly set Material, you will need to change how that function signature. Specifically the line:
ground.Colored(meshRenderer.material);
You have some object ground that is of type GroundPiece and has a function called Colored. I assume it currently look something like:
public void Colored(Color color){...}
You want to change this instead to:
public void Colored(Material mat{...}
After changing it, instead of changing the ground's color in this script, you would change its material directly. Let me know if you have more questions.

Change the Color for One of Many of the Same GameObject

I have a space shooter where players can select a color for their ships, and this applies to the bullets the ships shoot as well. The ships use the same bullet prefab, so I would like to change the color of the bullet once it's created in the game. I originally used the following code to change the color.
Material bulletMat = this.GetComponent<MeshRenderer>().sharedMaterial;
if (bulletMat != null)
{
bulletMat.SetColor("_TintColor", color);
}
However, I found out that this will change the color of the material and the bullets would switch in between colors with each bullet created. After doing some more research, I came accross the MaterialPropertyBlock variable and the following tutorial http://thomasmountainborn.com/2016/05/25/materialpropertyblocks/. So I followed this tutorial and setup the following code.
public Renderer renderer;
public MaterialPropertyBlock matBlock;
public Color color;
public bool colorSet = false;
void Awake()
{
renderer = this.GetComponent<Renderer>();
matBlock = new MaterialPropertyBlock();
}
public void ColorSet(Color color)
{
this.color = color;
this.colorSet = true;
}
void Update()
{
if (this.colorSet)
{
renderer.GetPropertyBlock(matBlock);
matBlock.SetColor("_Color", this.color);
renderer.SetPropertyBlock(matBlock);
}
}
However, the same issue as the previous solution happened....
Can someone help me figure out how to set the color for each individual bullet?
You use sharedMaterial only wherever you really want to change the material for every Object using this material. That's exactly what you don't want to do. Instead you ahve to use material so you only change a "clone" instance of the material.
Unity actually automatically instantiates a local copy of the current material only for that object if you do:
public class ColorChanger : MonoBehavior
{
private MeshRenderer meshRenderer;
private void OnEnable()
{
meshRenderer = GetComponent<MeshRenderer>();
}
public void SetColor(Color newColor)
{
renderer.material.color = newColor;
}
}
or using SetColor as you had it should do the same I guess
public void SetColor(Color newColor)
{
renderer.material.SetColor("_TintColor", newColor);
}
I personally find the before metnioned one easier. But the second one gives you more control since you can e.g. also change the emitColor which is not possible on another way.
Now when you instantiate your bullets make sure you first instantiate the prefab and than change the color using the component above:
public GameObject bulletPrefab;
public Color myColor;
public GameObject Hand;
private void Shoot()
{
// There are various ways for Instantiate e.g. providing start position and rotation or also the parent object
// Since it shall be fired from the player I'll assume here there is a "Hand" object from which you want to fire
// and that you want to add the bullet directly to the hierarchy without a parrent
GameObject newBullet = Instantiate(bulletPrefab, Hand.position, Hand.rotation);
// Now we get the color change component
ColorChanger colorChanger = newBullet.GetComponent<ColorChanger>();
// And set the color
colorChanger.SetColor(myColor);
}
Note:
In case this is a multiplayer game the bullets should be Instantiated only on the Server so you'ld have to first let the server know which color the bullet shall be and than also provide this information back to all players after you Network.Spawn them.
You don't need to keep updating the property block. Once is enough. You've also got to make sure that the material allows instancing, and that the shader supports instancing of "_Color". On the standard and sprite materials, you'll find that at the bottom of the inspector window for the material.
So, you can remove the test in Update, and modify the ColorSet method. In fact, on a Bullet type object, I'd be inclined to make it as lean as possible (ideally move all functionality to a central bullet manager, but that's off topic).
public void ColorSet(Color color)
{
renderer.GetPropertyBlock(matBlock);
matBlock.SetColor("_Color", color);
renderer.SetPropertyBlock(matBlock);
}
If this isn't helping, are you grabbing a bullet from a pool, which already has colours set on the objects? Is it simply a case that you're not setting the correct colour on already instantiated objects?
I'm also questioning why renderer.material didn't work (or if you ever tried it?). But, it's my understanding/guess that setting a new material actually breaks instancing. So you might actually get better performance from setting the property block anyway.

Change color of multiple game objects in Unity 5

I would like to change the color of multiple gameobjects in Unity using a single script. I'm kinda lost in the way how to do it. I'm new to Unity and this is some sort of basic training for me.
Unity version: 5.3.4
Observed Behavior:
Added the same script to the other gameobjects and all change to the same color
Expected Behavior:
Change the color of the gameobjects individually
List of things tried:
Using the -FindGameObject-
Tried to acces the materials using the -GameObject-
Tried both at the same time
Thinking in multiple scripts to achieve the results I want
Here's the code
C#:
using UnityEngine;
using System.Collections;
public class ChangeColor : MonoBehaviour
{
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
private Renderer cube;
private Renderer sphere;
void Start ()
{
//Tried here the -FindGameObjectWithTag-
cube = GetComponent<Renderer>();
sphere = GetComponent<Renderer>();
}
void Update ()
{
if(Input.GetKeyDown(KeyCode.A))
{
//Tried here the -FindGameObjectWithTag-
cube.material.color = Color.red;
}
if(Input.GetKeyDown(KeyCode.S))
{
//Tried here the -FindGameObjectWithTag-
sphere.material.color = Color.green;
}
}
}
Maybe I'm doing something wrong, as I said I'm new to Unity, I kindly accept any help, if it is noobfriendly the better.
Thanks
There are a couple different ways you can go about doing this.
If they are all under the same parent without other objects there too you could loop through the children and change their colors
GameObject Parent = GameObject.Find("ParentObject");
for( int x = 0; x > Parent.transform.childCount; x++);
{
Parent.transform.GetChild(x).GetComponent<Renderer>().material.color = Color.red;
}
If there are less than say 20ish, you could create a list and simply drag and drop each transform into the inspector after changing the list's size to the number of objects you have.
/*Make sure you have Using "System.Collections.Generic" at the top */
//put this outside function so that the inspector can see it
public List<Transform> Objs;
// in your function put this (when changing the color)
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red;
}
Similarly to 2, if you want to do it all in code you can do
//Outside function
public List<Transform> Objs;
//inside function
Objs.Add(GameObject.Find("FirstObject").transform);
Objs.Add(GameObject.Find("SecondObject").transform);
//... keep doing this
//now do the same foreach loop as in 2
You could search by tag (if you have a lot of objects) (i would imagine that this would take a bit longer just because it is going through each component but I have no evidence to back that up)
//Outside function
public GameObject[] Objs;
//inside function
Objs = GameObject.FindGameObjectsWithTag("ATagToChangeColor");
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red();
}
And about this comment:
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
If you make them of type GameObject then you should easily be able to access the renderer simply by doing this:
public GameObject cube;
public void Start(){
cube.GetComponent<Renderer>().material.color = Color.red();
}
And making the variables public allows unity's inspector to see the variables so that you can change them in the unity editor without having to open the script.

Categories