Script to combine Animated Mesh and Textures

Hi,

Here’s a script (based on some others I found here and on unifycommunity) that you can place on the root and will combine all the children Skinned meshes into a single one as well as combining all their textures into an atlas. Turning it into one draw call. It does it once while in the editor, creating new assets for the combination so that you can save them into your project for later.

Add it under an Editor/ folder. It’ll create Assets->CombineSkinnedMeshes. Highlight the object you want to combine in the Hiearchy view and then click that menu item. All the generated combined assets will go in the top level of your asset folder.

Good luck!

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;

public class EditorCombine : MonoBehaviour {

    static public int maxAtlasSize = 2048;

    [MenuItem ("Assets/CombineSkinnedMeshes")]
    static void CombineTheMeshes(){
        if (Selection.activeGameObject == null) return;

        SkinnedMeshRenderer[] SMRs;

        int vertCount = 0;
        int normCount = 0;
        int tanCount = 0;
        int triCount = 0;
        int uvCount = 0;
        int boneCount = 0;
        int bpCount = 0;
        int bwCount = 0;

        Transform[] bones;
        Matrix4x4[] bindPoses;
        BoneWeight[] weights;

        Vector3[] verts;
        Vector3[] norms;
        Vector4[] tans;
        int[] tris;
        Vector2[] uvs;
        Texture2D[] textures;

        int vertOffset = 0;
        int normOffset = 0;
        int tanOffset = 0;
        int triOffset = 0;
        int uvOffset = 0;
        int meshOffset = 0;
    
        int  boneSplit = 0;
        int bNum = 0;

        int[] bCount;

        SMRs = Selection.activeGameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
        foreach (SkinnedMeshRenderer smr in SMRs){
            vertCount += smr.sharedMesh.vertices.Length;
            normCount += smr.sharedMesh.normals.Length;
            tanCount += smr.sharedMesh.tangents.Length;
            triCount += smr.sharedMesh.triangles.Length;
            uvCount += smr.sharedMesh.uv.Length;
            boneCount += smr.bones.Length;
            bpCount += smr.sharedMesh.bindposes.Length;
            bwCount += smr.sharedMesh.boneWeights.Length;
            bNum++;
        }
        bCount = new int[3];
        bones = new Transform[boneCount];
        weights = new BoneWeight[bwCount];
        bindPoses = new Matrix4x4[bpCount];
        textures = new Texture2D[bNum];
        
        foreach (SkinnedMeshRenderer smr in SMRs){
            for(int b1 = 0; b1 < smr.bones.Length; b1++){
                bones[bCount[0]] = smr.bones[b1];
                bCount[0]++;
            }
            for(int b2 = 0; b2 < smr.sharedMesh.boneWeights.Length; b2++){
                weights[bCount[1]] = smr.sharedMesh.boneWeights[b2];
                weights[bCount[1]].boneIndex0 += boneSplit;
                weights[bCount[1]].boneIndex1 += boneSplit;
                weights[bCount[1]].boneIndex2 += boneSplit;
                weights[bCount[1]].boneIndex3 += boneSplit;
                bCount[1]++;
            }
            for(int b3 = 0; b3 < smr.sharedMesh.bindposes.Length; b3++){
                bindPoses[bCount[2]] = smr.sharedMesh.bindposes[b3];
                bCount[2]++;
            }
            boneSplit += smr.bones.Length;
        }
        verts = new Vector3[vertCount];
        norms = new Vector3[normCount];
        tans = new Vector4[tanCount];
        tris = new int[triCount];
        uvs = new Vector2[uvCount];
        
        foreach (SkinnedMeshRenderer smr in SMRs){
            foreach (int i in smr.sharedMesh.triangles){
                tris[triOffset++] = i + vertOffset;
            }
            foreach (Vector3 v in smr.sharedMesh.vertices){
                verts[vertOffset++] = v;
            }
            foreach (Vector3 n in smr.sharedMesh.normals){
                norms[normOffset++] = n;
            }
            foreach (Vector4 t in smr.sharedMesh.tangents){
                tans[tanOffset++] = t;
            }
            foreach (Vector2 uv in smr.sharedMesh.uv){
                uvs[uvOffset++] = uv;
            }
            textures[meshOffset] = (Texture2D) smr.sharedMaterial.mainTexture;
            string path = AssetDatabase.GetAssetPath (smr.sharedMaterial.mainTexture);
            TextureImporter imp = (TextureImporter) AssetImporter.GetAtPath (path);
            if (!imp.isReadable) {
                imp.isReadable = true;
                AssetDatabase.Refresh ();
                AssetDatabase.ImportAsset (path);
            }
            meshOffset++;
            smr.enabled = false;
        }

        Texture2D tx = new Texture2D (1,1);
        Rect[] r = tx.PackTextures (textures, 0, maxAtlasSize);
        File.WriteAllBytes (Application.dataPath + "/" + Selection.activeGameObject.name + ".png", tx.EncodeToPNG());
        AssetDatabase.Refresh ();
        tx = (Texture2D) AssetDatabase.LoadAssetAtPath ("Assets/" + Selection.activeGameObject.name + ".png", typeof(Texture2D)); 

        uvOffset = 0;
        meshOffset = 0;
        foreach (SkinnedMeshRenderer smr in SMRs) {
            foreach (Vector2 uv in smr.sharedMesh.uv) {
                uvs[uvOffset].x = Mathf.Lerp (r[meshOffset].xMin, r[meshOffset].xMax, uv.x);
                uvs[uvOffset].y = Mathf.Lerp (r[meshOffset].yMin, r[meshOffset].yMax, uv.y);
                uvOffset ++;
            }
            meshOffset ++;
        }

        Material mat = new Material (Shader.Find("Diffuse"));
        mat.mainTexture = tx;
        AssetDatabase.CreateAsset(mat, "Assets/" + Selection.activeGameObject.name + ".mat");

        //New Mesh
        Mesh me = new Mesh();
        me.name = Selection.activeGameObject.name;
        me.vertices = verts;
        me.normals = norms;
        me.tangents = tans;
        me.boneWeights = weights;
        me.uv = uvs;
        me.triangles = tris;
        AssetDatabase.CreateAsset(me, "Assets/" + Selection.activeGameObject.name + "mesh.asset");
        me.bindposes = bindPoses;
    
        SkinnedMeshRenderer newSMR = Selection.activeGameObject.AddComponent<SkinnedMeshRenderer>();
    
        newSMR.sharedMesh = me;
        newSMR.bones = bones;
        newSMR.updateWhenOffscreen = true;
        Selection.activeGameObject.renderer.material = mat;
    }
}

