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

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!

Never mind, I looked here and after some tinkering came up with this:

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

public class AnimationConverter : MonoBehaviour {

    //For example: Assets/_@MYSTUFF/StaticAnimations/
    public string SaveLocation;
    //create a component on your gameobject and add the 
    public Animation animation;
    public SkinnedMeshRenderer SkinMeshRender;
    private Mesh[] NewStaticMesh;
    //length of animation clip
    public float ClipLenth;
    //how many frames do you want to make?
    public int numberOfFrames;
    //time between frame captures
    private float WaitAmount;
    //How many frames done BEFORE saving
    private int AmountSoFar;
    //how many frames SAVED
    private int AmountSavedSoFar;
    private Mesh ThisMesh;
    public List<Mesh> MeshQ;

    private void Start()
    {
        Debug.Log("Exporting static meshes from skinned mesh...");
        ExportMeshes();
    }

    void ExportMeshes()
    {
        ClipLenth = animation.clip.length;
        WaitAmount = animation.clip.length / numberOfFrames;
        //now let's start waiting for the animation to play
        StartCoroutine(WaitForNextMesh());
    }

    void AddMeshToQ(Mesh NewMesh)
    {
        MeshQ.Add(NewMesh);
    }

    //IMPORTANT: I'm using a coroutine because if I don't, I create
    //the same static meshes because the animation won't change in 1 frame.
    //The purpose is to wait as the animation is playing, then make a static mesh at the correct time.

    IEnumerator WaitForNextMesh()
    {
        yield return new WaitForSeconds(WaitAmount);
        AmountSoFar++;
        //wait done! Let's make the static mesh!
        ThisMesh = new Mesh();
        SkinMeshRender.BakeMesh(ThisMesh);
        //now that's it's made, add it to the que
        AddMeshToQ(ThisMesh);
        if (AmountSoFar < numberOfFrames)
        {
            //do it again, we have more meshes to make!
            StartCoroutine(WaitForNextMesh());
        }

        else
        {
            //created all meshes, we're done!
            foreach (Mesh staticmesh in MeshQ)
            {
                AmountSavedSoFar++;
                //try to save to specified location
                try
                {
                    AssetDatabase.CreateAsset(staticmesh, SaveLocation + AmountSavedSoFar.ToString() + animation.clip.name + "_StaticFromSkinned" + ".asset");
                }
                //if the location is invalid, throw an error
                catch
                {
                    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>");
                    yield break;
                }
            }
            //spam the console in fancy ways
            Debug.Log("<color=green><b>All meshes created! You'll find them here: </b></color>" + SaveLocation);
            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>");
            Debug.Log("<color=red><i>Don't forget to disable/change this script, or you'll do what you just did again!</i></color>");
        }
    }
}

Fully commented for future reference, free to use for everyone :smile:

7 Likes

Thanks for this, @DroidifyDevs ! For us artist types, how would I go about using this? :slight_smile:

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. :wink:

@lod3 & @theANMATOR2b , I’ve used that a long time ago and even I have forgotten it a bit :frowning:

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!

1 Like

@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:

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:

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

public class AnimationConverter : MonoBehaviour {

    //For example: Assets/_@MYSTUFF/StaticAnimations/
    public string SaveLocation;
    //create a component on your gameobject and add the
    public Animation animation;
    public SkinnedMeshRenderer SkinMeshRender;
    private Mesh[] NewStaticMesh;
    //length of animation clip
    public float ClipLenth;
    //how many frames do you want to make?
    public int numberOfFrames;
    //time between frame captures
    private float WaitAmount;
    //How many frames done BEFORE saving
    private int AmountSoFar;
    //how many frames SAVED
    private int AmountSavedSoFar;
    private Mesh ThisMesh;
    public List<Mesh> MeshQ;

    private void Start()
    {
        Debug.Log("Exporting static meshes from skinned mesh...");
        ExportMeshes();
    }

    void ExportMeshes()
    {
        ClipLenth = animation.clip.length;
        WaitAmount = animation.clip.length / numberOfFrames;
        //now let's start waiting for the animation to play
        StartCoroutine(WaitForNextMesh());
    }

    void AddMeshToQ(Mesh NewMesh)
    {
        MeshQ.Add(NewMesh);
    }

    //IMPORTANT: I'm using a coroutine because if I don't, I create
    //the same static meshes because the animation won't change in 1 frame.
    //The purpose is to wait as the animation is playing, then make a static mesh at the correct time.

    IEnumerator WaitForNextMesh()
    {
        yield return new WaitForSeconds(WaitAmount);
        AmountSoFar++;
        //wait done! Let's make the static mesh!
        ThisMesh = new Mesh();
        SkinMeshRender.BakeMesh(ThisMesh);
        //now that's it's made, add it to the que
        AddMeshToQ(ThisMesh);
        if (AmountSoFar < numberOfFrames)
        {
            //do it again, we have more meshes to make!
            StartCoroutine(WaitForNextMesh());
        }

        else
        {
            //created all meshes, we're done!
            foreach (Mesh staticmesh in MeshQ)
            {
                AmountSavedSoFar++;
                //try to save to specified location
                try
                {
                    AssetDatabase.CreateAsset(staticmesh, SaveLocation + AmountSavedSoFar.ToString() + animation.clip.name + "_StaticFromSkinned" + ".asset");
                }
                //if the location is invalid, throw an error
                catch
                {
                    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>");
                    yield break;
                }
            }
            //spam the console in fancy ways
            Debug.Log("<color=green><b>All meshes created! You'll find them here: </b></color>" + SaveLocation);
            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>");
            Debug.Log("<color=red><i>Don't forget to disable/change this script, or you'll do what you just did again!</i></color>");
        }
    }
}
6 Likes

Good for : https://docs.unity3d.com/ScriptReference/Graphics.DrawMesh.html
or
https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstanced.html :sunglasses:

Have a look here : https://discussions.unity.com/t/649925

or
https://forum.unity.com/threads/how-does-uebs-get-a-million-troops-on-the-field-simultaneously.465320/#post-3026679

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…

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.

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…

2 Likes