sexta-feira, 7 de setembro de 2012

Frame by Frame: exporting animations made in Unity

Playing with some resources and inspired by some sessions I watched on Unite, I developed a small Unity plugin to export animations. The algorithm, available by the end of this post, allows the definition of parameters like frame per second, image quality, name and directory.



The classes that derive from the Editor class (EditorWindow, EditorApplication) are simple to implement and extremely flexible, allowing the creation of add-ons without any big difficulty to fulfill almost any need.

On Aquiris' session on Unite 12, members of the studio showed an animation made inside of Unity and exported frame by frame as png, allowing the manipulation of the video (which needed to be mixed with real life images) through an external edition software.

In the attempt to remedy the little experience I have with Blender's interface and avoid having to illegally download software for future project's screen captures, I developed an animation add-on based on the logic presented by Aquiris: take screenshots according to a certain frame per second rate and export it all for external editing.

Before posting the code, a few instructions and brief explanations:


To open the window it is necessary to click on Animation Exporter inside the Window dropdown (by the side of the Help dropdown). Once open it is possible to modify it like a traditional Unity window or tab.
About the options shown above:

  1. "Name of the animation" : Field where the user can type the name the exported images will have. Each image will also receive an id, for example: if by the time a frame is saved, other 199 were already saved, it will have the id 200.
  2. "Frames per Second" : Number of frames which will be saved every second. As a default I inserted the options 1, 12, 16, 24, 25, 29, 30, 48 and 60, which are the framerates more often used in video codecs. If needed, the user can insert a custom value by clicking on the "Custom" option on the dropdown.
  3. "Resolution" : Defines the quality of the images to be saved. "Normal" is the application's screen resolution, "Better" is that resolution times two, "Beautiful" and "Amazing" are, respectively, the resolution times three and four.
  4. "Record on Start" : Iniates the recording automatically when the application starts running. This way the first few seconds aren't lost.
  5. "Record" : Button that starts the recoding. When first clicked, opens a window which allows the user to choose the directory the animation will be saved.
  6. "Pause" / "Unpause" : Pauses or unpauses the recording.
  7. General information: Status (Recording, Paused, Waiting for Application, etc), recording time and number of frames saved.
I've made a few last minute changes in the code (the idea of including the "Record on Start" option came up when I was writing this post), so any terrible mistake or interesting improvement, please let me know! Suggestions of new resources to the next version of the algorithm are also welcome. In other words, if you're interested in getting and testing the code, please, send me a feedback about what you thought.

To use the Animation Exporter, create a new file .cs and name it AnimationExporter. Copy and paste the following on that file:

using UnityEngine;
using UnityEditor;
using System.Collections;

//Enum which defines the quaintity of frames per second to be exported
public enum FPSOPTIONS{
fps1 =0,
fps12 = 1,
fps16 =2,
fps24 =3,
fps25 =4,
fps29 =5,
fps30 =6,
fps48=7,
fps60 =8,
Custom =9
}
//Enum which defines the quality of the exported frames
public enum RESOLUTION{
Normal = 0,
Better = 1,
Beautiful = 2,
Amazing = 3
}