Most definitely going to help. Thanks a lot.

Hi there.
I was really hopping for something like this and your script looks like a life saver…
…but, it’s not working for me right now. It gives this message when I try to use it…

InvalidCastException: Cannot cast from source type to destination type.
EditorCombine.CombineTheMeshes () (at Assets/Editor/EditorCombine .cs:213)

Am I doing something wrong… or…

Edit: Hm I think it’s probably because the material as no texture associated. Will change the materials and try again

You’re right. Its because there aren’t textures. I forgot that I meant to write a workaround to that… its a little more complicated since once its combined it will get a texture. So its still probably better if you decide a texture yourself, even if it’s a solid white or something.

Hm I’ve tried with different models and it generates 3 files in the root as you said but the model it generates it’s empty… Again… Am I doing something wrong?

are there any errors? You could perhaps try adding the skinmeshrenderer component manually and add in the generated mesh and material. I haven’t tried this in very many cases… so there could be something else I missed.

Anyone succeeded with this script?

If you feel like sending a sample model, id be happy to try it and see what’s wrong.

I tried to use your script with Arteria’s Medieval Base Model Pack, but when combine the UV of newly generated Skinned Mesh Renderer is mess up. Any solution?

I tested using Debug.Log and UV coordinates of old Skinned Meshes are outside the range of 0 to 1, weird.

Normally you put the ranges outside 0 to 1 when repeating a texture multiple times along an object. I’m not sure if that’s really what they are doing, but that would create a problem for texture combining. I’m not sure if there’s a solution…

I solved the problem just add lines checking if the values are outside of [0,1]. If so, just add or subtract 1 until it is in range. And this script can be change to make it combine script on the fly during runtime easily. I have to untick “Play automatically” or something on animation component to make the animation work though.

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;

public class CombineSkinnedMeshesWithAtlas : MonoBehaviour {

    public int maxAtlasSize = 2048; 
	private bool initFlag;
	
	void Start () {
		initFlag = true;	
	}
	
	void Update () {
		if (initFlag) {
			CombineTheMeshes(gameObject);
			initFlag = false;
		}
		else animation.CrossFade("Run");
	}
    
