TextMeshPro Text colors reset when moved - unity3d

I've been struggling with this issue for a while now-
Essentially I have a TMP_Text object. Then (like in TMP example 23), I am setting the character's vertices colors to a specified color. However, when I move the TMP_Text object, all of the character's colors get reset.
Here is the relevant code that sets character color as well as a video displaying this behavior:
private void ChangeColor(int i, Color32 c)
//changes color of the letter that is # specific index to a color32
//this is getting called elsewhere in the script
{
int materialIndex = tf_.characterInfo[i].materialReferenceIndex;//get characters material index
cols = tf_.meshInfo[materialIndex].colors32;//get its colors
int vertexIndex = tf_.characterInfo[i].vertexIndex;//get its vertex index
cols[vertexIndex + 0] = c;//set vertex color to C
cols[vertexIndex + 1] = c;// ^
cols[vertexIndex + 2] = c;// ^
cols[vertexIndex + 3] = c;// ^
ms_.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32);
//update the vertex data to render the new colors
}
public void scrollContent()//this moves the TMP_Text object
{
sr.verticalNormalizedPosition = 0;//scroll the rect to the bottom
//performing this^ movement changes the objects position, thus resetting all the colors.
}
}

Related

How to know coloring is complete on the sprite?

I am trying to make color game in that there are three object border, 3d cube, and a pointer(tube), here i'm painting a 3d cube with a pointer by changing its texture and put a sprite on the cube so i want to detect the condition while i almost complete the coloring inside the sprite border how can i do that in unity?
Like in this image
I want to know how to detect almost complete coloring within the boundry sprite.
To detect if the coloring inside the sprite border is almost complete, you can use a combination of Unity's built-in collision detection and image processing techniques. Something like this:
First create a sprite mask on the sprite border to mask the cube image. This will allow you to apply an effect to only the area inside the border.
Then sample the color of each pixel inside the border using the GetPixels method of the Texture2D class. Store these values in an array.
Calculate the average color of all the pixels in the array.
Compare the average color to a pre-defined threshold color to determine if the coloring is complete. If the average color is close enough to the threshold color, you can assume that the coloring is complete.
Repeat steps 2-4 in a loop while the player is painting the cube to continuously check if the coloring is complete.
Once the coloring is complete, you can trigger an event or perform some other action in your game.
using UnityEngine;
public class ColorDetection : MonoBehaviour
{
public SpriteRenderer spriteRenderer;
public Texture2D maskTexture;
public Color thresholdColor;
private void Update()
{
Color averageColor = GetAverageColor();
if (IsColorSimilar(averageColor, thresholdColor))
{
Debug.Log("Coloring is complete");
}
}
private Color GetAverageColor()
{
Texture2D texture = spriteRenderer.sprite.texture;
Rect spriteRect = spriteRenderer.sprite.rect;
Color[] maskPixels = maskTexture.GetPixels();
Color[] spritePixels = texture.GetPixels((int)spriteRect.x, (int)spriteRect.y, (int)spriteRect.width, (int)spriteRect.height);
int count = 0;
Color sum = Color.clear;
for (int i = 0; i < maskPixels.Length; i++)
{
if (maskPixels[i].a > 0)
{
count++;
sum += spritePixels[i];
}
}
return sum / count;
}
private bool IsColorSimilar(Color a, Color b)
{
float delta = 0.05f;
return Mathf.Abs(a.r - b.r) < delta && Mathf.Abs(a.g - b.g) < delta && Mathf.Abs(a.b - b.b) < delta;
}
}

Get normalized click position on a RectTransform

