Edit only an Instance's mesh AND prefab-able??

CROSS POST FROM UNITY ANSWERS. (I appear to have stumped them :slight_smile: )

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 (I grant no warranties/ assume no liabilities, etc.)

    #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();
    }

C’mon yall, SOMEBODY has to know how to do it? I can rephrase my question if necessary:

How do you edit only the currently selected gameObject’s mesh?

bump

bump-ity bump. I’m sure SOMEONE had to have tried something remotely similar??

Don’t use sharedMesh. It’s shared. It’s in the name.

To prevent leaking, you’ll have to do more work. So the first time you edit it, you’ll need to use the regular mesh variable. After that, use sharedMesh to prevent leaks.

Thanks for the reply :slight_smile:

I know that sharedMesh is shared (thankfully, I can read) and I tried using MeshFilter.mesh initially, which almost worked except that I couldn’t save prefabs that kept the settings in the script. If you don’t mind, would you mind elaborating a bit more on your method?

Thanks!

Instead of changing the shared mesh, you have to create a new mesh and say “Hey, selected game object? You use THIS mesh as your sharedmesh now, please.”

myMesh : Mesh = CreateMesh();
selectedObject.meshFilter.sharedMesh = myMesh;

If you’re working in prefabs, you also need to save your created mesh as an asset or the object will forget about it when you change scenes.

Here’s a couple snippets:

	private function MakeCollider() : Mesh {
        var assetName : String = meshName + ".asset";
        var m : Mesh = AssetDatabase.LoadAssetAtPath("Assets/Meshes/" + assetName,Mesh) as Mesh;
		if ( m  overwrite ) {
			AssetDatabase.DeleteAsset("Assets/Meshes/"+assetName);
			m = null;
		}
        if (m == null) {
            m = new Mesh();
            m.name = meshName;
			var numTriangles : int = collider.Count * 6;
			var numVertices : int = collider.Count * 2;
            var vertices : Vector3[] = new Vector3[numVertices];
            var uvs : Vector2[] = new Vector2[numVertices];
            var triangles : int[] = new int[numTriangles];
            var ix : int;
			var index : int;
			
			for ( ix = 0; ix < collider.Count; ix++ ) {
				index = ix * 2;
				vertices[index] = new Vector3( collider[ix].x - 0.5, 1, -(collider[ix].z - 0.5) ) * scale;
				vertices[index+1]=new Vector3( collider[ix].x - 0.5,-1, -(collider[ix].z - 0.5) ) * scale;
				vertices[index].y = 1; vertices[index+1].y = -1;
				
				index = ix * 6;
				triangles[index]  =ix*2;
				triangles[index+1]=(ix*2+3)%(collider.Count*2);
				triangles[index+2]=(ix*2+1)%(collider.Count*2);
				triangles[index+3]=ix*2;
				triangles[index+4]=(ix*2+2)%(collider.Count*2);
				triangles[index+5]=(ix*2+3)%(collider.Count*2);
			}
			
            m.vertices = vertices;
            m.uv = uvs;
            m.triangles = triangles;
            m.RecalculateNormals();
 
            AssetDatabase.CreateAsset(m, "Assets/Meshes/" + assetName);
            AssetDatabase.SaveAssets();
		} else {
			Debug.LogWarning("Mesh Assets/Meshes/" + meshName + " already exists.");
		}
		return m;
	}

...
var sgo : GameObject = Selection.activeGameObject
...

			if ( GUILayout.Button("Make  Attach") ) {
				var m : Mesh = MakeCollider();
				if ( sgo.collider ) {
					DestroyImmediate( sgo.collider, true );
				}
				var mc : MeshCollider = sgo.AddComponent(MeshCollider);
				mc.sharedMesh = m;
				mc.convex = true;
				mc.isTrigger = true;
			}

Thanks Loius, appreciate the code sample, gonna try this asap.

I’ll post back with any questions/results :slight_smile:

UPDATE:

So I think I almost got it working, but I can’t seem to actually change the shared mesh?

Here’s the function I added to the editor script above:

public Mesh CreateNewMesh(){
		Mesh newMesh;
		GameObject temp = (GameObject)(Instantiate(Resources.Load("Models/Blocks/baseBlock"))as GameObject);
			MeshFilter tempF = temp.GetComponent<MeshFilter>();
		
		newMesh = tempF.mesh;	
		
		DestroyImmediate(temp);
		DestroyImmediate(tempF);
		
		return newMesh;
	}

And then I call it like this:

//sFilter is the current selection's MeshFilter
sFilter.sharedMesh = CreateNewMesh();

Any ideas?

I copied your function into my code and it works for my mesh filter with no changes :?

			if ( GUILayout.Button("Make  Attach") ) {
				if ( !sgo.GetComponent.<MeshFilter>() ) {
					var mf : MeshFilter = sgo.AddComponent(MeshFilter);
				}
				mf = sgo.GetComponent(MeshFilter);
				mf.sharedMesh = CreateNewMesh();
			}

I wouldn’t think the language would matter - I’m using unityscript but sharedMesh should behave the same for both… right?

You’re using the .mesh property in CreateNewMesh - that threw a leak exception for me in the editor. Perhaps C# ‘crashes’ on that exception? Try .sharedMesh instead, maybe that’ll do it.

@Loius No dice :\

On a side note, I’ve got the script(s) working with the following; it even can animate uvs! Only down side is that annoying mesh leak error. Anyone know how to manually catch/clean leaked meshes?

(Feel free to use, I assume no liability, yada yada):

#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 Start () {		
		rowNum = Mathf.Clamp (rowNum,1,15);
		columnNum = Mathf.Clamp (columnNum,1,15);
		Offset();
		if(isAnimated){
			Animate();
		}
}

function Update () {
	if(!Application.isPlaying||isAnimated)
		Offset();
}

And the editor (kinda sloppy):

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor (typeof(UVSlider2))]
[CanEditMultipleObjects]

public class UVSlider2Editor : Editor{
	
	SerializedProperty row, column, isAnimated, numOfAnimations,
	animCoords;
	
	public Mesh CreateNewMesh(){
		Mesh newMesh;
		GameObject temp = (GameObject)(Instantiate(Resources.Load("Models/Blocks/baseBlock"))as GameObject);
			MeshFilter tempF = temp.GetComponent<MeshFilter>();
		
		newMesh = tempF.sharedMesh;	
		
		DestroyImmediate(temp);
		DestroyImmediate(tempF);
		
		return newMesh;
	}
	
	public void OnEnable(){
		row 				= serializedObject.FindProperty ("rowNum");
		column 				= serializedObject.FindProperty ("columnNum");
		isAnimated 			= serializedObject.FindProperty ("isAnimated");
		numOfAnimations 	= serializedObject.FindProperty ("numOfAnimations");
		animCoords			= serializedObject.FindProperty ("animCoords");
	
		MeshFilter sFilter = Selection.activeGameObject.GetComponent<MeshFilter>();
		
		sFilter.sharedMesh = CreateNewMesh();
	}
	
	public bool iAnimated = false;
	public override void OnInspectorGUI(){
		serializedObject.Update();
		
		row.intValue = EditorGUILayout.IntSlider("Row", row.intValue,1,15);
		column.intValue = EditorGUILayout.IntSlider("Column", column.intValue,1,15);
		
		DrawDefaultInspector();
		
		serializedObject.ApplyModifiedProperties();
	}
	
}