	void CombineTheMeshes(GameObject source){
        if (source == null) return;

        SkinnedMeshRenderer[] SMRs;

        int vertCount = 0;
        int normCount = 0;
        int tanCount = 0;
        int triCount = 0;
        int uvCount = 0;
        int boneCount = 0;
        int bpCount = 0;
        int bwCount = 0;

        Transform[] bones;
        Matrix4x4[] bindPoses;
        BoneWeight[] weights;

        Vector3[] verts;
        Vector3[] norms;
        Vector4[] tans;
        int[] tris;
        Vector2[] uvs;
        Texture2D[] textures;

        int vertOffset = 0;
        int normOffset = 0;
        int tanOffset = 0;
        int triOffset = 0;
        int uvOffset = 0;
        int meshOffset = 0;

        int  boneSplit = 0;
        int bNum = 0;

        int[] bCount;

        SMRs = source.GetComponentsInChildren<SkinnedMeshRenderer>();
        foreach (SkinnedMeshRenderer smr in SMRs){
            vertCount += smr.sharedMesh.vertices.Length;
            normCount += smr.sharedMesh.normals.Length;
            tanCount += smr.sharedMesh.tangents.Length;
            triCount += smr.sharedMesh.triangles.Length;
            uvCount += smr.sharedMesh.uv.Length;
            boneCount += smr.bones.Length;
            bpCount += smr.sharedMesh.bindposes.Length;
            bwCount += smr.sharedMesh.boneWeights.Length;
            bNum++;
        }
        bCount = new int[3];
        bones = new Transform[boneCount];
        weights = new BoneWeight[bwCount];
        bindPoses = new Matrix4x4[bpCount];
        textures = new Texture2D[bNum];

        foreach (SkinnedMeshRenderer smr in SMRs){
            for(int b1 = 0; b1 < smr.bones.Length; b1++){
                bones[bCount[0]] = smr.bones[b1];
                bCount[0]++;
            }
            for(int b2 = 0; b2 < smr.sharedMesh.boneWeights.Length; b2++){
                weights[bCount[1]] = smr.sharedMesh.boneWeights[b2];
                weights[bCount[1]].boneIndex0 += boneSplit;
                weights[bCount[1]].boneIndex1 += boneSplit;
                weights[bCount[1]].boneIndex2 += boneSplit;
                weights[bCount[1]].boneIndex3 += boneSplit;
                bCount[1]++;
            }
            for(int b3 = 0; b3 < smr.sharedMesh.bindposes.Length; b3++){
                bindPoses[bCount[2]] = smr.sharedMesh.bindposes[b3];
                bCount[2]++;
            }
            boneSplit += smr.bones.Length;
        }
        verts = new Vector3[vertCount];
        norms = new Vector3[normCount];
        tans = new Vector4[tanCount];
        tris = new int[triCount];
        uvs = new Vector2[uvCount];

        foreach (SkinnedMeshRenderer smr in SMRs){
            foreach (int i in smr.sharedMesh.triangles){
                tris[triOffset++] = i + vertOffset;
            }
            foreach (Vector3 v in smr.sharedMesh.vertices){
                verts[vertOffset++] = v;
            }
            foreach (Vector3 n in smr.sharedMesh.normals){
                norms[normOffset++] = n;
            }
            foreach (Vector4 t in smr.sharedMesh.tangents){
                tans[tanOffset++] = t;
            }
            foreach (Vector2 uv in smr.sharedMesh.uv){
                uvs[uvOffset++] = uv;				
            }
            textures[meshOffset] = (Texture2D) smr.sharedMaterial.mainTexture;
            string path = AssetDatabase.GetAssetPath (smr.sharedMaterial.mainTexture);
            TextureImporter imp = (TextureImporter) AssetImporter.GetAtPath (path);
            if (!imp.isReadable) {
                imp.isReadable = true;
                AssetDatabase.Refresh ();
                AssetDatabase.ImportAsset (path);
            }
            meshOffset++;
            smr.enabled = false;
        }

        Texture2D tx = new Texture2D (1,1);
        Rect[] r = tx.PackTextures (textures, 0, maxAtlasSize);
        File.WriteAllBytes (Application.dataPath + "/" + source.name + ".png", tx.EncodeToPNG());
        AssetDatabase.Refresh ();
        tx = (Texture2D) AssetDatabase.LoadAssetAtPath ("Assets/" + source.name + ".png", typeof(Texture2D)); 

        uvOffset = 0;
        meshOffset = 0;
        foreach (SkinnedMeshRenderer smr in SMRs) {
            foreach (Vector2 uv in smr.sharedMesh.uv) {
				Vector2 uvClamped = new Vector2();
				
				uvClamped = uv;
				
				while (uvClamped.x > 1)
					uvClamped.x = uvClamped.x - 1;
				
				while (uvClamped.x < 0)
					uvClamped.x = uvClamped.x + 1;
				
				while (uvClamped.y > 1)
					uvClamped.y = uvClamped.y - 1;
				
				while (uvClamped.x < 0)
					uvClamped.y = uvClamped.y + 1;
				
                uvs[uvOffset].x = Mathf.Lerp (r[meshOffset].xMin, r[meshOffset].xMax, uvClamped.x);				
                uvs[uvOffset].y = Mathf.Lerp (r[meshOffset].yMin, r[meshOffset].yMax, uvClamped.y);				
                uvOffset ++;
            }
            meshOffset ++;
        }

        Material mat = new Material (Shader.Find("Diffuse"));
        mat.mainTexture = tx;
        AssetDatabase.CreateAsset(mat, "Assets/" + source.name + ".mat");

        //New Mesh
        Mesh me = new Mesh();
        me.name = source.name;
        me.vertices = verts;
        me.normals = norms;
        me.tangents = tans;
        me.boneWeights = weights;
        me.uv = uvs;
        me.triangles = tris;
        AssetDatabase.CreateAsset(me, "Assets/" + source.name + "mesh.asset");
        me.bindposes = bindPoses;
        SkinnedMeshRenderer newSMR = source.AddComponent<SkinnedMeshRenderer>();

        newSMR.sharedMesh = me;
        newSMR.bones = bones;
        newSMR.updateWhenOffscreen = true;
        source.renderer.material = mat;
    }
}

