Search Unity

Material is lost when saving a prefab from script

Discussion in 'Prefabs' started by BurnsU, Jun 7, 2020.

  1. BurnsU

    BurnsU

    Joined:
    Dec 31, 2014
    Posts:
    2
    Hi, I am doing the following:
    I have a 2D sprite pixel art game, with all animations working, and I wanted to try to voxelize it (I think would be cool to see the pixels as cubes in 3D, and apply 3D lighting, shadws, etc), i.e. create a cube for each pixel of each sprite.
    I first thought in creating a prefab for each sprite of the 2D animation. Create an empty game object, and for each pixel, create a cube, add a "sprites/default" material, and change its color to the color of the pixel, and set the empty object as the father of all cubes. And save it as a prefab.
    Here is my code (this function is called at the start method of every gameobject that has an sprite renderer):

    Code (CSharp):
    1. public void AllCubes(SpriteRenderer sprite)
    2.     {
    3.         Texture2D pixels=sprite.sprite.texture;
    4.         GameObject gameobject_i;
    5.  
    6.         string path = AssetDatabase.GetAssetPath(pixels);
    7.         TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
    8.         ti.isReadable = true;
    9.         List < SpriteMetaData > newData = new List < SpriteMetaData > ();
    10.         for (int i = 0; i < ti.spritesheet.Length; i++) {
    11.             SpriteMetaData d = ti.spritesheet[i];
    12.             gameobject_i = new GameObject(d.name);
    13.             int xi=(int)d.rect.x;
    14.             int xf=xi+(int)d.rect.width;
    15.             int yi=(int)d.rect.y;
    16.             int yf=yi+(int)d.rect.height;
    17.  
    18.             for (int x = xi; x < xf; x++){
    19.                 for (int y = yi; y < yf; y++){
    20.                     Color pixelColor = pixels.GetPixel(x, y);
    21.                     if (pixelColor.a!=0){
    22.                         GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    23.                         cube.transform.position = new Vector3(x-xi,y-yi, 0);
    24.                         Renderer rend = cube.GetComponent<Renderer> ();
    25.                         rend.material = new Material(Shader.Find("Sprites/Default"));
    26.                         rend.material.color = pixelColor;
    27.                         cube.transform.parent=gameobject_i.transform;
    28.                         }                      
    29.                 }
    30.             }
    31.              
    32.             string prefabPath = "Assets/Prefabs/Voxels/"+d.name+".prefab";
    33.             AssetDatabase.DeleteAsset(prefabPath);//if already exists    
    34.        PrefabUtility.SaveAsPrefabAsset(gameobject_i,prefabPath,out success);
    35.             //Destroy(gameobject_i);
    36.             if (success){
    37.                 Debug.Log(""+prefabPath+" saved.");
    38.             }          
    39.         }
    However, when I load the prefab as an game object, it appears as all cubes in pink, and the material appears as none...
    I comment //Destroy(gameobject_i); to see the gameobject_i in game, if it is with the material. And it works, the material is attributed and the cubes appear with correct color.

    Why? Somehow, it is not saving the material o the prefab...
    Am I doing something wrong?

    Thanks in advance.
     
  2. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    When you access Renderer.material and then changes the material, it creates a unique clone of the material used just for that renderer. This is described here:
    https://docs.unity3d.com/ScriptReference/Renderer-material.html

    Materials and other asset types are normally stored in the Project as files and are visible in the Project view. Such materials can be saved into a Prefab. However, when you modify a material on a MeshRenderer in the scene, you are creating a new Material clone which only lives in the scene and not in the Project. References to these cannot be applied to a Prefab Asset because assets can't reference objects from a scene.

    You'll have to save every material to the Project first before you can apply those materials to the Prefab Asset.
     
    hellobody and BurnsU like this.
  3. No_Username_Found

    No_Username_Found

    Joined:
    Mar 25, 2014
    Posts:
    2
    For the next person to come here from Google:

    Per runevision, if you have an instanced object and change the instancedObject.renderer.material then you create an instance of that material, and in that case the material needs to be saved before you save the object. That means that for OP, each cube will have its own material even if the cubes are the same color.

    HOWEVER, if you change the instancedObject.renderer.sharedMaterial you do not create an instance of the material. Instead you assign a pointer that references the material from the database. When you save the object it will save the pointer and thus be able to find the material. All cubes that are blue point towards the same blue material rather than having their own instance of blue.

    So to OP, a sollution would be to have a folder filled with materials for each color you expect to use. Then when you make your model you need to change the color of a cube by assigning a sharedMaterial to the renderer.

    Just keep in mind that if you assign a sharedMaterial then any change you make to the material affects ALL objects that reference that material. That's handy if you realize that all your dark blue need to be slightly less dark, but it's annoying if you want only one cube to be less dark. So you have to plan ahead on what your project will be doing.
     
  4. Kinami37

    Kinami37

    Joined:
    Jun 12, 2016
    Posts:
    17
    Same problem,

    even assigning .sharedMaterial doesnt help.

    The created prefab has all the references and scripts and everything EXCEPT the material on the renderer.
    I tried everything, any ideas?
     
  5. ryuMagicLeap

    ryuMagicLeap

    Joined:
    Jul 12, 2021
    Posts:
    1
    I had a similar issue and here is the solution:
    When you try to programmatically save an object in the scene to a prefab, don't save something that is programmatically created or modified on the fly in Play Mode, otherwise materials will be lost. One easy way to avoid this is to do all these "saving prefab" logic in Editor script, without even entering Play Mode.

    My guess of the reason behind this is that when you click "Play" and then spawn or modify something in Play Mode, some temporary copy of the materials are created on the fly and attached to the object in the scene. These temporary copies are not persistent and you can't "save" it as a part of a Prefab, if that makes sense. This echoes the logic mentioned by @runevision
     
  6. igarrickbeth

    igarrickbeth

    Joined:
    Sep 22, 2020
    Posts:
    18
    Any new thoughts on this? Having the same problem within Editor mode - just trying to add the plain old "Sprites/Default" material and the gameobject is created fine in the scene with the material, but then the material gets removed upon saving the gameobject as a Prefab. Again, this is all entirely within Editor mode and using a standard project material. Stumped...
     
    DanGoes1 likes this.
  7. mesmes123

    mesmes123

    Joined:
    Oct 11, 2021
    Posts:
    9
    When loading asset bundle directly to project editor there is a weird behavior.
    I was struggling with the same problem (in editor) but after building the project the assets worked fine without any material problem.
    Have a carful look on the buildTarget you are aim for in your "create asset bundle" script & check after build if everything is ok.
     
    Last edited: Mar 3, 2022
    igarrickbeth likes this.