How to replace element in MeshRenderer.sharedMaterials - c#

We are using a object from the asset store that makes usage's of a mesh renderer with 32 materials in it. We need to implement functionality that makes it possible to replace 1 single material in the list, and keep the rest.
I have tried several things, but I keep struggeling with the materials list, that is why I decided to ask you guys for help.
public Material TestMaterial;
void Update()
{
for (int i = 0; i < meshRenderer.sharedMaterials.Length; i++)
{
if (meshRenderer.sharedMaterials[i].name == "replaceableMat")
{
// Replace with TestMaterial
}
}
}
The above code is how I kinda want to use it.
The TestMaterial object is not null, it is selected from the Unity Editor, so that is fine.
Could someone give me some insight?
Thanks in forward!

from Renderer.sharedMaterials:
Note that like all arrays returned by Unity, this returns a copy of materials array. If you want to change some materials in it, get the value, change an entry and set materials back.
You have to do exactly that. Store the materials in a temporary variable, change entries and write the entire array back to sharedMaterials when done:
var materials = meshRenderer.sharedMaterials;
for(int i = 0; i < materials.Length; i++)
{
if(!string.Equals(materials[i].name, "replaceableMat") continue;
materials[i] = TestMaterial;
}
meshRenderer.sharedMaterials = materials;
The reason in the background is that sharedMaterials is not a field but a property.
Since the assignment of sharedMaterial[i] is not an assignment to the whole property what it does is just replacing that entry in a temporary array but not actually assigning it back to the Renderer component.
Only by assigning a value to the entire property actually makes the property execute it's setter and writes the array back to the Renderer component.

This should work.
void Update()
{
for (int i = 0; i < meshRenderer.sharedMaterials.Length; i++)
{
if (meshRenderer.sharedMaterials[i].name == "replaceableMat")
{
// Replace with TestMaterial
renderer.sharedMaterials[i] = TestMaterial
}
}
}

Related

Passing the cycle from the beginning to the end (Or vice versa)

Good afternoon guys, such a question, I'm doing an array with brute force. I can't understand why assigning a value from the last element of the FrezeTree array to the BufferObject works in the Start() method, but this does not happen in Update.
So.
At the start of the scene, there is a search for all objects with the Tree tag (This is a temporary and debag solution):
FrezeTree = GameObject.FindGameObjectsWithTag("Tree");
Then, this list is passed to the array:
public GameObject[] FrezeTree;
Next, I'm trying to pass one element from the array to another object (This happens in the Start() method):
foreach (GameObject gm in FrezeTree)
{
BufferObject = gm;
}
But it turns out that only the last element is transmitted (Because of My own crookedness, since I don't know how to fix it yet). Why do I need a Buffer Object? From it I get the X position of the object, which I use for other purposes. The idea is to transfer one element from the array to the object, and when the object ceases to exist (Gets the null status), it goes to the element above / below (No matter in what order). Yes, I know that in what I have given above and I do not feel that I am trying to make a transition or skip an element from the array. I found various solutions on the great Internet, but the result was always the same, gets the last element and does not choose another one. That's why I turned here.
As I understand you want to
set one closest target object
wait until it is destroyed
go to the next closest target object
You could do this by simply checking whether the BufferObject is already set and still alive like e.g.
using System.Linq;
...
public GameObject BufferObject;
private void Update ()
{
// This means BufferObject is either not assigned yet or was destroyed
if(!BufferObject)
{
BufferObject = FindClosestTarget();
}
}
private GameObject FindClosestTarget ()
{
// Get all currently existing trees
// since this happens only once in a while it should be okey to repeat this call
return GameObject.FindGameObjectsWithTag("Tree")
// order them by distance
.OrderBy(gm => (transform.position - gm.transform.position).sqrMagnitude)
// get the first one or null if there was none
.FirstOrDefault();
}
This way it would also take trees that are spawned later into account.
If you rather want to cache this array only once you can still do this like
private GameObject [] trees;
public GameObject BufferObject;
private void Start ()
{
trees = GameObject.FindGameObjectsWithTag("Tree");
}
private void Update ()
{
// This means BufferObject is either not assigned yet or was destroyed
if(!BufferObject)
{
BufferObject = FindClosestTarget();
}
}
private GameObject FindClosestTarget ()
{
// First filter out any null or destroyed objects
return trees.Where(t => t)
// Then order the rest by distance
.OrderBy(gm => (transform.position - gm.transform.position).sqrMagnitude)
// Then pick the first one or null if there isn't any
.FirstOrDefault();
}
See
Linq Where
Linq OrderBy
Linq FirstOrDefault
implicit UnityEngine.Object.bool operator
In general, I figured out how to implement everything myself. So, the code:
public GameObject[] FrezeTree; //Array of all objects in the scene
private void Start()
{
FrezeTree = GameObject.FindGameObjectsWithTag("Tree"); //Finding all objects with a tag (I do not recommend using tags due to the lack of code navigation)
}
private GameObject FindClosestTarget()
{
return False True.Where(t => t).FirstOrDefault(); //Assigning and sorting an array with objects from the scene, thanks #derHugo
}
void Update()
{
BufferObject = FindClosestTarget(); //will be Assigned the next element of the array, in case the current will become "null"
if (BufferObject == null)
{
Debug.Log("debug for (BufferObject == null)"); //arbitrary code in the case that any object on the stage left.
}
}
P.S. This code is perfect for creating a simple AI for NPCs in your game. It's not perfect, but at least something is better than nothing :) Thanks #derHugo

how to remove a specific game object (that is in an array) from a list?

my goal with the code is to have multiple skulls appear on screen for comparison. Depending on number of skulls on screen (1-6) they change position and size, Ive done this through the use of an array of gameObjects (the skulls) and a list that can keep track of which skull is in which "position".
The code works if you are adding, but runs into problems when you remove the object. It "works" on RemoveAt only if you remove the GameObjects in the same order that they were added. IE: The list is always removing element 0(first item in list) rather than the specific gameObject that is instigating the RemoveAt function.
While I have found many answers on how to remove specific individual gameObjects, They dont work since my gameObjects are in an array- I cant simply say remove gameObject name. Which leads me to my question of how to remove a specific gameObject[] from a list?
either remove or removeAll run these two errors:
error CS1502: The best overloaded method match for System.Collections.Generic.List<UnityEngine.GameObject>.RemoveAll(System.Predicate<UnityEngine.GameObject>)' has some invalid arguments.
Error CS1503: Argument#1' cannot convert int' expression to typeSystem.Predicate'
Here are my two scripts for all this:
Script on each bird skull named birdController:
public int myBirdID;
public int orderInList;
//bool to keep track of object active or not
public bool whoAmIReally;
//potentially redundant; allows game object to see if hes on or not
public GameObject IAmMe;
// Use this for initialization
void Start () {
//fixingIntOrder();
}
public void OnEnable(){
//when object is on check bool
whoAmIReally = true;
Debug.Log("IM ON");
}
public void OnDisable(){
//when object is turned off uncheck bool, change list value to 0
//**this wont change actual list order or value in cubeplacement script- my hope is that it might have- likely missing a step somewhere
whoAmIReally = false;
orderInList = 0;
Debug.Log("IMOFF");
}
Script on empty with all of my array and list info named cubePlacement:
public int numberSkullOnScreen;
public bool[] skullOn;
public GameObject[] allSkulls;
public GameObject listHolder;
public List<GameObject> birdsOnScreen = new List<GameObject>();
//declaring the script to avoid NullReferenceEcxeption where I reference it
public birdController _birdController;
// Use this for initialization
void Start () {
//only way I can get my list to work: have an object act as a placeholder onStart
birdsOnScreen.Add(listHolder);
}
//turning on and off the skulls
public void toggleBirdSkull(int mySkull){
if (skullOn[mySkull] == false){
//set specific gameObject active
allSkulls[mySkull].SetActive(true);
//make skull on bool true
skullOn[mySkull] = true;
//add one to the number on screen
numberSkullOnScreen++;
//reference int orderInList from Bird controller script
allSkulls[mySkull].gameObject.GetComponent<birdController>().orderInList = numberSkullOnScreen;
//add skull to list when turned on THIS WORKS YAY
birdsOnScreen.Add(allSkulls[mySkull]);
//Run function to place skulls
placementSkulls();
}
else{
allSkulls[mySkull].SetActive(false);
skullOn[mySkull] = false;
numberSkullOnScreen--;
//remove skull from list when turned off-- THIS DOESNT WORK...
birdsOnScreen.RemoveAt(allSkulls[mySkull].gameObject.GetComponent<birdController>().orderInList);
//Run function to place skulls based on the int number skulls on screen
placementSkulls();
}
}
Have you tried to remove objects using Remove method?
Like this:
birdsOnScreen.Remove (allSkulls[mySkull]);
Your birdsOnScreen.RemoveAt version also looks correct.
If you want to use RemoveAll method, you should pass predicate, which returns bool, eg:
birdsOnScreen.RemoveAll (bird => { return bird == allSkulls[mySkull]; } );
I would suggest to use Remove in your case, since you know the object you're removing.
Read more here: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.remove?view=netframework-4.7.2
Also, try to increase your numberSkullOnScreen after you assign its value to the object's property:
allSkulls[mySkull].gameObject.GetComponent<birdController>().orderInList = numberSkullOnScreen;
numberSkullOnScreen++;
This way you won't need to use a placeholder anymore, because your indices will be correct.
I guess you're looking for a similar function like in an List. You could use this:
public static void RemoveAt<T>(ref T[] arr, int index) {
for (int a = index; a < arr.Length - 1; a++)
{
arr[a] = arr[a + 1];
}
Array.Resize(ref arr, arr.Length - 1);
}
or if you know the Object:
public static T[] RemoveObjectArray<T> (this T[] arr, T ObjToRemove) {
int numIdx = System.Array.IndexOf(arr, ObjToRemove);
if (numIdx == -1) return arr;
List<T> tmp = new List<T>(arr);
tmp.RemoveAt(numIdx);
return tmp.ToArray();
}

Unity C# allows for embedded arrays? [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 4 years ago.
I was learning how to use Json to save data into files when a continuous error was appearing, its "Nullreference, object reference not set to an instance of an object" and I started to think that I was doing the save file part right but the error was in my usage of arrays somehow, so I started a new project and I started using arrays the same way without any usage of Json, and indeed the error was there, so I know there is something wrong with this but I cant really tell what, I know how simple arrays are initialized and how every array has to be initialized before being used, but in this case something is wrong for some reason, can you tell me what is that?
The scenario is really simple as its new project only to test the arrays the way I was using them in my bigger project, so its mainly a GameClass that hold several hi-scores tables (an array of them), these tables are made by a class (HiScoreClass) that holds arrays of names and points, and then well I was just initializing this and the error appeared, so no need to dig further, can you tell me what is wrong here? thanks a lot for your help, Im totally lost in this point. (To test this in a project I just attached the code to the camera so its nothing else in scene to think other object could be causing it)
using UnityEngine;
using System.Collections;
public class embed : MonoBehaviour
{
GameClass myGame;
int i, j;
void Awake ()
{
myGame = new GameClass();
myGame.arrptnm = new HiScoreClass[10];
for (i = 0; i < 100; i++)
{
myGame.arrptnm[i].pts = new int[10];
myGame.arrptnm[i].names = new string[10];
for (j = 0; j < 10; j++)
{
myGame.arrptnm[i].pts[j] = i * j;
myGame.arrptnm[i].names[j] = "ASD";
}
}
}
void Update ()
{
}
}
[System.Serializable]
class GameClass
{
[SerializeField]
public HiScoreClass[] arrptnm;
}
[System.Serializable]
class HiScoreClass
{
[SerializeField]
public int[] pts;
public string[] names;
}
The problem is that you're defining an array of HiScoreClass objects, but you're not initializing the elements in that array to new instances of the HiScoreClass class. Then you get a NullReferenceException when you try to reference a property of an item in the array, like myGame.arrptnm[i].pts.
To solve this, you can initialize each item to a new instance when you do your first iteration (also change the 100 to 10, since that's the size we declared for this array):
for (int i = 0; i < 10; i++)
{
// Initialize our array items
myGame.arrptnm[i] = new HiScoreClass();
// rest of code omitted...

How to make an interactive objects using PhotonNetwork?

On the stage, I create several objects using PhotonNetwork.Instatiate ();
On these objects there is a script that changes the transform of the object. In the same script, a random variable is selected from the list, depending on which the transform is changing.
Now the question. How do I make this object change its transform simultaneously and equally for all players?
I'm thinking of doing something like this: make a separate script that will select a random number -> in it, write something like this
if (view.isMaster) // ???????
for (int i = 0; ......) {
objectInList [i] .myFloatValue = randomValueInArray;
objectInList [i] .canToChange = true;
}
And in the script of a mutable object, write something like:
if (canToChange)
// change the position;

Add TextBox.Text to a list using a for loop

I am trying to take the values in the textboxes, named sequentially from 0-9, and add that to a List using a for loop. I am having problems with the syntax or something.
here is what I have now.
for (int i = 0; i <= amt.Count(); i++)
{
amt[i] = int.Parse(amtBox[i].Text);
}
The error is that amtBox doesnt exist in the current context.
My problem is within the loop where i have amtBox[i].Text. I have tried this several ways and VS always throws an error. I have tried "amtBox" + i and that compiles but then causes an error when I try to do something with it and says "data is of wrong type".
I am new to C# and come from PHP so maybe that is why I think this approach will work. PHP doesnt care about data types where C# really does. I have done this exact thing in PHP many times without any issue.
Any suggestions on another way to do this are appreciated as I am probably coming at this all wrong.
Thanks
One solution would be to declare an array and assign amtBox'es to the individual indexes in the array and then you can iterate on that array.
var amtBoxes = new TextBox[] { amtBox0, amtBox1, .... };
for (int i = 0; i <= amt.Count(); i++)
{
amt[i] = int.Parse(amtBoxes[i].Text);
}
If you end up needing to iterate on your TextBox controls in other places I would consider making the array an instance member of your object.
I suppose that your textbox are named "amtBox" + a number.
(The Name property is "amtBox1" as an example)
In this case you could use
Control[] t = Controls.Find("amtBox" + i, false);
for a code like this
for (int i = 0; i <= amt.Count(); i++)
{
Control[] t = Controls.Find("amtBox" + i, false);
if(t != null && t.Length > 0)
{
amt[i] = int.Parse(t[0].Text);
}
}
My understanding is that you have text boxes named amtBox1, amtBox2, etc., and what you are trying to do is sequence through them. As you point out, this is very easy in PHP. It is possible to do what you're suggesting using reflection, but that is expensive and, in any event, there's probably a better way to do what you're looking for.
You could put all of your amount boxes into an array, and then what you have would work:
var amtBoxes = new[] {
amtBox1,
amtBox2,
amtBox3
}

Categories