- Home /
Why are UVs wrong when building for iPhone?
I am making a tile puzzle game. Each tile is made of three segments, and I am changing their colours on the fly by setting UVs in code. (The mesh is built from scratch in code too). At Awake() I am filling an array with hard-coded UV settings, and then a ChangeColor(int,int,int) method returns a Vector2[] which goes straight into the mesh's uv settings. It is working fine in the editor, but when I build to test on my iPhone 4, the UVs get screwed around. I've attached a picture to illustrate. I've tried changing the import settings on the texture, and changing the shader. Same result. Is this a known problem? Am I doing something wrong?
Here's the code that handles the mesh: (By the way TriPos.height is a constant - the height of an equilateral triangle)
using UnityEngine;
using System.Collections;
public class TileData : MonoBehaviour {
// Holds info on UVs for all colors, trangled or not. Only a mono so it can init on its own
// Tile Prefab
//---------------------------------------------------------
public GameObject TilePrefab;
public static GameObject Prefab;
// Arrays to hold the information
//---------------------------------------------------------
private static Vector3[] verts;
private static int[] tris;
private static Vector2[,,] uvs; // for each color, for locked or not, for each vertex
public static readonly int colorCount = 9;
// Store hard coded data into arrays
//---------------------------------------------------------
void Awake () {
// Static access to prefab
Prefab = TilePrefab;
// Set Verts
verts = new Vector3[9];
verts[0] = Vector3.zero;
verts[1] = new Vector3( 0, TriPos.Height*(2f/3f), 0 );
verts[2] = new Vector3( 0.5f, -TriPos.Height/3f, 0 );
verts[3] = verts[0];
verts[4] = verts[2];
verts[5] = new Vector3( -0.5f, -TriPos.Height/3f, 0 );
verts[6] = verts[0];
verts[7] = verts[5];
verts[8] = verts[1];
// Set Tris
tris = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
// Set UVs for all colors, for unlocked/locked
// ??? needs to be sorted out
float h = TriPos.Height;
uvs = new Vector2[colorCount,2,3];
// color 0, locked
uvs[0,1,0] = new Vector2(0.25f, h/3f);
uvs[0,1,1] = new Vector2(0.25f, 0);
uvs[0,1,2] = new Vector2(0, h/2f);
// color 1, locked
uvs[1,1,0] = uvs[0,1,0];
uvs[1,1,1] = uvs[0,1,2];
uvs[1,1,2] = new Vector2(0.5f, h/2f);
// color 2, locked
uvs[2,1,0] = uvs[0,1,0];
uvs[2,1,1] = uvs[1,1,2];
uvs[2,1,2] = uvs[0,1,1];
// color 3, locked
uvs[3,1,0] = new Vector2(0.5f, h/6f);
uvs[3,1,1] = uvs[0,1,1];
uvs[3,1,2] = uvs[1,1,2];
// color 4, locked
uvs[4,1,0] = uvs[3,1,0];
uvs[4,1,1] = uvs[3,1,2];
uvs[4,1,2] = new Vector2(0.75f, 0);
// color 5, locked
uvs[5,1,0] = uvs[3,1,0];
uvs[5,1,1] = uvs[4,1,2];
uvs[5,1,2] = uvs[3,1,1];
// color 6, locked
uvs[6,1,0] = new Vector2(0.75f, h/3f);
uvs[6,1,1] = uvs[4,1,2];
uvs[6,1,2] = uvs[4,1,1];
// color 7, locked
uvs[7,1,0] = uvs[6,1,0];
uvs[7,1,1] = uvs[6,1,2];
uvs[7,1,2] = new Vector2(1, h/2f);
// color 8, locked
uvs[8,1,0] = uvs[6,1,0];
uvs[8,1,1] = uvs[7,1,2];
uvs[8,1,2] = uvs[6,1,1];
// Mirror along y==h/2 line for locked
for ( int c=0; c<colorCount; c++ ) {
for ( int v=0; v<3; v++ ) {
Vector2 untr = uvs[c,1,v];
uvs[c,0,v] = new Vector2( untr.x, h - untr.y);
}
}
}
// Get Verts from stored arrays
//---------------------------------------------------------
public static Vector3[] GetVerts() {
return verts;
}
// Get Tris from stored arrays
//---------------------------------------------------------
public static int[] GetTris() {
return tris;
}
// Get UVs from stored arrays
//---------------------------------------------------------
public static Vector2[] GetUVs( int c0, int c1, int c2, bool locked ) {
c0 = c0 % colorCount; c1 = c1 % colorCount; c2 = c2 % colorCount;
int tr = locked ? 1 : 0;
Vector2[] u = new Vector2[9];
u[0] = uvs[c0,tr,0];
u[1] = uvs[c0,tr,1];
u[2] = uvs[c0,tr,2];
u[3] = uvs[c1,tr,0];
u[4] = uvs[c1,tr,1];
u[5] = uvs[c1,tr,2];
u[6] = uvs[c2,tr,0];
u[7] = uvs[c2,tr,1];
u[8] = uvs[c2,tr,2];
return u;
}
}
And here's what the texture looks like:
Well, without seeing the code that produces the coordinates it's hard to tell...
The texture resolution might be different. Do you rely your calculations on a specific texture size? Do you use an atlas or any atlas tools? Packing textures into an atlas requires recalculation of the UVs.
Again, without more information noone can answer this.
Right. I've added the code and the atlas (no tools required to make the Atlas - the UV coords are easy enough to work out manually). As I said though, it's working fine in the editor.
I'd assume that the uv array that gets returned has random data in it. $$anonymous$$aybe make some tests, perhaps get the color0, locked UVs and print out what's returned.
How can a method that's just fetching data from an array give random results? I'll do some testing as you suggest, but as it only happens on the device I am guessing it has to be some kind of issue with meshes created in code on the iPhone.
Answer by robinking · Apr 02, 2013 at 03:04 PM
After some diagnostics, I've discovered the exact cause of the problem. A 3D array of Vector2s goes awry when built for iPhone: each Vector2 seems to use 2 array spaces - the index position for Vector2.x and the one after for Vector2.y! It can be grasped more easily using this script - attach it to an Empty in an otherwise empty scene. Then run it in the editor and build for the device. I've attached the two outputs as screen grabs below.
The script stores a sequence of Vector2s in a 3d array, retrieves each just after storing (works fine), then retrieves them all after all have been stored (the Y of each has been overwritten by the X of the subsequent one on the device). It prints the Vector2s three different ways, to test if it is a formatting problem (it isn't).
I don't know if this occurs for 2d arrays as well, or for Vector3s as well - but it's worth looking into. I think this is worth reporting as a bug. I fixed it for my game by splitting the 3d array of Vector2s into 2 x 3d arrays of floats (one for x, one for y). I tried a 4d array at first, but then discovered another issue, namely that 4d arrays work fine in the editor but cause fatal error on the device!
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Vec2Problem : MonoBehaviour {
public List<string> msgs;
// Use this for initialization
void Start () {
MakeV2s ();
}
private void MakeV2s() {
int width = 2, height = 2, depth = 2;
Vector2[,,] vecs = new Vector2[width,height,depth];
float a = 0;
float b = 0;
msgs.Add ( "STORE VECTOR2s IN 3D BUILT-IN ARRAY:" );
for ( int x = 0; x < width; x++ ) {
for ( int y = 0; y < height; y++ ) {
for ( int z = 0; z < depth; z++ ) {
Vector2 v = new Vector2(a,b);
string s1 = string.Format ("RAW Vector2: ",x,y,z);
s1+= v.ToString () + " / " + v.ToString ("F3") + " / (" + v.x.ToString ("F3") + ", " + v.y.ToString ("F3") + ")";
msgs.Add (s1);
vecs[x,y,z] = v;
string s2 = string.Format (" vecs[{0},{1},{2}]: ",x,y,z);
s2+= vecs[x,y,z].ToString () + " / " + vecs[x,y,z].ToString ("F3") + " / (" + vecs[x,y,z].x.ToString ("F3") + ", " + vecs[x,y,z].y.ToString ("F3") + ")";
msgs.Add (s2);
Vector2 v2 = vecs[x,y,z];
string s3 = string.Format (" Vector2: ",x,y,z);
s3+= v2.ToString () + " / " + v2.ToString ("F3") + " / (" + v2.x.ToString ("F3") + ", " + v2.y.ToString ("F3") + ")";
msgs.Add (s3);
// msgs.Add ("");
a += 0.279f;
b += 0.131f;
}
}
}
msgs.Add ("RETRIEVAL:");
for ( int x = 0; x < width; x++ ) {
for ( int y = 0; y < height; y++ ) {
for ( int z = 0; z < depth; z++ ) {
string s4 = string.Format ("vecs[{0},{1},{2}]: ",x,y,z);
s4+= vecs[x,y,z].ToString () + " / " + vecs[x,y,z].ToString ("F3") + " / (" + vecs[x,y,z].x.ToString ("F3") + ", " + vecs[x,y,z].y.ToString ("F3") + ")";
msgs.Add (s4);
Vector2 v3 = vecs[x,y,z];
string s5 = string.Format (" Vector2: ",x,y,z);
s5+= v3.ToString () + " / " + v3.ToString ("F3") + " / (" + v3.x.ToString ("F3") + ", " + v3.y.ToString ("F3") + ")";
msgs.Add (s5);
// msgs.Add ("");
}
}
}
}
void OnGUI() {
string msgBlock = "";
if (msgs==null) return;
foreach( string s in msgs ) {
msgBlock += s + "\n";
}
if (msgBlock != "")
GUI.Label( new Rect( 2, 2, Screen.width, Screen.height ), msgBlock );
}
}
Wow, thanks for posting this. Yes, please file a bug report because that really shouldn't happen. It seems that arrays with higher dimensions are not entirely supported / produce bugs:
http://answers.unity3d.com/questions/297051/why-doesnt-unity-iphone-support-4d-arrays.html
$$anonymous$$aybe there's something wrong with the mono framework for iPhone.
Answer by Bunny83 · Mar 21, 2013 at 06:01 PM
Well, this could be a race condition bacause you setup your array in Awake but all your variables and getter methods are static. So i guess you use the static functions from another class. I guess the other class reads the data before Awake has been called.
If it's static data you could use the static constructor of the TileData class
static TileData()
{
// init
}
But keep in mind that the static constructor will be called when this class is accessed the first time, so if this class relies on other things, make sure they are available at the time this class get initialized.
That's why a real Singleton pattern is better ;) A singleton will ensure that the object is created and initialized when someone tries to use it.
Anyways, you should figure out your initialization order. Keep in mind that the order in which things are called can change in the built version because the internal order of the objects might change. As a general advice: Use Awake to initialize the class itself. Use Start to initialize everything else that relies on other classes.
edit
I took another look at your picture and code and i actually don't believe that the arrays are uninitialized because:
An uninitialized array would be null or empty, so trying to access an element would either raise a null reference exception or an index out of bounds exception.
The UVs are actually (almost) the correct UVs.
First of all the only explanation i have is that you somehow "scramble" the whole UV array. As an example, in the 2nd row 4th column the right triangle quite clearly uses the center point of the left and right "locked" triangles and the center point of the middle "unlocked" triangle. That actually doesn't make any sense.
In addition it seems some uv positions are slightly off (beside that they are all in the wrong order ;) ). As example 1st row 3rd column bottom triangle. The 3 coordinates are actually the right ones but a little bit off and in the wrong order. It's rotated to the right.
Next thing is the corruption have to be inside your 3 dimensional array. That's simply because triangles which should have received the same color look exactly the same. I can't say that for sure since i don't know how you actually build the mesh, maybe there's something wrong. However it's hard to imagine anything that would make it work in the editor and fail on the device.
I'm really interested in what actually caused this strange behaviour.
Btw. I've written a generic singleton baseclass which makes it very easy to turn a $$anonymous$$onoBehaviour into a "UnitySingleton":
public class $$anonymous$$onoBehaviourSingleton< TSelfType > : $$anonymous$$onoBehaviour where TSelfType : $$anonymous$$onoBehaviour
{
private static TSelfType m_Instance = null;
public static TSelfType Instance
{
get
{
if (m_Instance == null)
{
m_Instance = (TSelfType)FindObjectOfType(typeof(TSelfType));
if (m_Instance == null)
m_Instance = (new GameObject(typeof(TSelfType).Name)).AddComponent<TSelfType>();
DontDestroyOnLoad(m_Instance.gameObject);
}
return m_Instance;
}
}
}
To turn a class into a singleton, just do this:
public class TileData : $$anonymous$$onoBehaviourSingleton< TileData >
{
//[...]
Now your class has an Instance property which can be used to access the instance from everywhere.
I will look into that -- however the TileData class relies only on the static readonly float TriPos.height -- and the verts are correct which also uses that. The tiles themselves are created after everything initialises, after the user chooses to start a game. I'd be very happy if this answer turns out to be correct though, as it sounds like the least work to diagnose and fix!
If TriPos.height is just a variable initialized by a field initializer (so you assigned the value in the same line you declared the variable) that's no problem. When a class is accessed for the first time the system will automatically execute all static field initializer and after that the static constructor. So the system ensures that the static part is initialized before any interaction is possible with the class.
Just place some Debug.Logs in your classes to figure out the execution order. When testing on mobile you could use something like this:
public class $$anonymous$$obileDebug : $$anonymous$$onoBehaviourSingleton < $$anonymous$$obileDebug >
{
private string m_DebugText = "";
private Vector2 m_ScrollPos = Vector2.zero;
private bool m_ShowDebug = false;
public static void Log(string aText)
{
Instance.m_DebugText += aText + "\n";
}
void OnGUI()
{
GUILayout.BeginVertical();
m_ShowDebug = GUILayout.Toggle(m_ShowDebug, "Debug", "Button", GUILayout.Width(100));
if (m_ShowDebug)
{
if (GUILayout.Button("clear"))
m_DebugText = "";
m_ScrollPos = GUILayout.BeginScrollView(m_ScrollPos);
GUILayout.Label(m_DebugText);
GUILayout.EndScrollView();
}
GUILayout.EndVertical();
}
}
Now just use:
$$anonymous$$obileDebug.Log("....");
(ps: i've written this helper from scratch right here on UA, so can't guarantee it will work ;) )
I love subclass referencing generics, but I hadn't thought of it for simple singletons, nice.
If you keep everything working that way I bet it also avoids the problem I occasionally have of the bloody singletons somehow persisting into the scene :S (Guessing it's one of my few real statics having going off before Unity is ready).
I have now discovered the root cause of the problem and posted a detailed answer here - an interesting development - it appears the problem is within Unity itself (unless I'm being stupid!)
Your answer
Follow this Question
Related Questions
How can I get unique identifier IOS in unity? 2 Answers
iPhone gets heated pretty quickly 1 Answer
Zero-ing the Input.acceleration.z 0 Answers
Is it possible to compress textures per device 0 Answers
Graphics bug while testing on device? 0 Answers