Ive made a ui touch/click controller by using an UI image with collider. The ui is rendered with a stacked camera.
Im using IPointerDownHandler.OnPointerDown to get the click event.
The controller is supposed to give a value from 0-1 depending on how far up you click it.
Im using Canvas Scaler on the UI to make the controllers resize depending on device. But that messes up my calculations since the click position wont be the same. How is this supposed to be handled? Now the calculation is only correct when i disable Canvas Scaler or run it on a display with the default dimensions.
public void OnPointerDown(PointerEventData pointerEventData)
{
SetAccelerationValue(pointerEventData.position.y);
}
private void SetAccelerationValue(float posY)
{
float percentagePosition;
var positionOnAccelerator = posY - minY;
var acceleratorHeight = maxY - minY;
percentagePosition = positionOnAccelerator / acceleratorHeight;
Debug.Log(percentagePosition);
}
I would use RectTransformUtility.ScreenPointToLocalPointInRectangle to get a position in the local space of the given RectTransform.
Then combine it with Rect.PointToNormalized
Returns the normalized coordinates cooresponding the the point.
The returned Vector2 is in the range 0 to 1 with values more 1 or less than zero clamped.
to get a normalized position within that RectTransform.rect (0,0) being bottom-left corner, (1,1) being the top-right corner
[SerializeField] private RectTransform _rectTransform;
private void Awake ()
{
if(!_rectTransform) _rectTransform = GetComponent<RectTransform>();
}
private bool GetNormalizedPosition(PointerEventData pointerEventData, out Vector2 normalizedPosition)
{
normalizedPosition = default;
// get the pointer position in the local space of the UI element
// NOTE: For click vents use "pointerEventData.pressEventCamera"
// For hover events you would rather use "pointerEventData.enterEventCamera"
if(!RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTransform, pointerEventData.position, pointerEventData.pressEventCamera, out var localPosition)) return false;
normalizedPosition = Rect.PointToNormalized(_rectTransform.rect, localPosition);
// I think this kind of equals doing something like
//var rect = _rectTransform.rect;
//var normalizedPosition = new Vector2 (
// (localPosition.x - rect.x) / rect.width,
// (localPosition.y - rect.y) / rect.height);
Debug.Log(normalizedPosition);
return true;
}
Since the normalized position returns values like
(0|1)-----(1|1)
| |
| (0|0) |
| |
(0|0)-----(1|0)
but you sounds like what you want to get is
(-1|1)----(1|1)
| |
| 0|0 |
| |
(-1|-1)----(1|-1)
So you can simply shift the returned value using e.g.
// Shift the normalized Rect position from [0,0] (bottom-left), [1,1] (top-right)
// into [-1, -1] (bottom-left), [1,1] (top-right)
private static readonly Vector2 _multiplcator = Vector2.one * 2f;
private static readonly Vector2 _shifter = Vector2.one * 0.5f;
private static Vector2 GetShiftedNormalizedPosition(Vector2 normalizedPosition)
{
return Vector2.Scale((normalizedPosition - _shifter), _multiplcator);
}
So finally you would use e.g.
public void OnPointerDown(PointerEventData pointerEventData)
{
if(!GetNormalizedPosition(pointerEventData, out var normalizedPosition)) return;
var shiftedNormalizedPosition = GetShiftedNormalizedPosition(normalizedPosition);
SetAccelerationValue(shiftedNormalizedPosition.y);
// And probably for your other question also
SetSteeringValue(shiftedNormalizedPosition.x);
}
And of course within SetAccelerationValue you don't calculate anything but just set the value ;)
This uses always the current rect so you don't have to store any min/max values and it also applies to any dynamic re-scaling of the rect.
This would then probably also apply to your other almost duplicate question ;)

Expose float2/vector2 property from shader to Unity material inspector

In a Unity shaderlab shader you can expose shader properties to the material inspector in the editor. This can be done by placing the properties you want to expose in the Properties section like so
Properties
{
_SomeFloat("A Float", float) = 5
}
Unity defines a list of properties in the documentation here.
However this does not include any form of float2 or vector2, just single Float or Vector which consists of xyzw.
I tried setting the property type to float2 And Vector2
_SomeFloat("A Float", float2) = (5,5)
_SomeFloat2("A Float2", Vector2) = (5,5)
which both return the error Parse error: syntax error, unexpected TVAL_ID at line 7
or trying to cut down the Vector in half by setting only half the members
_SomeFloat("A Float", Vector) = (5,5)
which return the error Parse error: syntax error, unexpected ')', expecting ','
I could just use the Vector type and only use its xy, but that makes for unclear UI as there are now two unused elements in the inspector, and could not find a Property Attribute or Drawer (Such as HideInInspector) that allows you to hide the zw values from the inspector.
So is there a way to expose a float2 using a property type? Or maybe an alternative where you can place two float properties next to each other in the editor like Tiling/Offset drawer is in the standard 2D property type (Maybe something similar to [EditorGUILayout.BeginHorizontal][2])?
From quick search I've found there's MaterialPropertyDrawer that can be extended to add custom tags in shader inspectors (ref: https://docs.unity3d.com/ScriptReference/MaterialPropertyDrawer.html).
Thus, you could use Vector property in shader, create custom attribute, let's say, [ShowAsVector2] and make MaterialPropertyDrawer for it, which would only show two input fields, and assign their value to vector's x and y values. This would result in shader property written as:
[ShowAsVector2] _Position2D("Position", Vector) = (0, 0, 0, 0)
This is an extension to #tsvedas's answer.
using UnityEngine;
using UnityEditor;
/// <summary>
/// Draws a vector2 field for vector properties.
/// Usage: [ShowAsVector2] _Vector2("Vector 2", Vector) = (0,0,0,0)
/// </summary>
public class ShowAsVector2Drawer : MaterialPropertyDrawer
{
public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
if( prop.type == MaterialProperty.PropType.Vector )
{
EditorGUIUtility.labelWidth = 0f;
EditorGUIUtility.fieldWidth = 0f;
if (!EditorGUIUtility.wideMode)
{
EditorGUIUtility.wideMode = true;
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - 212;
}
EditorGUI.BeginChangeCheck();
EditorGUI.showMixedValue = prop.hasMixedValue;
Vector4 vec = EditorGUI.Vector2Field(position, label, prop.vectorValue);
if (EditorGUI.EndChangeCheck()) {
prop.vectorValue = vec;
}
}
else
editor.DefaultShaderProperty( prop, label.text );
}
}
Simply put this script in an Editor folder, and you should be able to only see the x and y coordinates.

