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

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

Related

Is it possible to override the texture component values even though the variable is set to public?

I have a texture issue with a VR whiteboard. When I attach a texture to a plane in Unity that has the whiteboard.cs script attached, the whiteboard plane defaults to plain white when I press run. The plane is still reactive to the marker in a VR space, but I want to be able to put up a stencil texture/material that allows users to trace it. I've seen the similar issue when the variables are private and not when set to public. Has anyone experienced the same texture GetComponent issue? I'm using Unity 2021.2,10f.
Attached is a screenshot of the object before and after pressing run and the mentioned script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Whiteboard : MonoBehaviour
{
public Texture2D texture;
public Vector2 textureSize = new Vector2(x:2048, y:2048);
void Start()
{
var r = GetComponent<Renderer>();
texture = new Texture2D(width:(int)textureSize.x,
height:(int)textureSize.y);
r.material.mainTexture = texture;
}
}

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.

How to "Copy World Placement" in script?

I am using Unity 2020.1.15f1.
First of all if you don't know you can right click on unity editor on a transform or rect transform select "Copy World Placement" at the bottom and get this info.
UnityEditor.TransformWorldPlacementJSON:{"position":{"x":-17.771259307861329,"y":-9.999999046325684,"z":90.0},"rotation":{"x":0.0,"y":0.0,"z":0.0,"w":1.0},"scale":{"x":1.0,"y":1.0,"z":1.0}}
This is exactly what i need in script but could not find a way to get this info from rect transform components. It does not have to be in JSON format all i need is THE SAME x and y values.
PS: transform.position is not what i want. It does not give these values for a rect transform.
Edit:The reason I could not get these values in script from position was the project was taking these values in void Awake() method which changes position values you get if you call it in void Start()
So i guess derHugos first answer is correct for this question.
For me (Unity 2020.2.5f1) this simply returns the transform.position, transform.rotation and transform.localScale regardless of whether it is a RectTransform or just a Transform.
Note though that a RectTransform in a Canvas of type Screenspace - Overlay is in pixel space!
I don't know how it works internally but basically you could do something like
public static class TransformExtensions
{
[Serializable]
private struct TransformData
{
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
public TransformData(Transform transform)
{
position = transform.position;
rotation = transform.rotation;
scale = transform.localScale;
}
}
public static string CopyWorldPlacementJson(this Transform transform, bool humanReadable = false)
{
var data = new TransformData(transform);
var json = JsonUtility.ToJson(data, humanReadable);
return json;
}
}
With that extension method anywhere in your project you can simply do
var json = someGameObject.transform.CopyWorldPlacementJson();
Debug.Log(json);
In particular to UI stuff you might have to wait for the next re-draw of the rects. You could force this using Canvas.ForceUpdateCanvases
Force all canvases to update their content.
A canvas performs its layout and content generation calculations at the end of a frame, just before rendering, in order to ensure that it's based on all the latest changes that may have happened during that frame. This means that in the Start callback and the first Update callback, the layout and content under the canvas may not be up-to-date.
Code that relies on up-to-date layout or content can call this method to ensure it before executing code that relies on it.
The reason I could not get these values in script from position was the project was taking these values in void Awake() method which changes position values you get if you call it in void Start()
So i guess derHugos first answer is correct for this question.

How to change the "BaseMap" property of a shader from script? - Universal RP Template

I used to change the BaseMap texture using the standard 3D project in unity as follows:
[SerializeField] private Texture texture;
void Start()
{
this.GetComponent<Renderer>().material.mainTexture = texture;
}
However, the same code doesn't seem to work using the Universal RP Template. When I checked the shader properties, it shows that the BaseMap property is obsolete as shown in the picture below:
But when I drag and drop the texture at run time to the BaseMap, it works. I guess that means that it should work. (Below Pic):
My MAIN question now is, how do I change the BaseMap texture from script in Universal RP Template?
and is there other properties I should call and change their texture other than the BaseMap since it's written on the shader that the BaseMap is "ObsoletProperties"?
Update, this should do the trick:
[SerializeField] private Texture texture;
void Start()
{
GetComponent<Renderer>().material.SetTexture("_BaseMap", texture);
}
Try this;
Renderer m_Renderer;
public Texture m_MainTexture;
void Start () {
m_Renderer = GetComponent<Renderer> ();
m_Renderer.material.SetTexture("_MainTex", m_MainTexture);
}

Mouse events for Unity3D isometric tile map

I have been reading up on the new tile map system in Unity3D. I have managed to get to the point of setting up a grid -> tile-map and setting up a tile palette. However now i'm struggling with finding up-to-date tutorials for handling mouse events for this new tile map system.
I'm attempting to set a highlight when the mouse is over the tile and if the tile is clicked I want to be able to trigger scripts and other events. However the available tutorials online don't go into mouse events for the tile map system and very few talk about isometric tile maps.
Are there any good up to date tutorials for handling mouse events on an isometric tile map? Even a simple tutorial showing a hover effect on the tile and a "hello world from tile x.y" when tile is clicked, would be all i would really need to get going.
This is what I have so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseManager : MonoBehaviour
{
void Update()
{
Vector3 clickPosition = Vector3.one;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
clickPosition = hit.point;
}
Debug.Log(clickPosition);
}
}
This should get you started:
using UnityEngine;
using UnityEngine.Tilemaps;
public class Test : MonoBehaviour {
//You need references to to the Grid and the Tilemap
Tilemap tm;
Grid gd;
void Start() {
//This is probably not the best way to get references to
//these objects but it works for this example
gd = FindObjectOfType<Grid>();
tm = FindObjectOfType<Tilemap>();
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int posInt = gd.LocalToCell(pos);
//Shows the cell reference for the grid
Debug.Log(posInt);
// Shows the name of the tile at the specified coordinates
Debug.Log(tm.GetTile(posInt).name);
}
}
}
In short, get a reference to the grid and tilemap. Find the local coordinates using ScreenToWorldPoint(Input.mousePosition). Call the LocalToCell method of the grid object to get your local coordinates (Vector3) converted to cell coordinates (Vector3Int). Pass the cell coordinates to the GetTile method of the Tilemap object to get the Tile (then use the methods associated with the Tile class to make whatever changes you want to make).
In this example, I just attached the above script to an empty GameObject in the world. It would probably make sense to attach it to the Grid, instead. The general logic remains the same nonetheless.
This is a slightly different version from the way HumanWrites does it. It doesn't require a reference to the grid, and the mousePos is declared as a Vector2, rather than a Vector3 - this will avoid problems when working in 2D.
using UnityEngine;
using UnityEngine.Tilemaps;
public class MouseManager : MonoBehaviour
{
private Tilemap tilemap;
void Start()
{
tilemap = FindObjectOfType<Tilemap>();
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int gridPos = tilemap.WorldToCell(mousePos);
if (tilemap.HasTile(gridPos))
Debug.Log("Hello World from " + gridPos);
}
}
}
The 'tilemap' that we're referencing is a gameObject in your scene. You may have renamed it to something else, but it would be a child of the 'Grid' object.

Categories