Here is another adaptation of duhprey’s adaptation. This version of the script doesn’t do any atlasing; it just puts all original materials under the same combined renderer, which is useful if you want to be able to swap the texture of, say, a shirt or skin color without changing the rest. The atlasing code is just commented in case someone wants to add it back in.

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;

public class EditorCombine : MonoBehaviour {

//    static public int maxAtlasSize = 2048;

    [MenuItem ("Assets/CombineSkinnedMeshes")]

    static void CombineTheMeshes(){

        if (Selection.activeGameObject == null) return;

        SkinnedMeshRenderer[] SMRs;

        int vertCount = 0;
        int normCount = 0;
        int tanCount = 0;
        int triCount = 0;
        int uvCount = 0;
        int boneCount = 0;
        int bpCount = 0;
        int bwCount = 0;

        Transform[] bones;
        Matrix4x4[] bindPoses;
        BoneWeight[] weights;

        Vector3[] verts;
        Vector3[] norms;
        Vector4[] tans;
        Vector2[] uvs;
//      Texture2D[] textures;
        List<int[]> subMeshes;
        Material[] mats;

        int vertOffset = 0;
        int normOffset = 0;
        int tanOffset = 0;
        int triOffset = 0;
        int uvOffset = 0;
        int meshOffset = 0;

        int boneSplit = 0;
        int bNum = 0;

        int[] bCount;

        SMRs = Selection.activeGameObject.GetComponentsInChildren<SkinnedMeshRenderer>();

        foreach (SkinnedMeshRenderer smr in SMRs) {
            vertCount += smr.sharedMesh.vertices.Length;
            normCount += smr.sharedMesh.normals.Length;
            tanCount += smr.sharedMesh.tangents.Length;
            triCount += smr.sharedMesh.triangles.Length;
            uvCount += smr.sharedMesh.uv.Length;
            boneCount += smr.bones.Length;
            bpCount += smr.sharedMesh.bindposes.Length;
            bwCount += smr.sharedMesh.boneWeights.Length;
            bNum++;
        }

        bCount = new int[3];
        
        bones = new Transform[boneCount];
        weights = new BoneWeight[bwCount];
        bindPoses = new Matrix4x4[bpCount];
//      textures = new Texture2D[bNum];
        mats = new Material[bNum];

        foreach (SkinnedMeshRenderer smr in SMRs) {
        	
        	// Load bone transforms
            for(int b1 = 0; b1 < smr.bones.Length; b1++) {
            	
                bones[bCount[0]] = smr.bones[b1];
                bCount[0]++;
            }

			// Load bone weights
            for(int b2 = 0; b2 < smr.sharedMesh.boneWeights.Length; b2++){

                weights[bCount[1]] = smr.sharedMesh.boneWeights[b2];
                weights[bCount[1]].boneIndex0 += boneSplit;
                weights[bCount[1]].boneIndex1 += boneSplit;
                weights[bCount[1]].boneIndex2 += boneSplit;
                weights[bCount[1]].boneIndex3 += boneSplit;

                bCount[1]++;
            }

			// Load bone bindposes
            for(int b3 = 0; b3 < smr.sharedMesh.bindposes.Length; b3++){

                bindPoses[bCount[2]] = smr.sharedMesh.bindposes[b3];
                bCount[2]++;
            }

            boneSplit += smr.bones.Length;
        }

        verts = new Vector3[vertCount];
        norms = new Vector3[normCount];
        tans = new Vector4[tanCount];
        subMeshes = new List<int[]>();
        uvs = new Vector2[uvCount];

        foreach (SkinnedMeshRenderer smr in SMRs){
			
			int[] theseTris = new int[smr.sharedMesh.triangles.Length];
            foreach (int i in smr.sharedMesh.triangles){
                theseTris[triOffset++] = i + vertOffset;
            }
            
            subMeshes.Add(theseTris);
			triOffset = 0;

            foreach (Vector3 v in smr.sharedMesh.vertices){
                verts[vertOffset++] = v;
            }

            foreach (Vector3 n in smr.sharedMesh.normals){
                norms[normOffset++] = n;
            }

            foreach (Vector4 t in smr.sharedMesh.tangents){
                tans[tanOffset++] = t;
            }

            foreach (Vector2 uv in smr.sharedMesh.uv){
                uvs[uvOffset++] = uv;
            }
            
            mats[meshOffset] = smr.sharedMaterial;
            

//            textures[meshOffset] = (Texture2D) smr.sharedMaterial.mainTexture;
//            string path = AssetDatabase.GetAssetPath (smr.sharedMaterial.mainTexture);
//            TextureImporter imp = (TextureImporter) AssetImporter.GetAtPath (path);
//
//            if (!imp.isReadable) {
//                imp.isReadable = true;
//                AssetDatabase.Refresh ();
//                AssetDatabase.ImportAsset (path);
//            }
			
            meshOffset++;

            smr.enabled = false;
        }

 

//        Texture2D tx = new Texture2D (1,1);
//        Rect[] r = tx.PackTextures (textures, 0, maxAtlasSize);
//        File.WriteAllBytes (Application.dataPath + "/" + Selection.activeGameObject.name + ".png", tx.EncodeToPNG());
//        AssetDatabase.Refresh ();
//        tx = (Texture2D) AssetDatabase.LoadAssetAtPath ("Assets/" + Selection.activeGameObject.name + ".png", typeof(Texture2D)); 

//        uvOffset = 0;
//        meshOffset = 0;
//        foreach (SkinnedMeshRenderer smr in SMRs) {
//
//            foreach (Vector2 uv in smr.sharedMesh.uv) {
//            	
//                uvs[uvOffset].x = Mathf.Lerp (r[meshOffset].xMin, r[meshOffset].xMax, uv.x);
//                uvs[uvOffset].y = Mathf.Lerp (r[meshOffset].yMin, r[meshOffset].yMax, uv.y);
//
//                uvOffset ++;
//            }
//
//            meshOffset ++;
//        }

 

//        Material mat = new Material (Shader.Find("Diffuse"));
//        mat.mainTexture = tx;
//
//        AssetDatabase.CreateAsset(mat, "Assets/" + Selection.activeGameObject.name + ".mat");

 

        //New Mesh

        Mesh me = new Mesh();
        me.name = Selection.activeGameObject.name;
        me.vertices = verts;
        me.normals = norms;
        me.tangents = tans;
        me.boneWeights = weights;
        me.uv = uvs;
        me.subMeshCount = subMeshes.Count;
        
        for( int subMesh = 0; subMesh < subMeshes.Count; subMesh++ ) {
        	me.SetTriangles( subMeshes[subMesh], subMesh );
        }

        AssetDatabase.CreateAsset(me, "Assets/" + Selection.activeGameObject.name + "mesh.asset");

        me.bindposes = bindPoses;

        SkinnedMeshRenderer newSMR = Selection.activeGameObject.AddComponent<SkinnedMeshRenderer>();

        newSMR.sharedMesh = me;
        newSMR.bones = bones;
        newSMR.updateWhenOffscreen = true;
        
        Selection.activeGameObject.renderer.sharedMaterials = mats;

//        Selection.activeGameObject.renderer.material = mat;
    }

}

