Unity Changing Material Tiling per Gameobject - c#

I've written a script that is supposed to make tiling materials easier. I was tired of constantly re-adjusting the tiling settings of materials on my walls as I resized them.
[ExecuteInEditMode]
public class AutoTiler : MonoBehaviour
{
public float xScaleMult = 1;
public float yScaleMult = 1;
private Material texture;
// Update is called once per frame
void Update()
{
if (!texture)
{
texture = GetComponent<MeshRenderer>().material; //get the material if it is null
}
//set the material scale based on the size of the object
texture.mainTextureScale = new Vector2(Mathf.Max(transform.localScale.x, transform.localScale.z) * xScaleMult, transform.localScale.y * yScaleMult);
}
}
However, every time I reload the scene, I get an error message saying:
Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.
I've looked up this error but I can't quite figure out how to solve it. I understand that I'm copying the material in order to change it, but that is the desired result, as it needs different tiling settings for every wall, so that it does not appear stretched. Currently the code works, but fills the console with this error message. Is there a better way to do this?

in editor you can use sharedmaterial instead material property, something like this:
using UnityEngine;
[ExecuteInEditMode]
public class AutoTiler : MonoBehaviour
{
public float xScaleMult = 1;
public float yScaleMult = 1;
private Material material;
private Material Material
{
get
{
if (material == null)
{
MeshRenderer render = GetComponent<MeshRenderer>();
#if UNITY_EDITOR
material = render.sharedMaterial;
#else
material = render.material;
#endif
}
return material;
}
}
// Update is called once per frame
void Update()
{
//set the material scale based on the size of the object
Material.mainTextureScale = new Vector2(Mathf.Max(transform.localScale.x, transform.localScale.z) * xScaleMult, transform.localScale.y * yScaleMult);
}
}
for more details about difference between material and sharedmaterial see :
http://gyanendushekhar.com/2018/09/23/difference-material-shared-material-unity-tutorial/
https://docs.unity3d.com/ScriptReference/Renderer-sharedMaterial.html

Related

Unity3d - Trying to change color of Material via C#

I've looked all over for a solution for this and it seems simple enough but I don't understand why I can't change the property of one of the materials located on one of my NPC models.
To put it simple - I am just trying to change the "_SkinColor" property of what I believe is the material shader? I guess because using the code shown will change the skin color to black?
Thank you so much for your help!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class colorchanger : MonoBehaviour
{
[SerializeField] private Material myMaterial;
// [SerializeField]
private float red = Random.Range(180, 231);
private float green = Random.Range(200, 255);
private float blue = Random.Range(50, 100);
// Start is called before the first frame update
void Start()
{
Color newColor = new Color(red,green,blue);
myMaterial.SetColor("_SkinColor",newColor);
}
// Update is called once per frame
void Update()
{
}
}
You will need to set the color of the material via its Renderer component.
If the script is attached to the GameObject that has the renderer which is handling your material try:
void Start()
{
Renderer myRenderer = GetComponent<Renderer>();
Color newColor = new Color(red,green,blue);
myRenderer.material.color = newColor;
}
If the script is intended to change the colour of whatever material is passed in to it, you need to pass in the relevant Renderer rather than the material. For example:
public class MaterialColourChanger: MonoBehaviour {
public void ChangeMaterialColour(Renderer renderer, Color colour) {
renderer.material.color = colour;
}
}
The call it from any other script:
public class AnyOtherScript: MonoBehaviour {
MaterialColourChanger colourChanger;
void Start() {
colourChanger = GameObject.Find("TheColourChangerGameObject").GetComponent<MaterialColourChanger>();
// Get the renderer component for the object that this script is attached to...
Renderer thisObjectsRenderer = GetComponent<Renderer>();
Color myColour = Color.red;
// Pass it all to the colour changer script to change the colour
colourChanger.ChangeMaterialColour(thisObjectsRenderer , myColour);
}
}
If you are sure of the correct name of the property shader, it's because random numbers are float's between 0, 1, not up to 255. fix it:
[SerializeField] private Material myMaterial;
void Start()
{
var red = Random.Range(180, 231)/255f;
var green = Random.Range(200, 255)/255f;
var blue = Random.Range(50, 100)/255f;
var newColor = new Color(red,green,blue);
myMaterial.SetColor("_SkinColor",newColor);
}

How do I get the texture to scroll on my sprite?