Changing the color of a gameobject shows as white not the colour requested

I have a set of gameobjects (simple cubes). I can set their initial colour when instantiating them. However when I try and change the colour by code, the object in the game view and inspector show as white, but in the colour picker show the correct colour!
There is a single directional light (the default one).
IEnumerator ColourChange()
{
Color targetColour = new Color(Random.Range(0, 255), Random.Range(0, 255), Random.Range(0, 255));
Debug.Log("color = " + targetColour);
for (int x = 0; x < CreateCubeGrid.GRIDSIZE; x++) {
for (int z = 0; z < CreateCubeGrid.GRIDSIZE; z++) {
CreateCubeGrid.cubeGrid[x,z].GetComponent<Renderer>().material.color = targetColour;
}
yield return new WaitForSeconds (0.05f);
}
}
Colours are 0 to 1 not 0 to 255.
Use Color32 if you want to use 0-255 values.
Color32 Documentation
In order to change the material's color, you must tell it exactly what color you're trying to change, by using Shader.Find("_YourColor") (Emission, Albedo, etc).
An approach that should work for materials using Standard Shader can be seen below:
private void ChangeColor(Color color)
{
//Fetch the Renderer from the GameObject.
Renderer rend = GetComponent<Renderer>();
//Find and set the main Color ("_Color") of the Material to the new one
rend.material.shader = Shader.Find("_Color");
rend.material.SetColor("_Color", color);
}
You can read more about changing a Material's color at Unity's Documentation.

How to change the zoom centerpoint in an ILNumerics scene viewed with a camera

