- Home /
Moving Mesh Instance UVs in Editor? (code provided)
Hey,
So I made this script that edits the UV coordinates of a model to slide it along a texture atlas for a semi 2D game. This function in itself works, but in the editor, I used MeshFilter.sharedMesh (like the docs said to) to prevent leaking; as an unintended side-effect, all meshes in the scene with this script change their uvs to the selected coords in the script since they all share a model. I don't want that to happen because I can't tell what type of sprite is where without running the game.
TL;DR: How can I make this script ONLY affect the mesh it's attached to?
*feel free to use this if you like
#pragma strict
@script ExecuteInEditMode()
var myMesh:MeshFilter;
var myTex:Texture2D;
var uvs:Vector2[] =new Vector2[4];
//Use these like coordinates to access the UVs.
//These are controlled via editor script
var rowNum:int = 0; //On which row is the sprite located?
var columnNum:int = 0;//In which column is the sprite located;
var isAnimated:boolean;
var framerate:int = 10;
var animCoords : Vector2[] = new Vector2[10];
private var padding:float=0.00390625;//Whatever padding space you use between sprites
private var width:float = 0.0625;//either direction, because I only use square textures.
private var _myMesh:Mesh;
function getObjectMesh (){
var mf:MeshFilter= GetComponent(MeshFilter);
var mesh:Mesh;
if (Application.isEditor){
mesh=mf.sharedMesh;
} else {
mesh=mf.mesh;
}
return mesh;
}
function Offset(){
//Get the current mesh to edit
myMesh=GetComponent(MeshFilter);
_myMesh = myMesh.sharedMesh;
var mesh:Mesh = getObjectMesh();
//Current texture, in case you need it;
myTex =renderer.sharedMaterial.mainTexture;//don't want to generate an instance
//Assign uvs, maintain square aspect ratio
//bottom left
uvs[0] = Vector2(/*x value*/ (padding*(columnNum))+(((columnNum)-1)*width),
/*y value*/ (1-( (padding*(rowNum))+ (width*(rowNum-1))) ));
//bottom right
uvs[1] = Vector2(uvs[0].x+width, uvs[0].y);
//top left
uvs[2] = Vector2(uvs[1].x,1-( (padding*(rowNum)) + (width*(rowNum)) ) );
//top right.
uvs[3] = Vector2(uvs[0].x, uvs[2].y); // pretty easy, huh?
mesh.uv = uvs;
return;
}
function Start () {
rowNum = Mathf.Clamp (rowNum,1,15);
columnNum = Mathf.Clamp (columnNum,1,15);
Offset();
}
function Update () {
getObjectMesh ();
if(!Application.isPlaying)
Offset();
}
Answer by invicticide · Nov 30, 2012 at 02:25 AM
You have to use MeshFilter.mesh for this, as opposed to MeshFilter.sharedMesh. It does mean you'll have many instances of the mesh, but when the mesh is just a quad that cost isn't prohibitive.
Thanks for the reply! I actually tried that, but I couldn't make prefabs of the result because all of the $$anonymous$$eshFilters' mesh variables were assigned to an in-scene clone :\ Is there any way around that?
Oh, somehow I missed the part where this was happening in-editor ins$$anonymous$$d of in-game. :/
In-editor, $$anonymous$$eshFilter.mesh will cause leakage and other problems, as you've correctly noted. If you want a bunch of prefabs that are each the same quad with different UVs, then unfortunately the only way I know to handle that is to have each prefab actually reference a completely unique mesh asset.
For example, say you've got a texture atlas with four sprites in it: A, B, C, and D. Ins$$anonymous$$d of having a single quad.mb mesh and modifying the UVs programmatically, you'd need to have four separate meshes: a.mb, b.mb, c.mb, and d.mb. Each mesh is the same quad geometry but different UVs, and you've setup those UVs in your modeling package, not in Unity.
It's kind of a sucky workflow when all you're trying to do is have a sprite atlas, but I haven't yet found a better way to do it.
As a sidebar: in my game "Fail-Deadly" I set all my UVs at runtime, not in-editor. By doing it at runtime I was able to set the UVs programmatically, using $$anonymous$$eshFilter.mesh, and not have to worry about editor leakage. Those auto-instanced meshes went away when the game was shutdown, but that's okay in this case because they only needed to exist during the game session anyway. The tradeoff for this easy implementation is that I can't view my sprites in-editor using their correct UVs: ins$$anonymous$$d I see a quad with the entire sprite atlas on it. Fortunately for that game that's okay, because I didn't need to lay out levels or anything that required viewing the assets in the editor: everything is procedurally laid out at runtime. This probably isn't at all equivalent to your case, but it might lend some useful context to my previous answer. ;)
Answer by Bunny83 · Dec 02, 2012 at 10:02 PM
Like invicticide said you have to use individual assets. Everything at edit time has to be an asset, otherwise it's lost when you reload Unity / open another scene / ...
You can use the AssetDatabase to save a clone of your mesh as new asset. However it's up to you how you trigger the cloning & saving process since it only has to be done once. That's usually a good example for an EditorWindow or wizard.
However, beside modifying the mesh, wouldn't be enough in your case to just adjust the Materials uv-offset and scaling?
Answer by theRaddRedd · Dec 03, 2012 at 03:23 PM
Appreciate all the answers guys.
I scripted a pretty good solution to this over the past couple of days. Feel free to use/modify (I grant no warranties/assume no liabilities/etc.)
WHAT IT DOES:
-Slide quad uvs in the editor along an atlas
-Linearly Animate uv placements via an array of Row/column pairs
-Batched Draw Calls (for the most part)
-You can remove when done sliding, and changes hold.
-Prefab-able!
KNOWN PROBLEMS:
-QUADS ONLY. I hard-coded the the uv sliding to work with vertex indexes, so you may need to tweak the vertex numbers in the script.
used this on a 15x15 sprite atlas on a 256 X 256 image, so you'll have to adjust rowPadding etc. for your needs
used .mesh for this, so meshes DO leak. Sorry, no better way it seems :\ Since you'll be using quads, though, this isn't that bad a problem :)
PoorMan's SpriteManager:
#pragma strict
@script ExecuteInEditMode()
var myMesh:MeshFilter;
var myTex:Texture2D;
var uvs:Vector2[] =new Vector2[4];
//Use these like coordinates to access the UVs.
//These are controlled via editor script
var rowNum:int = 0; //On which row is the sprite located?
var columnNum:int = 0;//In which column is the sprite located;
var isAnimated:boolean;
var framerate:int = 10;
var animCoords : Vector2[] = new Vector2[10];
private var padding:float=0.00390625;//Whatever padding space you use between sprites
private var width:float = 0.0625;//either direction, because I only use square textures.
var index:int = 0;
var waitTime:float = 0.01;
function Animate():IEnumerator{
rowNum=animCoords[index].x;
columnNum =animCoords[index].y;
//ready next frame
if(index<(animCoords.length-1))
index++;
else{
index = 0;
}
yield WaitForSeconds(waitTime);
Animate();
return;
}
function Offset(){
//Get the current mesh to edit
myMesh=gameObject.GetComponent(MeshFilter);
//var mesh:Mesh = getObjectMesh();
//Current texture, in case you need it;
myTex =renderer.sharedMaterial.mainTexture;//don't want to generate an instance
//Assign uvs, maintain square aspect ratio
//bottom left
uvs[0] = Vector2(/*x value*/ (padding*(columnNum))+(((columnNum)-1)*width),
/*y value*/ (1-( (padding*(rowNum))+ (width*(rowNum-1))) ));
//bottom right
uvs[1] = Vector2(uvs[0].x+width, uvs[0].y);
//top left
uvs[2] = Vector2(uvs[1].x,1-( (padding*(rowNum)) + (width*(rowNum)) ) );
//top right.
uvs[3] = Vector2(uvs[0].x, uvs[2].y); // pretty easy, huh?
myMesh.mesh.uv = uvs;
return;
}
function CreateNewMesh(){
var newMesh:Mesh;
var temp:GameObject = (Instantiate(Resources.Load("Prefabs/Blocks/basePlane"))as GameObject);
var tempF:MeshFilter = temp.GetComponent(MeshFilter);
newMesh = tempF.sharedMesh;
DestroyImmediate(temp);
DestroyImmediate(tempF);
return newMesh;
}
function Start () {
if(!Application.isPlaying)
myMesh.mesh = CreateNewMesh();
rowNum = Mathf.Clamp (rowNum,1,15);
columnNum = Mathf.Clamp (columnNum,1,15);
Offset();
if(isAnimated){
Animate();
}
}
function Update () {
if(!Application.isPlaying||isAnimated)
Offset();
}