… and another. This version atlases at runtime, incorporates sunpats’ fix (with a somewhat more graceful modulus), and can accommodate normal maps. Also, it reminds any Mecanim animator that it has a new renderer bounds; otherwise, animation stops working.

using UnityEngine;
using System.Collections;
using System.IO;
 
public class CombineAndAtlas : MonoBehaviour {
	
	public bool hasNormalMaps = true;
	
    int maxAtlasSize = 2048;

    void Start() {

        SkinnedMeshRenderer[] SMRs;
        
        int vertCount = 0;
        int normCount = 0;
        int tanCount = 0;
        int triCount = 0;
        int uvCount = 0;
        int boneCount = 0;
        int bpCount = 0;
        int bwCount = 0;

        Transform[] bones;
        Matrix4x4[] bindPoses;
        BoneWeight[] weights;

        Vector3[] verts;
        Vector3[] norms;
        Vector4[] tans;
        int[] tris;
        Vector2[] uvs;
        Texture2D[] textures;
        Texture2D[] normalmaps;

        int vertOffset = 0;
        int normOffset = 0;
        int tanOffset = 0;
        int triOffset = 0;
        int uvOffset = 0;
        int meshOffset = 0;

        int  boneSplit = 0;
        int bNum = 0;

        int[] bCount;

        SMRs = GetComponentsInChildren<SkinnedMeshRenderer>();

        foreach (SkinnedMeshRenderer smr in SMRs) {
            vertCount += smr.sharedMesh.vertices.Length;
            normCount += smr.sharedMesh.normals.Length;
            tanCount += smr.sharedMesh.tangents.Length;
            triCount += smr.sharedMesh.triangles.Length;
            uvCount += smr.sharedMesh.uv.Length;
            boneCount += smr.bones.Length;
            bpCount += smr.sharedMesh.bindposes.Length;
            bwCount += smr.sharedMesh.boneWeights.Length;
            bNum++;
        }

        bCount = new int[3];
        bones = new Transform[boneCount];
        weights = new BoneWeight[bwCount];
        bindPoses = new Matrix4x4[bpCount];
        textures = new Texture2D[bNum];
        normalmaps = new Texture2D[bNum];
        
        foreach (SkinnedMeshRenderer smr in SMRs) {

            for(int b1 = 0; b1 < smr.bones.Length; b1++) {
                bones[bCount[0]] = smr.bones[b1];
                
                bCount[0]++;
            }

            for(int b2 = 0; b2 < smr.sharedMesh.boneWeights.Length; b2++) {
                weights[bCount[1]] = smr.sharedMesh.boneWeights[b2];
                weights[bCount[1]].boneIndex0 += boneSplit;
                weights[bCount[1]].boneIndex1 += boneSplit;
                weights[bCount[1]].boneIndex2 += boneSplit;
                weights[bCount[1]].boneIndex3 += boneSplit;

                bCount[1]++;
            }

            for(int b3 = 0; b3 < smr.sharedMesh.bindposes.Length; b3++) {
                bindPoses[bCount[2]] = smr.sharedMesh.bindposes[b3];

                bCount[2]++;
            }

            boneSplit += smr.bones.Length;
        }

        verts = new Vector3[vertCount];
        norms = new Vector3[normCount];
        tans = new Vector4[tanCount];
        tris = new int[triCount];
        uvs = new Vector2[uvCount];

        foreach (SkinnedMeshRenderer smr in SMRs) {
        	
            foreach (int i in smr.sharedMesh.triangles) {
                tris[triOffset++] = i + vertOffset;
            }

            foreach (Vector3 v in smr.sharedMesh.vertices) {
                verts[vertOffset++] = v;
            }

            foreach (Vector3 n in smr.sharedMesh.normals) {
                norms[normOffset++] = n;
            }

            foreach (Vector4 t in smr.sharedMesh.tangents) {
                tans[tanOffset++] = t;
            }

            foreach (Vector2 uv in smr.sharedMesh.uv) {
                uvs[uvOffset++] = uv;
            }

            textures[meshOffset] = (Texture2D) smr.sharedMaterial.GetTexture("_MainTex");
            if( hasNormalMaps ) normalmaps[meshOffset] = (Texture2D) smr.sharedMaterial.GetTexture("_BumpMap");

            meshOffset++;

            Destroy( smr.gameObject );
        }

        Texture2D tx = new Texture2D (1,1);
        Rect[] r = tx.PackTextures (textures, 0, maxAtlasSize);
        
	    Texture2D nm = new Texture2D (1,1);
	    
	    if( hasNormalMaps ) {
	        nm.PackTextures (normalmaps, 0, maxAtlasSize);
        }
 
        uvOffset = 0;
        meshOffset = 0;

        foreach (SkinnedMeshRenderer smr in SMRs) {

            foreach (Vector2 uv in smr.sharedMesh.uv) {
                uvs[uvOffset].x = Mathf.Lerp (r[meshOffset].xMin, r[meshOffset].xMax, uv.x % 1);
                uvs[uvOffset].y = Mathf.Lerp (r[meshOffset].yMin, r[meshOffset].yMax, uv.y % 1);

                uvOffset ++;
            }

            meshOffset ++;
        }

        Material mat;
        if( hasNormalMaps ) mat = new Material( Shader.Find("Bumped Diffuse") );
        else mat = new Material( Shader.Find("Diffuse") );
        
        mat.SetTexture("_MainTex", tx);
        if( hasNormalMaps ) mat.SetTexture("_BumpMap", nm);

        //New Mesh
        Mesh me = new Mesh();
        me.name = gameObject.name;
        me.vertices = verts;
        me.normals = norms;
        me.tangents = tans;
        me.boneWeights = weights;
        me.uv = uvs;
        me.triangles = tris;

        me.bindposes = bindPoses;

        SkinnedMeshRenderer newSMR = gameObject.AddComponent<SkinnedMeshRenderer>();

        newSMR.sharedMesh = me;
        newSMR.bones = bones;
        newSMR.updateWhenOffscreen = true;

        renderer.material = mat;
        
        // reset animator culling mode so that Mecanim recognizes renderer bounds again
        Animator animator = GetComponent<Animator>();
        if( animator ) {
        	animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
        	animator.cullingMode = AnimatorCullingMode.BasedOnRenderers;
        }
    }
}

