Search Unity

[SOLVED!] Save a model in animation's current frame position

Discussion in 'Animation' started by DroidifyDevs, Dec 10, 2016.

  1. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Hello!

    I was wondering if such a thing was possible. I'd like to animate a character, and save a separate model in the shape of the character during a frame of the animation. For example, if the character is walking, I'd like to save the model's mesh with one of his feet forward.

    Also, I guess I could call this "Saving a static mesh during a skinned mesh animation".

    Is this possible to do in Unity or some other software such as Blender?

    Thank you!
     
  2. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Never mind, I looked here and after some tinkering came up with this:

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. public class AnimationConverter : MonoBehaviour {
    8.  
    9.     //For example: Assets/_@MYSTUFF/StaticAnimations/
    10.     public string SaveLocation;
    11.     //create a component on your gameobject and add the
    12.     public Animation animation;
    13.     public SkinnedMeshRenderer SkinMeshRender;
    14.     private Mesh[] NewStaticMesh;
    15.     //length of animation clip
    16.     public float ClipLenth;
    17.     //how many frames do you want to make?
    18.     public int numberOfFrames;
    19.     //time between frame captures
    20.     private float WaitAmount;
    21.     //How many frames done BEFORE saving
    22.     private int AmountSoFar;
    23.     //how many frames SAVED
    24.     private int AmountSavedSoFar;
    25.     private Mesh ThisMesh;
    26.     public List<Mesh> MeshQ;
    27.  
    28.     private void Start()
    29.     {
    30.         Debug.Log("Exporting static meshes from skinned mesh...");
    31.         ExportMeshes();
    32.     }
    33.  
    34.     void ExportMeshes()
    35.     {
    36.         ClipLenth = animation.clip.length;
    37.         WaitAmount = animation.clip.length / numberOfFrames;
    38.         //now let's start waiting for the animation to play
    39.         StartCoroutine(WaitForNextMesh());
    40.     }
    41.  
    42.     void AddMeshToQ(Mesh NewMesh)
    43.     {
    44.         MeshQ.Add(NewMesh);
    45.     }
    46.  
    47.     //IMPORTANT: I'm using a coroutine because if I don't, I create
    48.     //the same static meshes because the animation won't change in 1 frame.
    49.     //The purpose is to wait as the animation is playing, then make a static mesh at the correct time.
    50.  
    51.     IEnumerator WaitForNextMesh()
    52.     {
    53.         yield return new WaitForSeconds(WaitAmount);
    54.         AmountSoFar++;
    55.         //wait done! Let's make the static mesh!
    56.         ThisMesh = new Mesh();
    57.         SkinMeshRender.BakeMesh(ThisMesh);
    58.         //now that's it's made, add it to the que
    59.         AddMeshToQ(ThisMesh);
    60.         if (AmountSoFar < numberOfFrames)
    61.         {
    62.             //do it again, we have more meshes to make!
    63.             StartCoroutine(WaitForNextMesh());
    64.         }
    65.  
    66.         else
    67.         {
    68.             //created all meshes, we're done!
    69.             foreach (Mesh staticmesh in MeshQ)
    70.             {
    71.                 AmountSavedSoFar++;
    72.                 //try to save to specified location
    73.                 try
    74.                 {
    75.                     AssetDatabase.CreateAsset(staticmesh, SaveLocation + AmountSavedSoFar.ToString() + animation.clip.name + "_StaticFromSkinned" + ".asset");
    76.                 }
    77.                 //if the location is invalid, throw an error
    78.                 catch
    79.                 {
    80.                     Debug.Log("<color=red><b>Invalid save location! Make sure you've spelled the path to a folder correctly. For example: Assets/_@MYSTUFF/StaticAnimations/ </b></color>");
    81.                     yield break;
    82.                 }
    83.             }
    84.             //spam the console in fancy ways
    85.             Debug.Log("<color=green><b>All meshes created! You'll find them here: </b></color>" + SaveLocation);
    86.             Debug.Log("<color=red><i>For some reason I can't explain, delete the first frame and the last 2 frames so they loop properly.</i></color>");
    87.             Debug.Log("<color=red><i>Don't forget to disable/change this script, or you'll do what you just did again!</i></color>");
    88.         }
    89.     }
    90. }
    91.  
    92.  
    Fully commented for future reference, free to use for everyone :D
     
    Last edited: Mar 29, 2017
  3. lod3

    lod3

    Joined:
    Mar 21, 2012
    Posts:
    676
    Thanks for this, @DroidifyDevs! For us artist types, how would I go about using this? :)
     
  4. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
    For us artist types the normal process is to take into any 3D package and create a snapshot mesh of the character mesh at the animation frame you desire.
    Although as a fellow artist type Id like to know how to use this as well. ;)
     
  5. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    @lod3 & @theANMATOR2b , I've used that a long time ago and even I have forgotten it a bit :(

    I'll probably make a video tomorrow, as this is perhaps the coolest thing I've created so far and I'm working on another project today.

    Also I'll update the script, as that one is flawed. Because it saves while the game is playing, it slows down the game and can only save a few frames per second. I have a new version that adds meshes to an array first, and then saves them once the animation is done.

    Sit tight!
     
    theANMATOR2b likes this.
  6. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    @lod3 & @theANMATOR2b I was really busy today and didn't have time to make a video. Sorry!!

    However I'll have a complete explanation here:

    InspectorScreenshot1.PNG

    That's pretty much what your object should look like.

    Breakdown:

    • Save Location: Pretty self-explanatory, that's where to save the static meshes.
    • Animation: Perhaps the trickiest part. The reason I require an Animation component is to get the animation clip length. So first add an Animation component, then in the Animation box select which animation you want to get the length of (in my case, WK_heavy_infantry_08_attack_B). It doesn't matter if the component is enabled or disabled, I like to keep it disabled since I suck at animations.
    • Skin Mesh Render: That's the skinned mesh renderer component. Simple drag on the gameObject that has the Skinned Mesh Renderer attached.
    • Clip Length: That's determined automatically by the Animation component you added.
    • Number of Frames: How many static to get out of 1 animation?
    NOTE: In order to have a perfect loop of static meshes, you must delete the first static mesh generated, and the last 2 generated. I don't know what causes that in my code, but it's there.

    If you have any questions, let me know and I'll help you out!

    I revised the code, here it is:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. public class AnimationConverter : MonoBehaviour {
    7.  
    8.     //For example: Assets/_@MYSTUFF/StaticAnimations/
    9.     public string SaveLocation;
    10.     //create a component on your gameobject and add the
    11.     public Animation animation;
    12.     public SkinnedMeshRenderer SkinMeshRender;
    13.     private Mesh[] NewStaticMesh;
    14.     //length of animation clip
    15.     public float ClipLenth;
    16.     //how many frames do you want to make?
    17.     public int numberOfFrames;
    18.     //time between frame captures
    19.     private float WaitAmount;
    20.     //How many frames done BEFORE saving
    21.     private int AmountSoFar;
    22.     //how many frames SAVED
    23.     private int AmountSavedSoFar;
    24.     private Mesh ThisMesh;
    25.     public List<Mesh> MeshQ;
    26.  
    27.     private void Start()
    28.     {
    29.         Debug.Log("Exporting static meshes from skinned mesh...");
    30.         ExportMeshes();
    31.     }
    32.  
    33.     void ExportMeshes()
    34.     {
    35.         ClipLenth = animation.clip.length;
    36.         WaitAmount = animation.clip.length / numberOfFrames;
    37.         //now let's start waiting for the animation to play
    38.         StartCoroutine(WaitForNextMesh());
    39.     }
    40.  
    41.     void AddMeshToQ(Mesh NewMesh)
    42.     {
    43.         MeshQ.Add(NewMesh);
    44.     }
    45.  
    46.     //IMPORTANT: I'm using a coroutine because if I don't, I create
    47.     //the same static meshes because the animation won't change in 1 frame.
    48.     //The purpose is to wait as the animation is playing, then make a static mesh at the correct time.
    49.  
    50.     IEnumerator WaitForNextMesh()
    51.     {
    52.         yield return new WaitForSeconds(WaitAmount);
    53.         AmountSoFar++;
    54.         //wait done! Let's make the static mesh!
    55.         ThisMesh = new Mesh();
    56.         SkinMeshRender.BakeMesh(ThisMesh);
    57.         //now that's it's made, add it to the que
    58.         AddMeshToQ(ThisMesh);
    59.         if (AmountSoFar < numberOfFrames)
    60.         {
    61.             //do it again, we have more meshes to make!
    62.             StartCoroutine(WaitForNextMesh());
    63.         }
    64.  
    65.         else
    66.         {
    67.             //created all meshes, we're done!
    68.             foreach (Mesh staticmesh in MeshQ)
    69.             {
    70.                 AmountSavedSoFar++;
    71.                 //try to save to specified location
    72.                 try
    73.                 {
    74.                     AssetDatabase.CreateAsset(staticmesh, SaveLocation + AmountSavedSoFar.ToString() + animation.clip.name + "_StaticFromSkinned" + ".asset");
    75.                 }
    76.                 //if the location is invalid, throw an error
    77.                 catch
    78.                 {
    79.                     Debug.Log("<color=red><b>Invalid save location! Make sure you've spelled the path to a folder correctly. For example: Assets/_@MYSTUFF/StaticAnimations/ </b></color>");
    80.                     yield break;
    81.                 }
    82.             }
    83.             //spam the console in fancy ways
    84.             Debug.Log("<color=green><b>All meshes created! You'll find them here: </b></color>" + SaveLocation);
    85.             Debug.Log("<color=red><i>For some reason I can't explain, delete the first frame and the last 2 frames so they loop properly.</i></color>");
    86.             Debug.Log("<color=red><i>Don't forget to disable/change this script, or you'll do what you just did again!</i></color>");
    87.         }
    88.     }
    89. }
    90.  
     
  7. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
  8. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761

    Did you ever figure out why the first and last frames get messed up? I'm having the exact same issue. I think even even Joachim Ante ran into the problem is his project https://github.com/joeante/Unity.GPUAnimation because he has a scene in that project called LastFrameLoopBug . Seems like a bug to me...
     
  9. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    I finally found the reason my first and last frames were busted! The reason is NOT related to the same issue that DroidifyDevs referred to. I looked through his code and don't see any obvious reasons the first and last frame would not work for what he's trying to do.
     
  10. DroidifyDevs

    DroidifyDevs

    Joined:
    Jun 24, 2015
    Posts:
    1,724
    Bro, I was 15 when I wrote that code and I still have no clue. I do not feel like I've gotten much smarter 8 years later...
     
    KuanMi and lclemens like this.