I have a large sprite on my screen but I want the image it displays to scroll infinitely horizontally.
I have the current code which does not have any effect at all.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjLayer : MonoBehaviour
{
public float Speed = 0;
public int layer = 0;
protected Material _material;
protected float currentscroll = 0f;
// Start is called before the first frame update
void Start()
{
_material = GetComponent<SpriteRenderer>().material;
}
// Update is called once per frame
void Update()
{
currentscroll += Speed * Time.deltaTime;
var currentOffset=_material.GetTextureOffset("Layer3");
_material.SetTextureOffset("Layer3", new Vector2(currentscroll, 0));
_material.mainTextureOffset = new Vector2(currentscroll, 0);
}
}
Just to note that I am setting both SetTextureOffset and mainTextureOffset as neither seem to be working.
Also currentOffset is changing as expected but the texture is not moving on the screen.
You are most likely using a Material that doesn't support texture offsetting, unless the property is HiddenInInspector you can check in the inspector if your material supports it by checking if it has the Offset x,y input fields underneath "Texture"
The standard Unlit/Texture shader has this property, so do any newly created UnlitShaders (as seen in my example screenshot).
If you are using a custom shader then your Texture isn't named "_MainTex", which is the property that Unity looks for when using mainTextureOffset, as cited from the docs:
By default, Unity considers a texture with the property name name "_MainTex" to be the main texture.
Using material.mainTextureOffset works fine for me when using a shader where the Texture is called _MainTex:
public Vector2 offset;
private Material material;
private void Start()
{
material = GetComponent<MeshRenderer>().material;
}
private void Update()
{
material.mainTextureOffset = offset;
}
Result (Gyazo gif)
In your example
_material.SetTextureOffset("Layer3", new Vector2(currentscroll, 0));
You are looking for a property named "Layer3" in your shader, this is not a name that is standard in use by Unity (in my knowledge), if you are using a custom shader then make sure your Texture property has the name Layer3 inside your shader.
Update after OP's comment:
Tiling and Offsetting is not available to Materials on a SpriteRenderer, Sprites are assumed to not be offset. As indicted by the warning given by Unity when you try to add a material that has Tiling/Ofsetting in its texture
Material texture property _MainTex has offset/scale set. it is incompatible with spriterenderer
Instead use a Quad, Plane, Image or raw Image component which does have support for materials with tiling/offsetting in combination with the above code.
I think technically it is still possible to use offsetting with sprites by throwing on a shader that support it and ignoring the warning, but I can not guarantee this won't break down the line or what the performance implications are

Change the color of an instantiated prefab Unity

so the player hits a button to create a box. I need this box to be randomly varying between a few colors. I also need this box to have a tag corresponding to said color. Green box - "greenBlock" tag etc.
I have instantiated the box and then try to change it's material with material.color. It doesn't do anything. I've seen suggestions of sharedMaterial but having tried that found it just ends up changing the color of every game object in the scene. I think I'm fetching the box prefabs renderer correctly? Any help would be appreciated!
Here's what I have so far:
public class ButtonScript : MonoBehaviour
{
public GameObject Box;
public Transform spawnpoint1;
public Transform spawnpoint2;
public Rigidbody2D player;
public Renderer boxRenderer;
[SerializeField]
private Color boxColor;
[SerializeField]
private AudioSource actionSound;
// Update is called once per frame
private void Start()
{
//boxRenderer = Box.gameObject.GetComponent<Renderer>();
boxRenderer = GameObject.Find("Box").GetComponent<Renderer>(); // Find the renderer of the box prefab
}
public void OnTriggerStay2D(Collider2D col)
{
if (player) // If it's the player in the collider trigger
{
if (Input.GetKeyDown(KeyCode.E))
{
Instantiate(Box, spawnpoint1.position, Quaternion.identity);
boxRenderer.material.color = boxColor; // change the color after it is instantiated
actionSound.Play();
}
}
}
}
boxRenderer.material.SetColor("_Color", boxColor);
or
boxRenderer.material.color = new Color(0.5, 0.5, 0.0, 1.0);
And when you instantiate the box, you need to get the render for the box at this point as it is a new object. So:
if (Input.GetKeyDown(KeyCode.E))
{
Box = Instantiate(Box, spawnpoint1.position, Quaternion.identity);
boxRenderer = Box.transform.GetComponent<Renderer>();
boxRenderer.material.color = boxColor; // change the color after it is instantiated
actionSound.Play();
}
Note you have creating a New color and assigning it, you can't modify the color there as it may be used on other objects that are using the same material.
Check out SetColor in the docs, that is setting the shader property called _Color which is the default color element in shaders, you can of course have more depending on the shader.