I modified a little your code because it gave me problems with normals, also i added multiple options, thanks for your script sir ^^.

using UnityEngine;

using System.Collections;

using System.IO;

 

public class CombineAndAtlas : MonoBehaviour {

    
	
    public bool hasNormalMaps = true;
	public bool inverseNormals = true;
	public bool destroy = false;
	public bool enable = true;
    

    int maxAtlasSize = 2048;

 

    void Start() {

 

        SkinnedMeshRenderer[] SMRs;

        

        int vertCount = 0;

        int normCount = 0;

        int tanCount = 0;

        int triCount = 0;

        int uvCount = 0;

        int boneCount = 0;

        int bpCount = 0;

        int bwCount = 0;

 

        Transform[] bones;

        Matrix4x4[] bindPoses;

        BoneWeight[] weights;

 

        Vector3[] verts;

        Vector3[] norms;

        Vector4[] tans;

        int[] tris;

        Vector2[] uvs;

        Texture2D[] textures;

        Texture2D[] normalmaps;

 

        int vertOffset = 0;

        int normOffset = 0;

        int tanOffset = 0;

        int triOffset = 0;

        int uvOffset = 0;

        int meshOffset = 0;

 

        int  boneSplit = 0;

        int bNum = 0;

 

        int[] bCount;

 

        SMRs = GetComponentsInChildren<SkinnedMeshRenderer>();

 

        foreach (SkinnedMeshRenderer smr in SMRs) {

            vertCount += smr.sharedMesh.vertices.Length;

            normCount += smr.sharedMesh.normals.Length;

            tanCount += smr.sharedMesh.tangents.Length;

            triCount += smr.sharedMesh.triangles.Length;

            uvCount += smr.sharedMesh.uv.Length;

            boneCount += smr.bones.Length;

            bpCount += smr.sharedMesh.bindposes.Length;

            bwCount += smr.sharedMesh.boneWeights.Length;

            bNum++;

        }

 

        bCount = new int[3];

        bones = new Transform[boneCount];

        weights = new BoneWeight[bwCount];

        bindPoses = new Matrix4x4[bpCount];

        textures = new Texture2D[bNum];

        normalmaps = new Texture2D[bNum];

        

        foreach (SkinnedMeshRenderer smr in SMRs) {

 

            for(int b1 = 0; b1 < smr.bones.Length; b1++) {

                bones[bCount[0]] = smr.bones[b1];

                

                bCount[0]++;

            }

 

            for(int b2 = 0; b2 < smr.sharedMesh.boneWeights.Length; b2++) {

                weights[bCount[1]] = smr.sharedMesh.boneWeights[b2];

                weights[bCount[1]].boneIndex0 += boneSplit;

                weights[bCount[1]].boneIndex1 += boneSplit;

                weights[bCount[1]].boneIndex2 += boneSplit;

                weights[bCount[1]].boneIndex3 += boneSplit;

 

                bCount[1]++;

            }

 

            for(int b3 = 0; b3 < smr.sharedMesh.bindposes.Length; b3++) {

                bindPoses[bCount[2]] = smr.sharedMesh.bindposes[b3];

 

                bCount[2]++;

            }

 

            boneSplit += smr.bones.Length;

        }

 

        verts = new Vector3[vertCount];

        norms = new Vector3[normCount];

        tans = new Vector4[tanCount];

        tris = new int[triCount];

        uvs = new Vector2[uvCount];

 

        foreach (SkinnedMeshRenderer smr in SMRs) {

            

            foreach (int i in smr.sharedMesh.triangles) {

                tris[triOffset++] = i + vertOffset;

            }

 

            foreach (Vector3 v in smr.sharedMesh.vertices) {

                verts[vertOffset++] = v;

            }

 

            foreach (Vector3 n in smr.sharedMesh.normals) {

                norms[normOffset++] = n;

            }

 

            foreach (Vector4 t in smr.sharedMesh.tangents) {

                tans[tanOffset++] = t;

            }

 

            foreach (Vector2 uv in smr.sharedMesh.uv) {

                uvs[uvOffset++] = uv;

            }

 

            textures[meshOffset] = (Texture2D) smr.sharedMaterial.GetTexture("_MainTex");

            if( hasNormalMaps ) normalmaps[meshOffset] = (Texture2D) smr.sharedMaterial.GetTexture("_BumpMap");

 

            meshOffset++;
 		
			if(destroy  !enable){
			Destroy(smr);	
			}
			
 			if(enable  !destroy){
			smr.enabled = false;	
			}
 			

          

        }

 

        Texture2D tx = new Texture2D (1,1);

        Rect[] r = tx.PackTextures (textures, 0, maxAtlasSize);

        

        Texture2D nm = new Texture2D (1,1);

        

        if( hasNormalMaps ) {

            nm.PackTextures (normalmaps, 0, maxAtlasSize);

        }

 

        uvOffset = 0;

        meshOffset = 0;

 

        foreach (SkinnedMeshRenderer smr in SMRs) {

 

            foreach (Vector2 uv in smr.sharedMesh.uv) {

                uvs[uvOffset].x = Mathf.Lerp (r[meshOffset].xMin, r[meshOffset].xMax, uv.x % 1);

                uvs[uvOffset].y = Mathf.Lerp (r[meshOffset].yMin, r[meshOffset].yMax, uv.y % 1);

 

                uvOffset ++;

            }

 

            meshOffset ++;

        }

 

        Material mat;

        if( hasNormalMaps ) mat = new Material( Shader.Find("Bumped Diffuse") );

        else mat = new Material( Shader.Find("Diffuse") );

        

        mat.SetTexture("_MainTex", tx);

        if( hasNormalMaps ) mat.SetTexture("_BumpMap", nm);

 

        //New Mesh

        Mesh me = new Mesh();

        me.name = gameObject.name;

        me.vertices = verts;

        me.normals = norms;

        me.tangents = tans;

        me.boneWeights = weights;

        me.uv = uvs;

        me.triangles = tris;

 

        me.bindposes = bindPoses;

 

        SkinnedMeshRenderer newSMR = gameObject.AddComponent<SkinnedMeshRenderer>();

 

        newSMR.sharedMesh = me;

        newSMR.bones = bones;

        newSMR.updateWhenOffscreen = true;

 

        renderer.material = mat;

     if(inverseNormals){
 
			Vector3[] normals = me.normals;
			for (int i=0;i<normals.Length;i++)
				normals[i] = -normals[i];
			me.normals = normals;
 
			for (int m=0;m<me.subMeshCount;m++)
			{
				int[] triangles = me.GetTriangles(m);
				for (int i=0;i<triangles.Length;i+=3)
				{
					int temp = triangles[i + 0];
					triangles[i + 0] = triangles[i + 1];
					triangles[i + 1] = temp;
				}
				me.SetTriangles(triangles, m);
			}
				
		}
		
		
		
	

        // reset animator culling mode so that Mecanim recognizes renderer bounds again

        Animator animator = GetComponent<Animator>();

        if( animator ) {

            animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;

            animator.cullingMode = AnimatorCullingMode.BasedOnRenderers;

        }

    }

}

Great this all scripts doing wrk well properly.

Forgive my ignorance but how do I apply this? Can’t drop the script on the character and can’t see it in the menu’s.

Why can’t you drop the script on your character? You need copy and paste the code into a C# script called CombineAndAtlas first, of course.

I missed the public class naming, and got Sanpaths version working through the asset menu, and now seems like I can get your versions too, Thanks for replying.

CombineAndAtlas works pretty well :slight_smile: Thank you people! I’m dropping it to a container with lots of the same SkinnedMeshes and it combines them to one reducing a lot of draw calls.