public class AnimationExporter : EditorWindow {

private int imageCount =0, resolution = 1;
private float recordDelta = 0f, recordTime =0f;
private string path = Application.dataPath, checkPath;
private bool record = false, paused = false, recordOnStart = false;
public int framesPerSecond = 24;
public string animationName = "ExportedAnimation", status = "Waiting for application";

public FPSOPTIONS fpsOptions = FPSOPTIONS.fps16;
public RESOLUTION resolutionOp = RESOLUTION.Normal, oldResolution = RESOLUTION.Normal;
 void OnGUI () {        
    DoWindow();
}
// Function that executes the resources of the window, called on function OnGUI
 void DoWindow () {
GUILayout.Space(20f);

if(!Application.isPlaying || Application.isPlaying && !record){
animationName = EditorGUILayout.TextField("Name of the Animation", animationName); //Sets the names
}
else{
EditorGUILayout.TextField("Name of the Animation", animationName);
}
if(!Application.isPlaying || Application.isPlaying && !record){
fpsOptions = (FPSOPTIONS)EditorGUILayout.EnumPopup("Frames per Second", fpsOptions); // Sets the fps
}
else{
EditorGUILayout.EnumPopup("Frames per Second", fpsOptions);
}
switch(fpsOptions){
case FPSOPTIONS.fps1: framesPerSecond =1; break;
case FPSOPTIONS.fps12: framesPerSecond =12; break;
case FPSOPTIONS.fps16: framesPerSecond =16; break;
case FPSOPTIONS.fps24: framesPerSecond =24; break;
case FPSOPTIONS.fps25: framesPerSecond =25; break;
case FPSOPTIONS.fps29: framesPerSecond =29; break;
case FPSOPTIONS.fps30: framesPerSecond =30; break;
case FPSOPTIONS.fps48: framesPerSecond =48; break;
case FPSOPTIONS.fps60: framesPerSecond =60; break;
case FPSOPTIONS.Custom: framesPerSecond = EditorGUILayout.IntField("Frames per Second",framesPerSecond);break;
}
if(framesPerSecond < 1){
framesPerSecond = 1;
}
if(framesPerSecond >64){
framesPerSecond = 64;
}
if(framesPerSecond >120){
Debug.LogWarning("WARNING: A high frames per second rate may slow your computer's perfomance!");
}
if(!Application.isPlaying || Application.isPlaying && !record){
resolutionOp = (RESOLUTION)EditorGUILayout.EnumPopup("Resolution: ", resolutionOp); // Define a resolução
}
else{
EditorGUILayout.EnumPopup("Resolution: ", resolutionOp);
}
if(resolutionOp != oldResolution){
switch(resolutionOp){
case RESOLUTION.Normal: resolution = 1;
oldResolution = resolutionOp;
break;
case RESOLUTION.Better: resolution =2;
oldResolution = resolutionOp;
Debug.LogWarning("WARNING: Higher resolution may slow your computer's performance!"); 
break;
case RESOLUTION.Beautiful: resolution =3;
   oldResolution = resolutionOp;
   Debug.LogWarning("WARNING: Higher resolution may slow your computer's performance!");
   break;
case RESOLUTION.Amazing: resolution =4;
 oldResolution = resolutionOp;
 Debug.LogWarning("WARNING: Higher resolution may slow your computer's performance!");
 break;
}
}
if(!Application.isPlaying || Application.isPlaying && !record){
recordOnStart = EditorGUILayout.Toggle("Record on Start", recordOnStart); //Starts recording automatically when the application starts
}
else{
EditorGUILayout.Toggle("Record on Start", recordOnStart);
}
if(GUILayout.Button ("Record")){ // Button that initiates the recording
if(Application.isPlaying && !record && !paused){
checkPath = EditorUtility.SaveFilePanel("Choose Directory", path, animationName, "");
if(checkPath != ""){
path = checkPath;
record = true;
}
}
else if(Application.isPlaying && !record && paused){
record = true;
paused = false;
}
else if(Application.isPlaying && record){
Debug.LogWarning("Application is already being recorded!");
}
else if(!Application.isPlaying){
Debug.LogError("Cannot record if application is not running!");
}
}
if(!paused){
if(GUILayout.Button ("Pause")){ // Pause Button
if(record){
record = false;
paused = true;
}
}
}
else{
if(GUILayout.Button ("Unpause")){
record = true;
paused = false;
}
}
EditorGUILayout.LabelField("Status: ", status); // Shows atatus
EditorGUILayout.LabelField("Recording Time: ", recordTime.ToString("F2")); // Shows the recording time
EditorGUILayout.LabelField("Frames recorded: ", imageCount.ToString()); // Shows the number of frames recorded
if(record){
status = "Recording";
}
else if(paused && Application.isPlaying){
status = "Paused";
}
else if(!Application.isPlaying){
status = "Waiting for application";
}
}

void Update(){
if(recordOnStart && Application.isPlaying){
checkPath = EditorUtility.SaveFilePanel("Choose Directory", path, animationName, "");
if(checkPath != ""){
path = checkPath;
record = true;
}
recordOnStart = false;
}
if(record && Application.isPlaying){
recordTime += Time.deltaTime*10;
recordDelta +=Time.deltaTime;
if(recordDelta >= 1/framesPerSecond){ // Período de latencia entre a exportação de um frame e outro
imageCount++;
Application.CaptureScreenshot(path+"(" + imageCount.ToString() + ")" + ".png", resolution); // Tira a screenshot e salva no diretório escolhido
recordDelta = 0f;
}
}
if(!Application.isPlaying && !recordOnStart){
record = false;
paused = false;
if(imageCount >0){
imageCount =0;
recordTime =0f;
}
}
Repaint(); // Updates the window during runtime
}
    [MenuItem ("Window/Animation Exporter")]
    
    static void Init () {
EditorWindow.GetWindow<AnimationExporter>();
    }
}

Feel free to edit and use the code as you will. Credit is not necessary, but it is welcome ;) .

Nenhum comentário:

Postar um comentário