Create material from code and assign it to an object

I am very new to Unity3d. I have a prefab which contains 6 quads making it a cube. I want to add image textures to different faces of cube.
I am getting images from a web service, so I have to add or change material in the script.
The problem I am facing is, I am not able to find material property in gameObject.
I have tried below code:
using UnityEngine;
using System.Collections;
public class shelfRuntime : MonoBehaviour {
public GameObject bottle;
public GameObject newBottle;
// Use this for initialization
void Start () {
iterateChildren(newBottle.transform.root);
GameObject rocketClone = (GameObject)Instantiate(bottle, bottle.transform.position, bottle.transform.rotation);
rocketClone.transform.localScale += new Vector3(1, 1, 1);
GameObject newBottleInMaking = (GameObject)Instantiate(newBottle, newBottle.transform.position, newBottle.transform.rotation);
Transform[] allChildren = newBottleInMaking.GetComponentsInChildren<Transform>();
foreach (Transform child in allChildren)
{
// do whatever with child transform here
}
}
void iterateChildren(Transform trans)
{
Debug.Log(trans.name);
if (trans.name == "Back") {
var ting = trans.gameObject.GetComponent<Renderer>();
// trans.renderer.material // there is no material property here
}
// Do whatever logic you want on child objects here
if (trans.childCount == 0) return;
foreach (Transform tran in trans)
{
iterateChildren(tran);
}
}
// Update is called once per frame
void Update () {
}
}
How to set material to quads? There are 6 quads inside my prefab.
You can no longer access some components directly in Unity. You must use GetComponent to get the component(Renderer) then access the material from it.
trans.renderer.material = ....
should be changed to
trans.GetComponent<Renderer>().material = yourNewMaterial;
Finally, when Cube or Quad is created in Unity, MeshRenderer is automtatically attached to them not Renderer. So, you might get run-time error with GetComponent<Renderer>(). Use MeshRenderer instead.
trans.GetComponent<MeshRenderer>().material = yourNewMaterial;
To create Material during run-time:
Material myNewMaterial = new Material(Shader.Find("Standard"));
The example below will create a Material, assign standard shader to it then change the texture to the texture from the myTexture variable before applying it to a GameObject.
public Texture myTexture;
void Start()
{
//Find the Standard Shader
Material myNewMaterial = new Material(Shader.Find("Standard"));
//Set Texture on the material
myNewMaterial.SetTexture("_MainTex", myTexture);
//Apply to GameObject
trans.GetComponent<MeshRenderer>().material = myNewMaterial;
}

OnGui elements interacting with other OnGui elements