I would like to be able to zoom into an ILNumerics scene viewed by a camera (as in scene.Camera) with the center point of the zoom determined by where the mouse pointer is located when I start spinning the mouse scroll wheel. The default zoom behavior is for the zoom center to be at the scene.Camera.LookAt point. So I guess this would require the mouse to be tracked in (X,Y) continuously and for that point to be used as the new LookAt point? This seems to be like this post on getting the 3D coordinates from a mouse click, but in my case there's no click to indicate the location of the mouse.
Tips would be greatly appreciated!
BTW, this kind of zoom method is standard operating procedure in CAD software to zoom in and out on an assembly of parts. It's super convenient for the user.
One approach is to overload the MouseWheel event handler. The current coordinates of the mouse are available here, too.
Use the mouse screen coordinates to acquire (to "pick") the world
coordinate corresponding to the primitive under the mouse.
Adjust the Camera.Position and Camera.ZoomFactor to 'move' the camera closer to the point under the mouse and to achieve the required 'directional zoom' effect.
Here is a complete example from the ILNumerics website:
using System;
using System.Windows.Forms;
using ILNumerics;
using ILNumerics.Drawing;
using ILNumerics.Drawing.Plotting;
using static ILNumerics.Globals;
using static ILNumerics.ILMath;
namespace ILNumerics.Examples.DirectionalZoom {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void panel2_Load(object sender, EventArgs e) {
Array<float> X = 0, Y = 0, Z = CreateData(X, Y);
var surface = new Surface(Z, X, Y, colormap: Colormaps.Winter);
surface.UseLighting = true;
surface.Wireframe.Visible = false;
panel2.Scene.Camera.Add(surface);
// setup mouse handlers
panel2.Scene.Camera.Projection = Projection.Orthographic;
panel2.Scene.Camera.MouseDoubleClick += Camera_MouseDoubleClick;
panel2.Scene.Camera.MouseWheel += Camera_MouseWheel;
// initial zoom all
ShowAll(panel2.Scene.Camera);
}
private void Camera_MouseWheel(object sender, Drawing.MouseEventArgs e) {
// Update: added comments.
// the next conditionals help to sort out some calls not needed. Helpful for performance.
if (!e.DirectionUp) return;
if (!(e.Target is Triangles)) return;
// make sure to start with the SceneSyncRoot - the copy of the scene which receives
// user interaction and is eventually used for rendering. See: https://ilnumerics.net/scene-management.html
var cam = panel2.SceneSyncRoot.First<Camera>();
if (Equals(cam, null)) return; // TODO: error handling. (Should not happen in regular setup, though.)
// in case the user has configured limited interaction
if (!cam.AllowZoom) return;
if (!cam.AllowPan) return; // this kind of directional zoom "comprises" a pan operation, to some extent.
// find mouse coordinates. Works only if mouse is over a Triangles shape (surfaces, but not wireframes):
using (var pick = panel2.PickPrimitiveAt(e.Target as Drawable, e.Location)) {
if (pick.NextVertex.IsEmpty) return;
// acquire the target vertex coordinates (world coordinates) of the mouse
Array<float> vert = pick.VerticesWorld[pick.NextVertex[0], r(0, 2), 0];
// and transform them into a Vector3 for easier computations
var vertVec = new Vector3(vert.GetValue(0), vert.GetValue(1), vert.GetValue(2));
// perform zoom: we move the camera closer to the target
float scale = Math.Sign(e.Delta) * (e.ShiftPressed ? 0.01f : 0.2f); // adjust for faster / slower zoom
var offs = (cam.Position - vertVec) * scale; // direction on the line cam.Position -> target vertex
cam.Position += offs; // move the camera on that line
cam.LookAt += offs; // keep the camera orientation
cam.ZoomFactor *= (1 + scale);
// TODO: consider adding: the lookat point now moved away from the center / the surface due to our zoom.
// In order for better rotations it makes sense to place the lookat point back to the surface,
// by adjusting cam.LookAt appropriately. Otherwise, one could use cam.RotationCenter.
e.Cancel = true; // don't execute common mouse wheel handlers
e.Refresh = true; // immediate redraw at the end of event handling
}
}
private void Camera_MouseDoubleClick(object sender, Drawing.MouseEventArgs e) {
var cam = panel2.Scene.Camera;
ShowAll(cam);
e.Cancel = true;
e.Refresh = true;
}
// Some sample data. Replace this with your own data!
private static RetArray<float> CreateData(OutArray<float> Xout, OutArray<float> Yout) {
using (Scope.Enter()) {
Array<float> x_ = linspace<float>(0, 20, 100);
Array<float> y_ = linspace<float>(0, 18, 80);
Array<float> Y = 1, X = meshgrid(x_, y_, Y);
Array<float> Z = abs(sin(sin(X) + cos(Y))) + .01f * abs(sin(X * Y));
if (!isnull(Xout)) {
Xout.a = X;
}
if (!isnull(Yout)) {
Yout.a = Y;
}
return -Z;
}
}
// See: https://ilnumerics.net/examples.php?exid=7b0b4173d8f0125186aaa19ee8e09d2d
public static double ShowAll(Camera cam) {
// Update: adjusts the camera Position too.
// this example works only with orthographic projection. You will need to take the view frustum
// into account, if you want to make this method work with perspective projection also. however,
// the general functioning would be similar....
if (cam.Projection != Projection.Orthographic) {
throw new NotImplementedException();
}
// get the overall extend of the cameras scene content
var limits = cam.GetLimits();
// take the maximum of width/ height
var maxExt = limits.HeightF > limits.WidthF ? limits.HeightF : limits.WidthF;
// make sure the camera looks at the unrotated bounding box
cam.Reset();
// center the camera view
cam.LookAt = limits.CenterF;
cam.Position = cam.LookAt + Vector3.UnitZ * 10;
// apply the zoom factor: the zoom factor will scale the 'left', 'top', 'bottom', 'right' limits
// of the view. In order to fit exactly, we must take the "radius"
cam.ZoomFactor = maxExt * .50;
return cam.ZoomFactor;
}
}
}
Note, that the new handler performs the directional zoom only when the mouse is located over an object hold by this Camera! If, instead, the mouse is placed on the background of the scene or over some other Camera / plot cube object no effect will be visible and the common zoom feature is performed (zooming in/out to the look-at point).