Within my 2d game i wsih to have a number of OnGui elements there for the user to select, however, the cursor that im using is another ongui element(using kinect to navigate) is this possible by any chance, at the moment im using planes but i will be zooming in and out of the camera so ill essentially need them attatched to the screen. Any ideas, suggestions or workarounds.
THis is currently my cursor.
using UnityEngine;
using System;
using System.Collections;
public class PillarAgent : MonoBehaviour {
public SkeletonWrapper sw;
public Vector3 distance;
public float progress =0f;
public Texture2D cursor;
public Texture2D load;
public Camera mainCam;
public float startTime;
private int roundedRestSecounds;
// Use this for initialization
float differencex = 0;
float differencey = 0;
void Start () {
distance =new Vector3(0f,0f,0f);
}
float translate(float value, float leftMin, float leftMax,
float rightMin,float rightMax)
{
float leftSpan = leftMax - leftMin;
float rightSpan= rightMax - rightMin;
float valueScaled = (value-leftMin)/(leftSpan);
return rightMin+(valueScaled * rightSpan);
}
// Update is called once per frame
void Update () {
if (sw.pollSkeleton())
{
distance.x=sw.bonePos[0,0].x - sw.bonePos[0,7].x;//5 is left shoulder
distance.y=sw.bonePos[0,0].y -sw.bonePos[0,7].y;
differencex=translate(distance.x,.6f,0,0,Screen.width);
differencey=translate(distance.y,-.5f,0,0,Screen.height);
//Debug.Log();
float width = sw.bonePos[0,5].x+ sw.bonePos[0,9].x;
float height =sw.bonePos[0,4].y- sw.bonePos[0,0].y;
float heightdiv= (height/2)+sw.bonePos[0,0].y;
}
}
void OnGUI() {
//left top width height
Rect r = new Rect(differencex,differencey,80,50);
GUI.Label(r,cursor);
GUI.BeginGroup(new Rect(differencex,differencey+50,50*Mathf.Clamp01(progress),15));
//Debug.Log(progress);
GUI.DrawTexture(new Rect(0,0,50,50),load);
GUI.EndGroup();
transform.position =mainCam.ScreenToWorldPoint(new Vector3(differencex,Screen.height-differencey,50));
//mainCam.fieldOfView()
}
void OnCollisionStay(Collision Other)
{
startTime+=Time.deltaTime;
if(Other.gameObject.GetComponent(typeof(TextControl)))
{
roundedRestSecounds=Mathf.CeilToInt(Time.time);
progress = Time.time *0.2f;
CurrentState=true;
}
else if(Other.gameObject.tag==("Scalpal")){
progress = startTime *0.5f;
//scallpall activated
//
}
}
void OnCollisionExit(Collision Other){
startTime =0f;
progress =0f;
}
public Boolean CurrentState{get;set;}
}
The next class is essentially the class in which i pick up my tools, currently this code doesnt work(not sure why), but what i wish to do is select some tools which show up on the screen so that i can use them,for e.g pick up paint brush start painting bricks or what not. at the moment i have my tools on a plane, i wish to always have them on the screen at all times when the camera moves.
using UnityEngine;
using System.Collections;
public class SelectTool : MonoBehaviour {
public Tools tools;
public float startTime;
public bool ScalpalSelected;
public GameObject selectedTool;
void Start()
{
tools = this.GetComponent<Tools>(); //in order to use this tools muyst be attached to the game object
//this is essentially saying with regards to this game object get the component named tools
}
void update()
{
}
void OnCollisionStay(Collision Other)
{
startTime +=Time.deltaTime;
if(startTime >5f){
if(Other.collider.tag==("Scalpal"))
{
selectedTool = Other.collider.gameObject;
Debug.Log(selectedTool+" What in gods good name is:" +tools.utilities[0]);
}
else {
selectedTool=null;
}
if(selectedTool){
for(int i=0;i<tools.utilities.Length;i++)
{
}
}
ScalpalSelected=true;
renderer.material.color = Color.yellow;
}
}
void OncollisionStay(Collision other){
startTime = 0f;
}
}
From the comment section to the question I will assume that you want to know how to do this:
"... you want your plane objects to move together with the camera ..." - Steven Mills
"thank you #StevenMills Do you have any example of how this can be done?" j bel
While the answer provided in the comments is to just manually add the planes as children of the camera (a very straightforward, manual approach), I will give another way to do this through scripting (in light of maybe this will help someone else out disregarding the likeliness of someone using this solution).
The idea of this is to create a script (thus following being attached to the MainCamera) that will search through all of the GameObject in the Object Hierarchy with the method GameObject.FindGameObjectsWithTag. Once we have all our GameObject with the associated Tag, we can then loop through the array and parent the attached script's GameObject to each.
public class ParentGameObjects : MonoBehaviour {
//The tag to search for on all game objects in the hierarchy
public String objectTag;
//The game objects that we will parent the main camera to
private GameObject[] children;
//It's not necessary to store references to the children but if you want to modify them at some point you will be able to
void Start() {
//Find all game objects with the tag we want
children = GameObject.FindGameObjectsWithTag(objectTag);
//Loop through all of the game objects found and parent this object's transform
for(int i = 0; i < children.Length; i++) {
children[i].transform.parent = transform;
}
}
}
Now, there are a few things you have to do for this script to work:
Attach this script to any GameObject that you want to parent other objects to.
In the Inspector of the GameObject that the script is attached to, enter in the name of the Tag you want to use.
For all GameObject(s) in the Hierarchy that should be added as children, assign the same Tag.
Of course there are other things you can do like, for example, instead of only searching for one Tag be able to search for multiple but that requires a bit (not exactly much) more work. Nonetheless, I hope this will at least be useful information to someone on how parenting works via scripting.

Categories