This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Random = UnityEngine.Random;
using UnityEditor;
namespace AnimationImporter
{
[InitializeOnLoad]
public class AnimationAssetPostprocessor : AssetPostprocessor
{
private static List<string> _assetsMarkedForImport = new List<string>();
private static EditorApplication.CallbackFunction _importDelegate;
private static bool _editorIsInUserMode = false;
// ================================================================================
// static constructor
// --------------------------------------------------------------------------------
static AnimationAssetPostprocessor()
{
EditorApplication.update += EditorApplicationRunOnce;
}
static void EditorApplicationRunOnce()
{
EditorApplication.update -= EditorApplicationRunOnce;
_editorIsInUserMode = true;
}
// ================================================================================
// unity methods
// --------------------------------------------------------------------------------
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath)
{
// skip automatic import on opening a project since this might create a few unexpected issues, including broken preview images
if (!_editorIsInUserMode)
{
return;
}
AnimationImporter importer = AnimationImporter.Instance;
if (importer == null)
{
return;
}
// Do not create shared config during AssetPostprocess, or else it will recreate an empty config
importer.LoadUserConfig();
// If no config exists, they can't have set up automatic importing so just return out.
if (importer.sharedData == null)
{
return;
}
if (importer.sharedData.automaticImporting)
{
List<string> markedAssets = new List<string>();
foreach (string asset in importedAssets)
{
if (AnimationImporter.IsValidAsset(asset))
{
MarkAssetForImport(asset, markedAssets);
}
}
if (markedAssets.Count > 0)
{
_assetsMarkedForImport.Clear();
_assetsMarkedForImport.AddRange(markedAssets);
if (_importDelegate == null)
{
_importDelegate = new EditorApplication.CallbackFunction(ImportAssets);
}
// Subscribe to callback
EditorApplication.update = Delegate.Combine(EditorApplication.update, _importDelegate) as EditorApplication.CallbackFunction;
}
}
}
// ================================================================================
// private methods
// --------------------------------------------------------------------------------
private static void MarkAssetForImport(string asset, List<string> markedAssets)
{
AnimationImporter importer = AnimationImporter.Instance;
if (!importer.canImportAnimations)
{
return;
}
if ((AnimationImporter.HasCustomReImport != null && AnimationImporter.HasCustomReImport(asset))
|| importer.HasExistingAnimatorController(asset)
|| importer.HasExistingAnimatorOverrideController(asset))
{
markedAssets.Add(asset);
}
}
private static void ImportAssets()
{
// Unsubscribe from callback
EditorApplication.update = Delegate.Remove(EditorApplication.update, _importDelegate as EditorApplication.CallbackFunction) as EditorApplication.CallbackFunction;
AssetDatabase.Refresh();
AnimationImporter importer = AnimationImporter.Instance;
importer.AutomaticReImport(_assetsMarkedForImport.ToArray());
_assetsMarkedForImport.Clear();
}
}
}

View File

@@ -0,0 +1,154 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using System.IO;
namespace AnimationImporter
{
public class AnimationImportJob
{
private string _assetPath;
public string name { get { return Path.GetFileNameWithoutExtension(fileName); } }
public string fileName { get { return Path.GetFileName(_assetPath); } }
public string assetDirectory { get { return GetBasePath(_assetPath); } }
private string _directoryPathForSprites = "";
public string directoryPathForSprites
{
get
{
if (!Directory.Exists(_directoryPathForSprites))
{
Directory.CreateDirectory(_directoryPathForSprites);
}
return _directoryPathForSprites;
}
set
{
_directoryPathForSprites = value;
}
}
private string _directoryPathForAnimations = "";
public string directoryPathForAnimations
{
get
{
if (!Directory.Exists(_directoryPathForAnimations))
{
Directory.CreateDirectory(_directoryPathForAnimations);
}
return _directoryPathForAnimations;
}
set
{
_directoryPathForAnimations = value;
}
}
private string _directoryPathForAnimationControllers = "";
public string directoryPathForAnimationControllers
{
get
{
if (!Directory.Exists(_directoryPathForAnimationControllers))
{
Directory.CreateDirectory(_directoryPathForAnimationControllers);
}
return _directoryPathForAnimationControllers;
}
set
{
_directoryPathForAnimationControllers = value;
}
}
public string imageAssetFilename
{
get
{
return directoryPathForSprites + "/" + name + ".png";
}
}
private string _sheetConfigParameter = "--sheet-pack";
public string sheetConfigParameter
{
get
{
return _sheetConfigParameter;
}
set
{
_sheetConfigParameter = value;
}
}
public PreviousImportSettings previousImportSettings = null;
// additional import settings
public string additionalCommandLineArguments = null;
public bool createUnityAnimations = true;
public ImportAnimatorController importAnimatorController = ImportAnimatorController.None;
public bool useExistingAnimatorController = false;
// ================================================================================
// constructor
// --------------------------------------------------------------------------------
public AnimationImportJob(string assetPath)
{
_assetPath = assetPath;
}
// ================================================================================
// progress
// --------------------------------------------------------------------------------
public delegate void ProgressUpdatedDelegate(float progress);
public event ProgressUpdatedDelegate progressUpdated;
private float _progress = 0;
public float progress
{
get
{
return _progress;
}
}
public void SetProgress(float progress)
{
_progress = progress;
if (progressUpdated != null)
{
progressUpdated(_progress);
}
}
// ================================================================================
// private methods
// --------------------------------------------------------------------------------
private string GetBasePath(string path)
{
string extension = Path.GetExtension(path);
if (extension.Length > 0 && extension[0] == '.')
{
extension = extension.Remove(0, 1);
}
string fileName = Path.GetFileNameWithoutExtension(path);
string lastPart = "/" + fileName + "." + extension;
return path.Replace(lastPart, "");
}
}
}

View File

@@ -0,0 +1,728 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using System.IO;
using UnityEditor.Animations;
using System.Linq;
using AnimationImporter.Aseprite;
namespace AnimationImporter
{
public class AnimationImporter
{
// ================================================================================
// Singleton
// --------------------------------------------------------------------------------
private static AnimationImporter _instance = null;
public static AnimationImporter Instance
{
get
{
if (_instance == null)
{
_instance = new AnimationImporter();
}
return _instance;
}
}
// ================================================================================
// delegates
// --------------------------------------------------------------------------------
public delegate ImportedAnimationSheet ImportDelegate(AnimationImportJob job, AnimationImporterSharedConfig config);
public delegate bool CustomReImportDelegate(string fileName);
public static CustomReImportDelegate HasCustomReImport = null;
public static CustomReImportDelegate HandleCustomReImport = null;
public delegate void ChangeImportJob(AnimationImportJob job);
// ================================================================================
// const
// --------------------------------------------------------------------------------
private const string PREFS_PREFIX = "ANIMATION_IMPORTER_";
private const string SHARED_CONFIG_PATH = "Assets/Resources/AnimationImporter/AnimationImporterConfig.asset";
// ================================================================================
// user values
// --------------------------------------------------------------------------------
string _asepritePath = "";
public string asepritePath
{
get
{
return _asepritePath;
}
set
{
if (_asepritePath != value)
{
_asepritePath = value;
SaveUserConfig();
}
}
}
private RuntimeAnimatorController _baseController = null;
public RuntimeAnimatorController baseController
{
get
{
return _baseController;
}
set
{
if (_baseController != value)
{
_baseController = value;
SaveUserConfig();
}
}
}
private AnimationImporterSharedConfig _sharedData;
public AnimationImporterSharedConfig sharedData
{
get
{
return _sharedData;
}
}
// ================================================================================
// Importer Plugins
// --------------------------------------------------------------------------------
private static Dictionary<string, IAnimationImporterPlugin> _importerPlugins = new Dictionary<string, IAnimationImporterPlugin>();
public static void RegisterImporter(IAnimationImporterPlugin importer, params string[] extensions)
{
foreach (var extension in extensions)
{
_importerPlugins[extension] = importer;
}
}
// ================================================================================
// validation
// --------------------------------------------------------------------------------
// this was used in the past, might be again in the future, so leave it here
public bool canImportAnimations
{
get
{
return true;
}
}
public bool canImportAnimationsForOverrideController
{
get
{
return canImportAnimations && _baseController != null;
}
}
// ================================================================================
// save and load user values
// --------------------------------------------------------------------------------
public void LoadOrCreateUserConfig()
{
LoadPreferences();
_sharedData = ScriptableObjectUtility.LoadOrCreateSaveData<AnimationImporterSharedConfig>(SHARED_CONFIG_PATH);
}
public void LoadUserConfig()
{
LoadPreferences();
_sharedData = ScriptableObjectUtility.LoadSaveData<AnimationImporterSharedConfig>(SHARED_CONFIG_PATH);
}
private void LoadPreferences()
{
if (PlayerPrefs.HasKey(PREFS_PREFIX + "asepritePath"))
{
_asepritePath = PlayerPrefs.GetString(PREFS_PREFIX + "asepritePath");
}
else
{
_asepritePath = AsepriteImporter.standardApplicationPath;
if (!File.Exists(_asepritePath))
_asepritePath = "";
}
if (PlayerPrefs.HasKey(PREFS_PREFIX + "baseControllerPath"))
{
string baseControllerPath = PlayerPrefs.GetString(PREFS_PREFIX + "baseControllerPath");
if (!string.IsNullOrEmpty(baseControllerPath))
{
_baseController = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(baseControllerPath);
}
}
}
private void SaveUserConfig()
{
PlayerPrefs.SetString(PREFS_PREFIX + "asepritePath", _asepritePath);
if (_baseController != null)
{
PlayerPrefs.SetString(PREFS_PREFIX + "baseControllerPath", AssetDatabase.GetAssetPath(_baseController));
}
else
{
PlayerPrefs.SetString(PREFS_PREFIX + "baseControllerPath", "");
}
}
// ================================================================================
// import methods
// --------------------------------------------------------------------------------
public void ImportAssets(DefaultAsset[] assets, ImportAnimatorController importAnimatorController = ImportAnimatorController.None)
{
List<AnimationImportJob> jobs = new List<AnimationImportJob>();
foreach (var asset in assets)
{
string assetPath = AssetDatabase.GetAssetPath(asset);
if (!IsValidAsset(assetPath))
{
continue;
}
AnimationImportJob job = CreateAnimationImportJob(assetPath);
job.importAnimatorController = importAnimatorController;
jobs.Add(job);
}
Import(jobs.ToArray());
}
/// <summary>
/// can be used by custom import pipeline
/// </summary>
public ImportedAnimationSheet ImportSpritesAndAnimationSheet(
string assetPath,
ChangeImportJob changeImportJob = null,
string additionalCommandLineArguments = null
)
{
// making sure config is valid
if (sharedData == null)
{
LoadOrCreateUserConfig();
}
if (!IsValidAsset(assetPath))
{
return null;
}
// create a job
AnimationImportJob job = CreateAnimationImportJob(assetPath, additionalCommandLineArguments);
job.createUnityAnimations = false;
if (changeImportJob != null)
{
changeImportJob(job);
}
return ImportJob(job);
}
private void Import(AnimationImportJob[] jobs)
{
if (jobs == null || jobs.Length == 0)
{
return;
}
float progressPerJob = 1f / jobs.Length;
try
{
for (int i = 0; i < jobs.Length; i++)
{
AnimationImportJob job = jobs[i];
job.progressUpdated += (float progress) => {
float completeProgress = i * progressPerJob + progress * progressPerJob;
EditorUtility.DisplayProgressBar("Import", job.name, completeProgress);
};
ImportJob(job);
}
AssetDatabase.Refresh();
}
catch (Exception error)
{
Debug.LogWarning(error.ToString());
throw;
}
EditorUtility.ClearProgressBar();
}
private ImportedAnimationSheet ImportJob(AnimationImportJob job)
{
job.SetProgress(0);
IAnimationImporterPlugin importer = _importerPlugins[GetExtension(job.fileName)];
ImportedAnimationSheet animationSheet = importer.Import(job, sharedData);
job.SetProgress(0.3f);
if (animationSheet != null)
{
animationSheet.assetDirectory = job.assetDirectory;
animationSheet.name = job.name;
animationSheet.ApplySpriteNamingScheme(sharedData.spriteNamingScheme);
CreateSprites(animationSheet, job);
job.SetProgress(0.6f);
if (job.createUnityAnimations)
{
CreateAnimations(animationSheet, job);
job.SetProgress(0.8f);
if (job.importAnimatorController == ImportAnimatorController.AnimatorController)
{
CreateAnimatorController(animationSheet);
}
else if (job.importAnimatorController == ImportAnimatorController.AnimatorOverrideController)
{
CreateAnimatorOverrideController(animationSheet, job.useExistingAnimatorController);
}
}
}
return animationSheet;
}
// ================================================================================
// create animator controllers
// --------------------------------------------------------------------------------
private void CreateAnimatorController(ImportedAnimationSheet animations)
{
AnimatorController controller;
string directory = sharedData.animationControllersTargetLocation.GetAndEnsureTargetDirectory(animations.assetDirectory);
// check if controller already exists; use this to not loose any references to this in other assets
string pathForAnimatorController = directory + "/" + animations.name + ".controller";
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(pathForAnimatorController);
if (controller == null)
{
// create a new controller and place every animation as a state on the first layer
controller = AnimatorController.CreateAnimatorControllerAtPath(pathForAnimatorController);
controller.AddLayer("Default");
foreach (var animation in animations.animations)
{
AnimatorState state = controller.layers[0].stateMachine.AddState(animation.name);
state.motion = animation.animationClip;
}
}
else
{
// look at all states on the first layer and replace clip if state has the same name
var childStates = controller.layers[0].stateMachine.states;
foreach (var childState in childStates)
{
AnimationClip clip = animations.GetClip(childState.state.name);
if (clip != null)
childState.state.motion = clip;
}
}
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
}
private void CreateAnimatorOverrideController(ImportedAnimationSheet animations, bool useExistingBaseController = false)
{
AnimatorOverrideController overrideController;
string directory = sharedData.animationControllersTargetLocation.GetAndEnsureTargetDirectory(animations.assetDirectory);
// check if override controller already exists; use this to not loose any references to this in other assets
string pathForOverrideController = directory + "/" + animations.name + ".overrideController";
overrideController = AssetDatabase.LoadAssetAtPath<AnimatorOverrideController>(pathForOverrideController);
RuntimeAnimatorController baseController = _baseController;
if (useExistingBaseController && overrideController.runtimeAnimatorController != null)
{
baseController = overrideController.runtimeAnimatorController;
}
if (baseController != null)
{
if (overrideController == null)
{
overrideController = new AnimatorOverrideController();
AssetDatabase.CreateAsset(overrideController, pathForOverrideController);
}
overrideController.runtimeAnimatorController = baseController;
// set override clips
#if UNITY_5_6_OR_NEWER
var clipPairs = new List<KeyValuePair<AnimationClip, AnimationClip>>(overrideController.overridesCount);
overrideController.GetOverrides(clipPairs);
foreach (var pair in clipPairs)
{
string animationName = pair.Key.name;
AnimationClip clip = animations.GetClipOrSimilar(animationName);
overrideController[animationName] = clip;
}
#else
var clipPairs = overrideController.clips;
for (int i = 0; i < clipPairs.Length; i++)
{
string animationName = clipPairs[i].originalClip.name;
AnimationClip clip = animations.GetClipOrSimilar(animationName);
clipPairs[i].overrideClip = clip;
}
overrideController.clips = clipPairs;
#endif
EditorUtility.SetDirty(overrideController);
}
else
{
Debug.LogWarning("No Animator Controller found as a base for the Override Controller");
}
}
// ================================================================================
// create sprites and animations
// --------------------------------------------------------------------------------
private void CreateAnimations(ImportedAnimationSheet animationSheet, AnimationImportJob job)
{
if (animationSheet == null)
{
return;
}
string imageAssetFilename = job.imageAssetFilename;
if (animationSheet.hasAnimations)
{
string targetPath = _sharedData.animationsTargetLocation.GetAndEnsureTargetDirectory(animationSheet.assetDirectory);
CreateAnimationAssets(animationSheet, imageAssetFilename, targetPath);
}
}
private void CreateAnimationAssets(ImportedAnimationSheet animationInfo, string imageAssetFilename, string pathForAnimations)
{
string masterName = Path.GetFileNameWithoutExtension(imageAssetFilename);
foreach (var animation in animationInfo.animations)
{
animationInfo.CreateAnimation(animation, pathForAnimations, masterName, sharedData.targetObjectType);
}
}
private void CreateSprites(ImportedAnimationSheet animationSheet, AnimationImportJob job)
{
if (animationSheet == null)
{
return;
}
string imageAssetFile = job.imageAssetFilename;
TextureImporter importer = AssetImporter.GetAtPath(imageAssetFile) as TextureImporter;
// apply texture import settings if there are no previous ones
if (!animationSheet.hasPreviousTextureImportSettings)
{
importer.textureType = TextureImporterType.Sprite;
importer.spritePixelsPerUnit = sharedData.spritePixelsPerUnit;
importer.mipmapEnabled = false;
importer.filterMode = FilterMode.Point;
#if UNITY_5_5_OR_NEWER
importer.textureCompression = TextureImporterCompression.Uncompressed;
#else
importer.textureFormat = TextureImporterFormat.AutomaticTruecolor;
#endif
}
// create sub sprites for this file according to the AsepriteAnimationInfo
importer.spritesheet = animationSheet.GetSpriteSheet(
sharedData.spriteAlignment,
sharedData.spriteAlignmentCustomX,
sharedData.spriteAlignmentCustomY);
// reapply old import settings (pivot settings for sprites)
if (animationSheet.hasPreviousTextureImportSettings)
{
animationSheet.previousImportSettings.ApplyPreviousTextureImportSettings(importer);
}
// these values will be set in any case, not influenced by previous import settings
importer.spriteImportMode = SpriteImportMode.Multiple;
importer.maxTextureSize = animationSheet.maxTextureSize;
EditorUtility.SetDirty(importer);
try
{
importer.SaveAndReimport();
}
catch (Exception e)
{
Debug.LogWarning("There was a problem with applying settings to the generated sprite file: " + e.ToString());
}
AssetDatabase.ImportAsset(imageAssetFile, ImportAssetOptions.ForceUpdate);
Sprite[] createdSprites = GetAllSpritesFromAssetFile(imageAssetFile);
animationSheet.ApplyCreatedSprites(createdSprites);
}
private static Sprite[] GetAllSpritesFromAssetFile(string imageFilename)
{
var assets = AssetDatabase.LoadAllAssetsAtPath(imageFilename);
// make sure we only grab valid sprites here
List<Sprite> sprites = new List<Sprite>();
foreach (var item in assets)
{
if (item is Sprite)
{
sprites.Add(item as Sprite);
}
}
return sprites.ToArray();
}
// ================================================================================
// querying existing assets
// --------------------------------------------------------------------------------
// check if this is a valid file; we are only looking at the file extension here
public static bool IsValidAsset(string path)
{
string extension = GetExtension(path);
if (!string.IsNullOrEmpty(path))
{
if (_importerPlugins.ContainsKey(extension))
{
IAnimationImporterPlugin importer = _importerPlugins[extension];
if (importer != null)
{
return importer.IsValid();
}
}
}
return false;
}
// check if there is a configured importer for the specified extension
public static bool IsConfiguredForAssets(DefaultAsset[] assets)
{
foreach(var asset in assets)
{
string assetPath = AssetDatabase.GetAssetPath(asset);
string extension = GetExtension(assetPath);
if (!string.IsNullOrEmpty(assetPath))
{
if (_importerPlugins.ContainsKey(extension))
{
IAnimationImporterPlugin importer = _importerPlugins[extension];
if (importer != null)
{
if(!importer.IsConfigured())
{
return false;
}
}
}
}
}
return true;
}
private static string GetExtension(string path)
{
if (string.IsNullOrEmpty(path))
{
return null;
}
string extension = Path.GetExtension(path);
if (extension.StartsWith("."))
{
extension = extension.Remove(0, 1);
}
return extension;
}
public bool HasExistingRuntimeAnimatorController(string assetPath)
{
return HasExistingAnimatorController(assetPath) || HasExistingAnimatorOverrideController(assetPath);
}
public bool HasExistingAnimatorController(string assetPath)
{
return GetExistingAnimatorController(assetPath) != null;
}
public bool HasExistingAnimatorOverrideController(string assetPath)
{
return GetExistingAnimatorOverrideController(assetPath) != null;
}
public RuntimeAnimatorController GetExistingRuntimeAnimatorController(string assetPath)
{
AnimatorController animatorController = GetExistingAnimatorController(assetPath);
if (animatorController != null)
{
return animatorController;
}
return GetExistingAnimatorOverrideController(assetPath);
}
public AnimatorController GetExistingAnimatorController(string assetPath)
{
string name = Path.GetFileNameWithoutExtension(assetPath);
string basePath = GetBasePath(assetPath);
string targetDirectory = sharedData.animationControllersTargetLocation.GetTargetDirectory(basePath);
string pathForController = targetDirectory + "/" + name + ".controller";
AnimatorController controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(pathForController);
return controller;
}
public AnimatorOverrideController GetExistingAnimatorOverrideController(string assetPath)
{
string name = Path.GetFileNameWithoutExtension(assetPath);
string basePath = GetBasePath(assetPath);
string targetDirectory = sharedData.animationControllersTargetLocation.GetTargetDirectory(basePath);
string pathForController = targetDirectory + "/" + name + ".overrideController";
AnimatorOverrideController controller = AssetDatabase.LoadAssetAtPath<AnimatorOverrideController>(pathForController);
return controller;
}
// ================================================================================
// automatic ReImport
// --------------------------------------------------------------------------------
/// <summary>
/// will be called by the AssetPostProcessor
/// </summary>
public void AutomaticReImport(string[] assetPaths)
{
if (sharedData == null)
{
LoadOrCreateUserConfig();
}
List<AnimationImportJob> jobs = new List<AnimationImportJob>();
foreach (var assetPath in assetPaths)
{
if (string.IsNullOrEmpty(assetPath))
{
continue;
}
if (HandleCustomReImport != null && HandleCustomReImport(assetPath))
{
continue;
}
AnimationImportJob job = CreateAnimationImportJob(assetPath);
if (job != null)
{
if (HasExistingAnimatorController(assetPath))
{
job.importAnimatorController = ImportAnimatorController.AnimatorController;
}
else if (HasExistingAnimatorOverrideController(assetPath))
{
job.importAnimatorController = ImportAnimatorController.AnimatorOverrideController;
job.useExistingAnimatorController = true;
}
jobs.Add(job);
}
}
Import(jobs.ToArray());
}
// ================================================================================
// private methods
// --------------------------------------------------------------------------------
private AnimationImportJob CreateAnimationImportJob(string assetPath, string additionalCommandLineArguments = "")
{
AnimationImportJob importJob = new AnimationImportJob(assetPath);
importJob.additionalCommandLineArguments = additionalCommandLineArguments;
importJob.directoryPathForSprites = _sharedData.spritesTargetLocation.GetTargetDirectory(importJob.assetDirectory);
importJob.directoryPathForAnimations = _sharedData.animationsTargetLocation.GetTargetDirectory(importJob.assetDirectory);
importJob.directoryPathForAnimationControllers = _sharedData.animationControllersTargetLocation.GetTargetDirectory(importJob.assetDirectory);
// we analyze import settings on existing files
importJob.previousImportSettings = CollectPreviousImportSettings(importJob);
return importJob;
}
private PreviousImportSettings CollectPreviousImportSettings(AnimationImportJob importJob)
{
PreviousImportSettings previousImportSettings = new PreviousImportSettings();
previousImportSettings.GetTextureImportSettings(importJob.imageAssetFilename);
return previousImportSettings;
}
private string GetBasePath(string path)
{
string extension = Path.GetExtension(path);
if (extension.Length > 0 && extension[0] == '.')
{
extension = extension.Remove(0, 1);
}
string fileName = Path.GetFileNameWithoutExtension(path);
string lastPart = "/" + fileName + "." + extension;
return path.Replace(lastPart, "");
}
}
}

View File

@@ -0,0 +1,432 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Random = UnityEngine.Random;
using UnityEditor;
using System.IO;
using AnimationImporter.Boomlagoon.JSON;
using UnityEditor.Animations;
using System.Linq;
namespace AnimationImporter
{
public class AnimationImporterWindow : EditorWindow
{
// ================================================================================
// private
// --------------------------------------------------------------------------------
private AnimationImporter importer
{
get
{
return AnimationImporter.Instance;
}
}
private GUIStyle _dropBoxStyle;
private GUIStyle _infoTextStyle;
private string _nonLoopingAnimationEnterValue = "";
private Vector2 _scrollPos = Vector2.zero;
// ================================================================================
// menu entry
// --------------------------------------------------------------------------------
[MenuItem("Window/Animation Importer")]
public static void ImportAnimationsMenu()
{
GetWindow(typeof(AnimationImporterWindow), false, "Anim Importer");
}
// ================================================================================
// unity methods
// --------------------------------------------------------------------------------
public void OnEnable()
{
importer.LoadOrCreateUserConfig();
}
public void OnGUI()
{
CheckGUIStyles();
if (importer.canImportAnimations)
{
_scrollPos = GUILayout.BeginScrollView(_scrollPos);
EditorGUILayout.Space();
ShowAnimationsGUI();
GUILayout.Space(25f);
ShowAnimatorControllerGUI();
GUILayout.Space(25f);
ShowAnimatorOverrideControllerGUI();
GUILayout.Space(25f);
ShowUserConfig();
GUILayout.EndScrollView();
}
else
{
EditorGUILayout.Space();
ShowHeadline("Select Aseprite Application");
EditorGUILayout.Space();
ShowAsepriteApplicationSelection();
EditorGUILayout.Space();
GUILayout.Label("Aseprite has to be installed on this machine because the Importer calls Aseprite through the command line for creating images and getting animation data.", _infoTextStyle);
}
}
// ================================================================================
// GUI methods
// --------------------------------------------------------------------------------
private void CheckGUIStyles()
{
if (_dropBoxStyle == null)
{
GetBoxStyle();
}
if (_infoTextStyle == null)
{
GetTextInfoStyle();
}
}
private void GetBoxStyle()
{
_dropBoxStyle = new GUIStyle(EditorStyles.helpBox);
_dropBoxStyle.alignment = TextAnchor.MiddleCenter;
}
private void GetTextInfoStyle()
{
_infoTextStyle = new GUIStyle(EditorStyles.label);
_infoTextStyle.wordWrap = true;
}
private void ShowUserConfig()
{
if (importer == null || importer.sharedData == null)
{
return;
}
ShowHeadline("Config");
/*
Aseprite Application
*/
ShowAsepriteApplicationSelection();
GUILayout.Space(5f);
/*
sprite values
*/
importer.sharedData.targetObjectType = (AnimationTargetObjectType)EditorGUILayout.EnumPopup("Target Object", importer.sharedData.targetObjectType);
importer.sharedData.spriteAlignment = (SpriteAlignment)EditorGUILayout.EnumPopup("Sprite Alignment", importer.sharedData.spriteAlignment);
if (importer.sharedData.spriteAlignment == SpriteAlignment.Custom)
{
importer.sharedData.spriteAlignmentCustomX = EditorGUILayout.Slider("x", importer.sharedData.spriteAlignmentCustomX, 0, 1f);
importer.sharedData.spriteAlignmentCustomY = EditorGUILayout.Slider("y", importer.sharedData.spriteAlignmentCustomY, 0, 1f);
}
importer.sharedData.spritePixelsPerUnit = EditorGUILayout.FloatField("Sprite Pixels per Unit", importer.sharedData.spritePixelsPerUnit);
GUILayout.Space(5f);
ShowTargetLocationOptions("Sprites", importer.sharedData.spritesTargetLocation);
ShowTargetLocationOptions("Animations", importer.sharedData.animationsTargetLocation);
ShowTargetLocationOptions("AnimationController", importer.sharedData.animationControllersTargetLocation);
GUILayout.Space(5f);
importer.sharedData.spriteNamingScheme = (SpriteNamingScheme)EditorGUILayout.IntPopup("Sprite Naming Scheme",
(int)importer.sharedData.spriteNamingScheme,
SpriteNaming.namingSchemesDisplayValues, SpriteNaming.namingSchemesValues);
GUILayout.Space(25f);
ShowHeadline("Automatic Import");
EditorGUILayout.BeginHorizontal();
importer.sharedData.automaticImporting = EditorGUILayout.Toggle("Automatic Import", importer.sharedData.automaticImporting);
EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField("Looks for existing Animation Controller with same name.");
/*
animations that do not loop
*/
GUILayout.Space(25f);
ShowHeadline("Non-looping Animations");
for (int i = 0; i < importer.sharedData.animationNamesThatDoNotLoop.Count; i++)
{
GUILayout.BeginHorizontal();
GUILayout.Label(importer.sharedData.animationNamesThatDoNotLoop[i]);
bool doDelete = GUILayout.Button("Delete");
GUILayout.EndHorizontal();
if (doDelete)
{
importer.sharedData.RemoveAnimationThatDoesNotLoop(i);
break;
}
}
EditorGUILayout.Space();
GUILayout.BeginHorizontal();
GUILayout.Label("Add ");
_nonLoopingAnimationEnterValue = EditorGUILayout.TextField(_nonLoopingAnimationEnterValue);
if (GUILayout.Button("Enter"))
{
if (importer.sharedData.AddAnimationThatDoesNotLoop(_nonLoopingAnimationEnterValue))
{
_nonLoopingAnimationEnterValue = "";
}
}
GUILayout.EndHorizontal();
EditorGUILayout.LabelField("Enter Part of the Animation Name or a Regex Expression.");
if (GUI.changed)
{
EditorUtility.SetDirty(importer.sharedData);
}
}
private void ShowTargetLocationOptions(string label, AssetTargetLocation targetLocation)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label(label, GUILayout.Width(130f));
targetLocation.locationType = (AssetTargetLocationType)EditorGUILayout.EnumPopup(targetLocation.locationType, GUILayout.Width(130f));
bool prevEnabled = GUI.enabled;
GUI.enabled = targetLocation.locationType == AssetTargetLocationType.GlobalDirectory;
string globalDirectory = targetLocation.globalDirectory;
if (GUILayout.Button("Select", GUILayout.Width(50f)))
{
var startDirectory = globalDirectory;
if (!Directory.Exists(startDirectory))
{
startDirectory = Application.dataPath;
}
startDirectory = Application.dataPath;
var path = EditorUtility.OpenFolderPanel("Select Target Location", globalDirectory, "");
if (!string.IsNullOrEmpty(path) && AssetDatabase.IsValidFolder(AssetDatabaseUtility.GetAssetPath(path)))
{
targetLocation.globalDirectory = AssetDatabaseUtility.GetAssetPath(path);
}
}
if (targetLocation.locationType == AssetTargetLocationType.GlobalDirectory)
{
string displayDirectory = "/" + globalDirectory;
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label(displayDirectory, GUILayout.MaxWidth(300f));
}
GUI.enabled = prevEnabled;
EditorGUILayout.EndHorizontal();
}
private void ShowAsepriteApplicationSelection()
{
GUILayout.BeginHorizontal();
GUILayout.Label("Aseprite Application Path");
string newPath = importer.asepritePath;
if (GUILayout.Button("Select"))
{
var path = EditorUtility.OpenFilePanel(
"Select Aseprite Application",
"",
"exe,app");
if (!string.IsNullOrEmpty(path))
{
newPath = path;
if (Application.platform == RuntimePlatform.OSXEditor)
{
newPath += "/Contents/MacOS/aseprite";
}
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
importer.asepritePath = GUILayout.TextField(newPath, GUILayout.MaxWidth(300f));
GUILayout.EndHorizontal();
if(!File.Exists(AnimationImporter.Instance.asepritePath))
{
var fileErrorMessage = string.Format(
"Cannot find Aseprite at the specified path. Use the Select button to locate the application.");
EditorGUILayout.HelpBox(fileErrorMessage, MessageType.Warning);
}
}
private void ShowAnimationsGUI()
{
ShowHeadline("Animations");
DefaultAsset[] droppedAssets = ShowDropButton<DefaultAsset>(importer.canImportAnimations, AnimationImporter.IsValidAsset);
if (droppedAssets != null && droppedAssets.Length > 0)
{
ImportAssetsOrError(droppedAssets);
}
}
private void ShowAnimatorControllerGUI()
{
ShowHeadline("Animator Controller + Animations");
DefaultAsset[] droppedAssets = ShowDropButton<DefaultAsset>(importer.canImportAnimations, AnimationImporter.IsValidAsset);
if (droppedAssets != null && droppedAssets.Length > 0)
{
ImportAssetsOrError(droppedAssets, ImportAnimatorController.AnimatorController);
}
}
private void ShowAnimatorOverrideControllerGUI()
{
ShowHeadline("Animator Override Controller + Animations");
importer.baseController = EditorGUILayout.ObjectField("Based on Controller:", importer.baseController, typeof(RuntimeAnimatorController), false) as RuntimeAnimatorController;
DefaultAsset[] droppedAssets = ShowDropButton<DefaultAsset>(importer.canImportAnimationsForOverrideController, AnimationImporter.IsValidAsset);
if (droppedAssets != null && droppedAssets.Length > 0)
{
ImportAssetsOrError(droppedAssets, ImportAnimatorController.AnimatorOverrideController);
}
}
private void ImportAssetsOrError(DefaultAsset[] assets, ImportAnimatorController importAnimatorController = ImportAnimatorController.None)
{
if(AnimationImporter.IsConfiguredForAssets(assets))
{
importer.ImportAssets(assets, importAnimatorController);
}
else
{
ShowPopupForBadAsepritePath(assets[0].name);
}
}
private void ShowPopupForBadAsepritePath(string assetName)
{
var message = string.Format(
"Cannot import Aseprite file \"{0}\" because the application cannot be found at the configured path. Use the Select button in the Config section to locate Aseprite.",
assetName);
EditorUtility.DisplayDialog("Error", message, "Ok");
}
private void ShowHeadline(string headline)
{
EditorGUILayout.LabelField(headline, EditorStyles.boldLabel, GUILayout.Height(20f));
}
// ================================================================================
// OnGUI helper
// --------------------------------------------------------------------------------
public delegate bool IsValidAssetDelegate(string path);
private T[] ShowDropButton<T>(bool isEnabled, IsValidAssetDelegate IsValidAsset) where T : UnityEngine.Object
{
T[] returnValue = null;
Rect drop_area = GUILayoutUtility.GetRect(0.0f, 80.0f, GUILayout.ExpandWidth(true));
GUI.enabled = isEnabled;
GUI.Box(drop_area, "Drop Animation files here", _dropBoxStyle);
GUI.enabled = true;
if (!isEnabled)
return null;
Event evt = Event.current;
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!drop_area.Contains(evt.mousePosition)
|| !DraggedObjectsContainValidObject<T>(IsValidAsset))
return null;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
List<T> validObjects = new List<T>();
foreach (UnityEngine.Object dragged_object in DragAndDrop.objectReferences)
{
var assetPath = AssetDatabase.GetAssetPath(dragged_object);
if (dragged_object is T && IsValidAsset(assetPath))
{
validObjects.Add(dragged_object as T);
}
}
returnValue = validObjects.ToArray();
}
evt.Use();
break;
}
return returnValue;
}
private bool DraggedObjectsContainValidObject<T>(IsValidAssetDelegate IsValidAsset) where T : UnityEngine.Object
{
foreach (UnityEngine.Object dragged_object in DragAndDrop.objectReferences)
{
var assetPath = AssetDatabase.GetAssetPath(dragged_object);
if (dragged_object is T && IsValidAsset(assetPath))
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,314 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Random = UnityEngine.Random;
using AnimationImporter.Boomlagoon.JSON;
using UnityEditor;
using System.IO;
namespace AnimationImporter.Aseprite
{
[InitializeOnLoad]
public class AsepriteImporter : IAnimationImporterPlugin
{
// ================================================================================
// const
// --------------------------------------------------------------------------------
const string ASEPRITE_STANDARD_PATH_WINDOWS = @"C:\Program Files (x86)\Aseprite\Aseprite.exe";
const string ASEPRITE_STANDARD_PATH_MACOSX = @"/Applications/Aseprite.app/Contents/MacOS/aseprite";
public static string standardApplicationPath
{
get
{
if (Application.platform == RuntimePlatform.WindowsEditor)
{
return ASEPRITE_STANDARD_PATH_WINDOWS;
}
else
{
return ASEPRITE_STANDARD_PATH_MACOSX;
}
}
}
// ================================================================================
// static constructor, registering plugin
// --------------------------------------------------------------------------------
static AsepriteImporter()
{
AsepriteImporter importer = new AsepriteImporter();
AnimationImporter.RegisterImporter(importer, "ase", "aseprite");
}
// ================================================================================
// public methods
// --------------------------------------------------------------------------------
public ImportedAnimationSheet Import(AnimationImportJob job, AnimationImporterSharedConfig config)
{
if (CreateSpriteAtlasAndMetaFile(job))
{
AssetDatabase.Refresh();
ImportedAnimationSheet animationSheet = CreateAnimationSheetFromMetaData(job, config);
return animationSheet;
}
return null;
}
public bool IsValid()
{
return AnimationImporter.Instance != null && AnimationImporter.Instance.sharedData != null;
}
public bool IsConfigured()
{
return File.Exists(Path.GetFullPath(AnimationImporter.Instance.asepritePath));
}
// ================================================================================
// private methods
// --------------------------------------------------------------------------------
// parses a JSON file and creates the raw data for ImportedAnimationSheet from it
private static ImportedAnimationSheet CreateAnimationSheetFromMetaData(AnimationImportJob job, AnimationImporterSharedConfig config)
{
string textAssetFilename = job.directoryPathForSprites + "/" + job.name + ".json";
TextAsset textAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(textAssetFilename);
if (textAsset != null)
{
JSONObject jsonObject = JSONObject.Parse(textAsset.ToString());
ImportedAnimationSheet animationSheet = GetAnimationInfo(jsonObject);
if (animationSheet == null)
{
return null;
}
if (!animationSheet.hasAnimations)
{
Debug.LogWarning("No Animations found in Aseprite file. Use Aseprite Tags to assign names to Animations.");
}
animationSheet.previousImportSettings = job.previousImportSettings;
animationSheet.SetNonLoopingAnimations(config.animationNamesThatDoNotLoop);
// delete JSON file afterwards
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(textAsset));
return animationSheet;
}
else
{
Debug.LogWarning("Problem with JSON file: " + textAssetFilename);
}
return null;
}
/// <summary>
/// calls the Aseprite application which then should output a png with all sprites and a corresponding JSON
/// </summary>
/// <returns></returns>
private static bool CreateSpriteAtlasAndMetaFile(AnimationImportJob job)
{
char delimiter = '\"';
string parameters = "--data " + delimiter + job.name + ".json" + delimiter + " --sheet " + delimiter + job.name + ".png" + delimiter + " " + job.sheetConfigParameter + " --list-tags --format json-array " + delimiter + job.fileName + delimiter;
if (!string.IsNullOrEmpty(job.additionalCommandLineArguments))
{
parameters = job.additionalCommandLineArguments + " " + parameters;
}
bool success = CallAsepriteCLI(AnimationImporter.Instance.asepritePath, job.assetDirectory, parameters) == 0;
// move png and json file to subfolder
if (success && job.directoryPathForSprites != job.assetDirectory)
{
// create subdirectory
if (!Directory.Exists(job.directoryPathForSprites))
{
Directory.CreateDirectory(job.directoryPathForSprites);
}
// check and copy json file
string jsonSource = job.assetDirectory + "/" + job.name + ".json";
string jsonTarget = job.directoryPathForSprites + "/" + job.name + ".json";
if (File.Exists(jsonSource))
{
if (File.Exists(jsonTarget))
{
File.Delete(jsonTarget);
}
File.Move(jsonSource, jsonTarget);
}
else
{
Debug.LogWarning("Calling Aseprite resulted in no json data file. Wrong Aseprite version?");
return false;
}
// check and copy png file
string pngSource = job.assetDirectory + "/" + job.name + ".png";
string pngTarget = job.directoryPathForSprites + "/" + job.name + ".png";
if (File.Exists(pngSource))
{
if (File.Exists(pngTarget))
{
File.Delete(pngTarget);
}
File.Move(pngSource, pngTarget);
}
else
{
Debug.LogWarning("Calling Aseprite resulted in no png Image file. Wrong Aseprite version?");
return false;
}
}
return success;
}
private static ImportedAnimationSheet GetAnimationInfo(JSONObject root)
{
if (root == null)
{
Debug.LogWarning("Error importing JSON animation info: JSONObject is NULL");
return null;
}
ImportedAnimationSheet animationSheet = new ImportedAnimationSheet();
// import all informations from JSON
if (!root.ContainsKey("meta"))
{
Debug.LogWarning("Error importing JSON animation info: no 'meta' object");
return null;
}
var meta = root["meta"].Obj;
GetMetaInfosFromJSON(animationSheet, meta);
if (GetAnimationsFromJSON(animationSheet, meta) == false)
{
return null;
}
if (GetFramesFromJSON(animationSheet, root) == false)
{
return null;
}
animationSheet.ApplyGlobalFramesToAnimationFrames();
return animationSheet;
}
private static int CallAsepriteCLI(string asepritePath, string path, string buildOptions)
{
string workingDirectory = Application.dataPath.Replace("Assets", "") + path;
System.Diagnostics.ProcessStartInfo start = new System.Diagnostics.ProcessStartInfo();
start.Arguments = "-b " + buildOptions;
start.FileName = Path.GetFullPath(asepritePath);
start.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
start.CreateNoWindow = true;
start.UseShellExecute = false;
start.WorkingDirectory = workingDirectory;
// Run the external process & wait for it to finish
using (System.Diagnostics.Process proc = System.Diagnostics.Process.Start(start))
{
proc.WaitForExit();
// Retrieve the app's exit code
return proc.ExitCode;
}
}
private static void GetMetaInfosFromJSON(ImportedAnimationSheet animationSheet, JSONObject meta)
{
var size = meta["size"].Obj;
animationSheet.width = (int)size["w"].Number;
animationSheet.height = (int)size["h"].Number;
}
private static bool GetAnimationsFromJSON(ImportedAnimationSheet animationSheet, JSONObject meta)
{
if (!meta.ContainsKey("frameTags"))
{
Debug.LogWarning("No 'frameTags' found in JSON created by Aseprite.");
IssueVersionWarning();
return false;
}
var frameTags = meta["frameTags"].Array;
foreach (var item in frameTags)
{
JSONObject frameTag = item.Obj;
ImportedAnimation anim = new ImportedAnimation();
anim.name = frameTag["name"].Str;
anim.firstSpriteIndex = (int)(frameTag["from"].Number);
anim.lastSpriteIndex = (int)(frameTag["to"].Number);
switch (frameTag["direction"].Str)
{
default:
anim.direction = PlaybackDirection.Forward;
break;
case "reverse":
anim.direction = PlaybackDirection.Reverse;
break;
case "pingpong":
anim.direction = PlaybackDirection.PingPong;
break;
}
animationSheet.animations.Add(anim);
}
return true;
}
private static bool GetFramesFromJSON(ImportedAnimationSheet animationSheet, JSONObject root)
{
var list = root["frames"].Array;
if (list == null)
{
Debug.LogWarning("No 'frames' array found in JSON created by Aseprite.");
IssueVersionWarning();
return false;
}
foreach (var item in list)
{
ImportedAnimationFrame frame = new ImportedAnimationFrame();
var frameValues = item.Obj["frame"].Obj;
frame.width = (int)frameValues["w"].Number;
frame.height = (int)frameValues["h"].Number;
frame.x = (int)frameValues["x"].Number;
frame.y = animationSheet.height - (int)frameValues["y"].Number - frame.height; // unity has a different coord system
frame.duration = (int)item.Obj["duration"].Number;
animationSheet.frames.Add(frame);
}
return true;
}
private static void IssueVersionWarning()
{
Debug.LogWarning("Please use official Aseprite 1.1.1 or newer.");
}
}
}

View File

@@ -0,0 +1,227 @@
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
namespace AnimationImporter
{
public class AnimationImporterSharedConfig : ScriptableObject
{
private const string PREFS_PREFIX = "ANIMATION_IMPORTER_";
[SerializeField]
private List<string> _animationNamesThatDoNotLoop = new List<string>() { "death" };
public List<string> animationNamesThatDoNotLoop { get { return _animationNamesThatDoNotLoop; } }
[SerializeField]
private bool _automaticImporting = false;
public bool automaticImporting
{
get
{
return _automaticImporting;
}
set
{
_automaticImporting = value;
}
}
// sprite import values
[SerializeField]
private float _spritePixelsPerUnit = 100f;
public float spritePixelsPerUnit
{
get
{
return _spritePixelsPerUnit;
}
set
{
_spritePixelsPerUnit = value;
}
}
[SerializeField]
private AnimationTargetObjectType _targetObjectType = AnimationTargetObjectType.SpriteRenderer;
public AnimationTargetObjectType targetObjectType
{
get
{
return _targetObjectType;
}
set
{
_targetObjectType = value;
}
}
[SerializeField]
private SpriteAlignment _spriteAlignment = SpriteAlignment.BottomCenter;
public SpriteAlignment spriteAlignment
{
get
{
return _spriteAlignment;
}
set
{
_spriteAlignment = value;
}
}
[SerializeField]
private float _spriteAlignmentCustomX = 0;
public float spriteAlignmentCustomX
{
get
{
return _spriteAlignmentCustomX;
}
set
{
_spriteAlignmentCustomX = value;
}
}
[SerializeField]
private float _spriteAlignmentCustomY = 0;
public float spriteAlignmentCustomY
{
get
{
return _spriteAlignmentCustomY;
}
set
{
_spriteAlignmentCustomY = value;
}
}
[SerializeField]
private AssetTargetLocation _spritesTargetLocation = new AssetTargetLocation(AssetTargetLocationType.SubDirectory, "Sprites");
public AssetTargetLocation spritesTargetLocation
{
get { return _spritesTargetLocation; }
set { _spritesTargetLocation = value; }
}
[SerializeField]
private AssetTargetLocation _animationsTargetLocation = new AssetTargetLocation(AssetTargetLocationType.SubDirectory, "Animations");
public AssetTargetLocation animationsTargetLocation
{
get { return _animationsTargetLocation; }
set { _animationsTargetLocation = value; }
}
[SerializeField]
private AssetTargetLocation _animationControllersTargetLocation = new AssetTargetLocation(AssetTargetLocationType.SameDirectory, "Animations");
public AssetTargetLocation animationControllersTargetLocation
{
get { return _animationControllersTargetLocation; }
set { _animationControllersTargetLocation = value; }
}
[SerializeField]
private SpriteNamingScheme _spriteNamingScheme = SpriteNamingScheme.Classic;
public SpriteNamingScheme spriteNamingScheme
{
get { return _spriteNamingScheme; }
set { _spriteNamingScheme = value; }
}
public void RemoveAnimationThatDoesNotLoop(int index)
{
animationNamesThatDoNotLoop.RemoveAt(index);
}
public bool AddAnimationThatDoesNotLoop(string animationName)
{
if (string.IsNullOrEmpty(animationName) || animationNamesThatDoNotLoop.Contains(animationName))
return false;
animationNamesThatDoNotLoop.Add(animationName);
return true;
}
/// <summary>
/// Specify if the Unity user has preferences for an older version of AnimationImporter
/// </summary>
/// <returns><c>true</c>, if the user has old preferences, <c>false</c> otherwise.</returns>
public bool UserHasOldPreferences()
{
var pixelsPerUnityKey = PREFS_PREFIX + "spritePixelsPerUnit";
return PlayerPrefs.HasKey(pixelsPerUnityKey) || EditorPrefs.HasKey(pixelsPerUnityKey);
}
private bool HasKeyInPreferences(string key)
{
return PlayerPrefs.HasKey(key) || EditorPrefs.HasKey(key);
}
private int GetIntFromPreferences(string intKey)
{
if (PlayerPrefs.HasKey(intKey))
{
return PlayerPrefs.GetInt(intKey);
}
else if (EditorPrefs.HasKey(intKey))
{
return EditorPrefs.GetInt(intKey);
}
else
{
return int.MinValue;
}
}
private float GetFloatFromPreferences(string floatKey)
{
if (PlayerPrefs.HasKey(floatKey))
{
return PlayerPrefs.GetFloat(floatKey);
}
else if (EditorPrefs.HasKey(floatKey))
{
return EditorPrefs.GetFloat(floatKey);
}
else
{
return float.NaN;
}
}
private bool GetBoolFromPreferences(string boolKey)
{
if (PlayerPrefs.HasKey(boolKey))
{
return System.Convert.ToBoolean(PlayerPrefs.GetInt(boolKey));
}
else if (EditorPrefs.HasKey(boolKey))
{
return EditorPrefs.GetBool(boolKey);
}
else
{
return false;
}
}
private string GetStringFromPreferences(string stringKey)
{
if (PlayerPrefs.HasKey(stringKey))
{
return PlayerPrefs.GetString(stringKey);
}
else if (EditorPrefs.HasKey(stringKey))
{
return EditorPrefs.GetString(stringKey);
}
else
{
return string.Empty;
}
}
}
}

View File

@@ -0,0 +1,7 @@

public enum AnimationTargetObjectType : int
{
SpriteRenderer,
Image,
SpriteRendererAndImage
}

View File

@@ -0,0 +1,75 @@
using System.IO;
using UnityEngine;
namespace AnimationImporter
{
[System.Serializable]
public class AssetTargetLocation
{
[SerializeField]
private AssetTargetLocationType _locationType;
public AssetTargetLocationType locationType
{
get { return _locationType; }
set { _locationType = value; }
}
[SerializeField]
private string _globalDirectory = "Assets";
public string globalDirectory
{
get { return _globalDirectory; }
set { _globalDirectory = value; }
}
private string _subDirectoryName;
public string subDirectoryName
{
get {return _subDirectoryName; }
}
// ================================================================================
// constructor
// --------------------------------------------------------------------------------
public AssetTargetLocation(AssetTargetLocationType type, string subFolderName) : this(type)
{
_subDirectoryName = subFolderName;
}
public AssetTargetLocation(AssetTargetLocationType type)
{
locationType = type;
}
// ================================================================================
// public methods
// --------------------------------------------------------------------------------
public string GetAndEnsureTargetDirectory(string assetDirectory)
{
string directory = GetTargetDirectory(assetDirectory);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
return directory;
}
public string GetTargetDirectory(string assetDirectory)
{
if (locationType == AssetTargetLocationType.GlobalDirectory)
{
return globalDirectory;
}
else if (locationType == AssetTargetLocationType.SubDirectory)
{
return Path.Combine(assetDirectory, subDirectoryName);
}
return assetDirectory;
}
}
}

View File

@@ -0,0 +1,10 @@

namespace AnimationImporter
{
public enum AssetTargetLocationType : int
{
SameDirectory,
SubDirectory,
GlobalDirectory
}
}

View File

@@ -0,0 +1,10 @@

namespace AnimationImporter
{
public enum ImportAnimatorController
{
None,
AnimatorController,
AnimatorOverrideController
}
}

View File

@@ -0,0 +1,102 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Random = UnityEngine.Random;
using UnityEditor;
namespace AnimationImporter
{
public class PreviousImportSettings
{
private SpriteMetaData? _previousFirstSprite = null;
private bool _hasPreviousTextureImportSettings = false;
public bool hasPreviousTextureImportSettings
{
get
{
return _hasPreviousTextureImportSettings;
}
}
// ================================================================================
// public methods
// --------------------------------------------------------------------------------
public void GetTextureImportSettings(string filename)
{
TextureImporter importer = AssetImporter.GetAtPath(filename) as TextureImporter;
if (importer != null)
{
_hasPreviousTextureImportSettings = true;
if (importer.spritesheet != null && importer.spritesheet.Length > 0)
{
_previousFirstSprite = importer.spritesheet[0];
}
}
}
public void ApplyPreviousTextureImportSettings(TextureImporter importer)
{
if (!_hasPreviousTextureImportSettings|| importer == null)
{
return;
}
// apply old pivot point settings
// we assume every sprite should have the same pivot point
if (_previousFirstSprite.HasValue)
{
var spritesheet = importer.spritesheet; // read values
for (int i = 0; i < spritesheet.Length; i++)
{
spritesheet[i].alignment = _previousFirstSprite.Value.alignment;
spritesheet[i].pivot = _previousFirstSprite.Value.pivot;
}
importer.spritesheet = spritesheet; // write values
}
}
// ================================================================================
// analyzing animations
// --------------------------------------------------------------------------------
public static AnimationTargetObjectType GetAnimationTargetFromExistingClip(AnimationClip clip)
{
var curveBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
bool targetingSpriteRenderer = false;
bool targetingImage = false;
for (int i = 0; i < curveBindings.Length; i++)
{
if (curveBindings[i].type == typeof(SpriteRenderer))
{
targetingSpriteRenderer = true;
}
else if (curveBindings[i].type == typeof(UnityEngine.UI.Image))
{
targetingImage = true;
}
}
if (targetingSpriteRenderer && targetingImage)
{
return AnimationTargetObjectType.SpriteRendererAndImage;
}
else if (targetingImage)
{
return AnimationTargetObjectType.Image;
}
else
{
return AnimationTargetObjectType.SpriteRenderer;
}
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using UnityEngine;
namespace AnimationImporter
{
public enum SpriteNamingScheme : int
{
Classic, // hero 0
FileAnimationZero, // hero_idle_0, ...
FileAnimationOne, // hero_idle_1, ...
AnimationZero, // idle_0, ...
AnimationOne // idle_1, ...
}
public static class SpriteNaming
{
private static int[] _namingSchemesValues = null;
public static int[] namingSchemesValues
{
get
{
if (_namingSchemesValues == null)
{
InitNamingLists();
}
return _namingSchemesValues;
}
}
private static string[] _namingSchemesDisplayValues = null;
public static string[] namingSchemesDisplayValues
{
get
{
if (_namingSchemesDisplayValues == null)
{
InitNamingLists();
}
return _namingSchemesDisplayValues;
}
}
private static void InitNamingLists()
{
var allNamingSchemes = Enum.GetValues(typeof(SpriteNamingScheme));
_namingSchemesValues = new int[allNamingSchemes.Length];
_namingSchemesDisplayValues = new string[allNamingSchemes.Length];
for (int i = 0; i < allNamingSchemes.Length; i++)
{
SpriteNamingScheme namingScheme = (SpriteNamingScheme)allNamingSchemes.GetValue(i);
_namingSchemesValues[i] = (int)namingScheme;
_namingSchemesDisplayValues[i] = namingScheme.ToDisplayString();
}
}
private static string ToDisplayString(this SpriteNamingScheme namingScheme)
{
switch (namingScheme)
{
case SpriteNamingScheme.Classic:
return "hero 0, hero 1, ... (Default)";
case SpriteNamingScheme.FileAnimationZero:
return "hero_idle_0, hero_idle_1, ...";
case SpriteNamingScheme.FileAnimationOne:
return "hero_idle_1, hero_idle_2, ...";
case SpriteNamingScheme.AnimationZero:
return "idle_0, idle_1, ...";
case SpriteNamingScheme.AnimationOne:
return "idle_1, idle_2, ...";
}
return "";
}
}
}

View File

@@ -0,0 +1,14 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace AnimationImporter
{
public interface IAnimationImporterPlugin
{
ImportedAnimationSheet Import(AnimationImportJob job, AnimationImporterSharedConfig config);
bool IsValid();
bool IsConfigured();
}
}

View File

@@ -0,0 +1,71 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;
namespace AnimationImporter
{
public enum PlaybackDirection
{
Forward, // default
Reverse, // reversed frames
PingPong // forward, then reverse
}
public class ImportedAnimation
{
public string name;
public ImportedAnimationFrame[] frames = null;
public bool isLooping = true;
// final animation clip; saved here for usage when building the AnimatorController
public AnimationClip animationClip;
// ================================================================================
// temporary data, only used for first import
// --------------------------------------------------------------------------------
// assuming all sprites are in some array/list and an animation is defined as a continous list of indices
public int firstSpriteIndex;
public int lastSpriteIndex;
// unity animations only play forward, so this will affect the way frames are added to the final animation clip
public PlaybackDirection direction;
// used with the indices because we to not have the Frame array yet
public int Count
{
get
{
return lastSpriteIndex - firstSpriteIndex + 1;
}
}
// ================================================================================
// public methods
// --------------------------------------------------------------------------------
/// <summary>
/// Lists frames so that the final anim seems to play in the desired direction.
/// *Attention:* Can return more than <see cref="Count"/> frames.
/// </summary>
public IEnumerable<ImportedAnimationFrame> ListFramesAccountingForPlaybackDirection()
{
switch (direction)
{
default:
case PlaybackDirection.Forward: // ex: 1, 2, 3, 4
return frames;
case PlaybackDirection.Reverse: // ex: 4, 3, 2, 1
return frames.Reverse();
case PlaybackDirection.PingPong: // ex: 1, 2, 3, 4, 3, 2
return frames.Concat(frames.Skip(1).Take(frames.Length - 2).Reverse());
}
}
}
}

View File

@@ -0,0 +1,34 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace AnimationImporter
{
public class ImportedAnimationFrame
{
// ================================================================================
// naming
// --------------------------------------------------------------------------------
private string _name;
public string name
{
get { return _name; }
set { _name = value; }
}
// ================================================================================
// properties
// --------------------------------------------------------------------------------
public int x;
public int y;
public int width;
public int height;
public int duration; // in milliseconds as part of an animation
// reference to the Sprite that was created with this frame information
public Sprite sprite = null;
}
}

View File

@@ -0,0 +1,354 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEditor;
using System.Linq;
namespace AnimationImporter
{
public class ImportedAnimationSheet
{
public string name { get; set; }
public string assetDirectory { get; set; }
public int width { get; set; }
public int height { get; set; }
public int maxTextureSize
{
get
{
return Mathf.Max(width, height);
}
}
public List<ImportedAnimationFrame> frames = new List<ImportedAnimationFrame>();
public List<ImportedAnimation> animations = new List<ImportedAnimation>();
public bool hasAnimations
{
get
{
return animations != null && animations.Count > 0;
}
}
private Dictionary<string, ImportedAnimation> _animationDatabase = null;
private PreviousImportSettings _previousImportSettings = null;
public PreviousImportSettings previousImportSettings
{
get
{
return _previousImportSettings;
}
set
{
_previousImportSettings = value;
}
}
public bool hasPreviousTextureImportSettings
{
get
{
return _previousImportSettings != null && _previousImportSettings.hasPreviousTextureImportSettings;
}
}
// ================================================================================
// public methods
// --------------------------------------------------------------------------------
// get animation by name; used when updating an existing AnimatorController
public AnimationClip GetClip(string clipName)
{
if (_animationDatabase == null)
BuildIndex();
if (_animationDatabase.ContainsKey(clipName))
return _animationDatabase[clipName].animationClip;
return null;
}
/*
get animation by name; used when creating an AnimatorOverrideController
we look for similar names so the OverrideController is still functional in cases where more specific or alternative animations are not present
idle <- idle
idleAlt <- idle
*/
public AnimationClip GetClipOrSimilar(string clipName)
{
AnimationClip clip = GetClip(clipName);
if (clip != null)
return clip;
List<ImportedAnimation> similarAnimations = new List<ImportedAnimation>();
foreach (var item in animations)
{
if (clipName.Contains(item.name))
similarAnimations.Add(item);
}
if (similarAnimations.Count > 0)
{
ImportedAnimation similar = similarAnimations.OrderBy(x => x.name.Length).Reverse().First();
return similar.animationClip;
}
return null;
}
public void CreateAnimation(ImportedAnimation anim, string basePath, string masterName, AnimationTargetObjectType targetType)
{
AnimationClip clip;
string fileName = basePath + "/" + masterName + "_" + anim.name + ".anim";
bool isLooping = anim.isLooping;
// check if animation file already exists
clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(fileName);
if (clip != null)
{
// get previous animation settings
targetType = PreviousImportSettings.GetAnimationTargetFromExistingClip(clip);
}
else
{
clip = new AnimationClip();
AssetDatabase.CreateAsset(clip, fileName);
}
// change loop settings
if (isLooping)
{
clip.wrapMode = WrapMode.Loop;
clip.SetLoop(true);
}
else
{
clip.wrapMode = WrapMode.Clamp;
clip.SetLoop(false);
}
// convert keyframes
ImportedAnimationFrame[] srcKeyframes = anim.ListFramesAccountingForPlaybackDirection().ToArray();
ObjectReferenceKeyframe[] keyFrames = new ObjectReferenceKeyframe[srcKeyframes.Length + 1];
float timeOffset = 0f;
for (int i = 0; i < srcKeyframes.Length; i++)
{
// first sprite will be set at the beginning (t=0) of the animation
keyFrames[i] = new ObjectReferenceKeyframe
{
time = timeOffset,
value = srcKeyframes[i].sprite
};
// add duration of frame in seconds
timeOffset += srcKeyframes[i].duration / 1000f;
}
// repeating the last frame at a point "just before the end" so the animation gets its correct length
keyFrames[srcKeyframes.Length] = new ObjectReferenceKeyframe
{
time = timeOffset - (1f / clip.frameRate), // substract the duration of one frame
value = srcKeyframes.Last().sprite
};
// save curve into clip, either for SpriteRenderer, Image, or both
if (targetType == AnimationTargetObjectType.SpriteRenderer)
{
AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.spriteRendererCurveBinding, keyFrames);
AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.imageCurveBinding, null);
}
else if (targetType == AnimationTargetObjectType.Image)
{
AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.spriteRendererCurveBinding, null);
AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.imageCurveBinding, keyFrames);
}
else if (targetType == AnimationTargetObjectType.SpriteRendererAndImage)
{
AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.spriteRendererCurveBinding, keyFrames);
AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.imageCurveBinding, keyFrames);
}
EditorUtility.SetDirty(clip);
anim.animationClip = clip;
}
public void ApplyGlobalFramesToAnimationFrames()
{
for (int i = 0; i < animations.Count; i++)
{
ImportedAnimation anim = animations[i];
anim.frames = frames.GetRange(anim.firstSpriteIndex, anim.Count).ToArray();
}
}
// ================================================================================
// determine looping state of animations
// --------------------------------------------------------------------------------
public void SetNonLoopingAnimations(List<string> nonLoopingAnimationNames)
{
Regex nonLoopingAnimationsRegex = GetRegexFromNonLoopingAnimationNames(nonLoopingAnimationNames);
foreach (var item in animations)
{
item.isLooping = ShouldLoop(nonLoopingAnimationsRegex, item.name);
}
}
private bool ShouldLoop(Regex nonLoopingAnimationsRegex, string name)
{
if (!string.IsNullOrEmpty(nonLoopingAnimationsRegex.ToString()))
{
if (nonLoopingAnimationsRegex.IsMatch(name))
{
return false;
}
}
return true;
}
private Regex GetRegexFromNonLoopingAnimationNames(List<string> value)
{
string regexString = string.Empty;
if (value.Count > 0)
{
// Add word boundaries to treat non-regular expressions as exact names
regexString = string.Concat("\\b", value[0], "\\b");
}
for (int i = 1; i < value.Count; i++)
{
string anim = value[i];
// Add or to speed up the test rather than building N regular expressions
regexString = string.Concat(regexString, "|", "\\b", anim, "\\b");
}
return new System.Text.RegularExpressions.Regex(regexString);
}
// ================================================================================
// Sprite Data
// --------------------------------------------------------------------------------
public SpriteMetaData[] GetSpriteSheet(SpriteAlignment spriteAlignment, float customX, float customY)
{
SpriteMetaData[] metaData = new SpriteMetaData[frames.Count];
for (int i = 0; i < frames.Count; i++)
{
ImportedAnimationFrame spriteInfo = frames[i];
SpriteMetaData spriteMetaData = new SpriteMetaData();
// sprite alignment
spriteMetaData.alignment = (int)spriteAlignment;
if (spriteAlignment == SpriteAlignment.Custom)
{
spriteMetaData.pivot.x = customX;
spriteMetaData.pivot.y = customY;
}
spriteMetaData.name = spriteInfo.name;
spriteMetaData.rect = new Rect(spriteInfo.x, spriteInfo.y, spriteInfo.width, spriteInfo.height);
metaData[i] = spriteMetaData;
}
return metaData;
}
public void ApplySpriteNamingScheme(SpriteNamingScheme namingScheme)
{
const string NAME_DELIMITER = "_";
if (namingScheme == SpriteNamingScheme.Classic)
{
for (int i = 0; i < frames.Count; i++)
{
frames[i].name = name + " " + i.ToString();
}
}
else
{
foreach (var anim in animations)
{
for (int i = 0; i < anim.frames.Length; i++)
{
var animFrame = anim.frames[i];
switch (namingScheme)
{
case SpriteNamingScheme.FileAnimationZero:
animFrame.name = name + NAME_DELIMITER + anim.name + NAME_DELIMITER + i.ToString();
break;
case SpriteNamingScheme.FileAnimationOne:
animFrame.name = name + NAME_DELIMITER + anim.name + NAME_DELIMITER + (i + 1).ToString();
break;
case SpriteNamingScheme.AnimationZero:
animFrame.name = anim.name + NAME_DELIMITER + i.ToString();
break;
case SpriteNamingScheme.AnimationOne:
animFrame.name = anim.name + NAME_DELIMITER + (i + 1).ToString();
break;
}
}
}
}
// remove unused frames from the list so they don't get created for the sprite sheet
for (int i = frames.Count - 1; i >= 0; i--)
{
if (string.IsNullOrEmpty(frames[i].name))
{
frames.RemoveAt(i);
}
}
}
public void ApplyCreatedSprites(Sprite[] sprites)
{
if (sprites == null)
{
return;
}
// add final Sprites to frames by comparing names
// as we can't be sure about the right order of the sprites
for (int i = 0; i < sprites.Length; i++)
{
Sprite sprite = sprites[i];
for (int k = 0; k < frames.Count; k++)
{
if (frames[k].name == sprite.name)
{
frames[k].sprite = sprite;
break;
}
}
}
}
// ================================================================================
// private methods
// --------------------------------------------------------------------------------
private void BuildIndex()
{
_animationDatabase = new Dictionary<string, ImportedAnimation>();
for (int i = 0; i < animations.Count; i++)
{
ImportedAnimation anim = animations[i];
_animationDatabase[anim.name] = anim;
}
}
}
}

View File

@@ -0,0 +1,3 @@
The Ionic.Zip.dll is part of the DotNetZip Library.
You can find more about this Library, including Source Code, over here: http://dotnetzip.codeplex.com/

View File

@@ -0,0 +1,112 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using AnimationImporter.Boomlagoon.JSON;
namespace AnimationImporter.PyxelEdit
{
public class PyxelEditData
{
public Tileset tileset = new Tileset();
public Canvas canvas = new Canvas();
public string name;
public Animations animations = new Animations();
public string version;
}
public class Tileset
{
public int tileWidth;
public int tileHeight;
public int tilesWide;
public bool fixedWidth;
public int numTiles;
}
public class Animations : Dictionary<int, Animation>
{
}
public class Canvas
{
public int width;
public int height;
public int tileWidth;
public int tileHeight;
public int numLayers;
public Layers layers = new Layers();
}
public class Layers : Dictionary<int, Layer>
{
}
public class Layer
{
public string name;
public int alpha;
public bool hidden = false;
public string blendMode = "normal";
public TileRefs tileRefs = new TileRefs();
public Texture2D texture = null;
public Layer(JSONObject obj)
{
name = obj["name"].Str;
alpha = (int)obj["alpha"].Number;
hidden = obj["hidden"].Boolean;
blendMode = obj["blendMode"].Str;
foreach (var item in obj["tileRefs"].Obj)
{
tileRefs[int.Parse(item.Key)] = new TileRef(item.Value.Obj);
}
}
}
public class TileRefs : Dictionary<int, TileRef>
{
}
public class TileRef
{
public int index;
public int rot;
public bool flipX;
public TileRef(JSONObject obj)
{
index = (int)obj["index"].Number;
rot = (int)obj["rot"].Number;
flipX = obj["flipX"].Boolean;
}
}
public class Animation
{
public string name;
public int baseTile = 0;
public int length = 7;
public int[] frameDurationMultipliers;
public int frameDuration = 200;
public Animation(JSONObject value)
{
name = value["name"].Str;
baseTile = (int)value["baseTile"].Number;
length = (int)value["length"].Number;
var list = value["frameDurationMultipliers"].Array;
frameDurationMultipliers = new int[list.Length];
for (int i = 0; i < list.Length; i++)
{
frameDurationMultipliers[i] = (int)list[i].Number;
}
frameDuration = (int)value["frameDuration"].Number;
}
}
}

View File

@@ -0,0 +1,380 @@
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using AnimationImporter.Boomlagoon.JSON;
namespace AnimationImporter.PyxelEdit
{
[InitializeOnLoad]
public class PyxelEditImporter : IAnimationImporterPlugin
{
private static PyxelEditData _latestData = null;
// ================================================================================
// static constructor, registering plugin
// --------------------------------------------------------------------------------
static PyxelEditImporter()
{
PyxelEditImporter importer = new PyxelEditImporter();
AnimationImporter.RegisterImporter(importer, "pyxel");
}
public ImportedAnimationSheet Import(AnimationImportJob job, AnimationImporterSharedConfig config)
{
if (ImportImageAndMetaInfo(job))
{
AssetDatabase.Refresh();
return GetAnimationInfo(_latestData);
}
return null;
}
public bool IsValid()
{
return IonicZipDllIsPresent();
}
public bool IsConfigured()
{
return true;
}
private static bool ImportImageAndMetaInfo(AnimationImportJob job)
{
_latestData = null;
var zipFilePath = GetFileSystemPath(job.assetDirectory + "/" + job.fileName);
var files = GetContentsFromZipFile(zipFilePath);
if (files.ContainsKey("docData.json"))
{
string jsonData = System.Text.Encoding.UTF8.GetString(files["docData.json"]);
PyxelEditData pyxelEditData = ReadJson(jsonData);
List<Layer> allLayers = new List<Layer>();
foreach (var item in pyxelEditData.canvas.layers)
{
Layer layer = item.Value;
string layerName = "layer" + item.Key.ToString() + ".png";
layer.texture = LoadTexture(files[layerName]);
allLayers.Add(layer);
}
Texture2D image = CreateBlankTexture(new Color(0f, 0f, 0f, 0), pyxelEditData.canvas.width, pyxelEditData.canvas.height);
for (int i = allLayers.Count - 1; i >= 0; i--)
{
Layer layer = allLayers[i];
if (!layer.hidden)
{
float maxAlpha = layer.alpha / 255f;
image = CombineTextures(image, layer.texture, maxAlpha);
}
}
if (!Directory.Exists(job.directoryPathForSprites))
{
Directory.CreateDirectory(job.directoryPathForSprites);
}
SaveTextureToAssetPath(image, job.imageAssetFilename);
_latestData = pyxelEditData;
return true;
}
else
{
return false;
}
}
private static ImportedAnimationSheet GetAnimationInfo(PyxelEditData data)
{
if (data == null)
{
return null;
}
int tileWidth = data.tileset.tileWidth;
int tileHeight = data.tileset.tileHeight;
int maxTileIndex = 0;
ImportedAnimationSheet animationSheet = new ImportedAnimationSheet();
animationSheet.width = data.canvas.width;
animationSheet.height = data.canvas.height;
// animations
animationSheet.animations = new List<ImportedAnimation>();
for (int i = 0; i < data.animations.Count; i++)
{
var animationData = data.animations[i];
ImportedAnimation importAnimation = new ImportedAnimation();
importAnimation.name = animationData.name;
importAnimation.firstSpriteIndex = animationData.baseTile;
importAnimation.lastSpriteIndex = animationData.baseTile + animationData.length - 1;
maxTileIndex = Mathf.Max(maxTileIndex, importAnimation.lastSpriteIndex);
ImportedAnimationFrame[] frames = new ImportedAnimationFrame[animationData.length];
for (int frameIndex = 0; frameIndex < animationData.length; frameIndex++)
{
ImportedAnimationFrame frame = new ImportedAnimationFrame();
frame.duration = animationData.frameDuration;
if (animationData.frameDurationMultipliers[frameIndex] != 100)
{
frame.duration *= (int)(animationData.frameDurationMultipliers[frameIndex] / 100f);
}
int tileIndex = animationData.baseTile + frameIndex;
int columnCount = data.canvas.width / tileWidth;
int column = tileIndex % columnCount;
int row = tileIndex / columnCount;
frame.x = column * tileWidth;
frame.y = animationSheet.height - (row + 1) * tileHeight;
frame.width = tileWidth;
frame.height = tileHeight;
frames[frameIndex] = frame;
}
importAnimation.frames = frames;
animationSheet.animations.Add(importAnimation);
}
// gather all frames used by animations for the sprite sheet
animationSheet.frames = new List<ImportedAnimationFrame>();
foreach (var anim in animationSheet.animations)
{
foreach (var frame in anim.frames)
{
animationSheet.frames.Add(frame);
}
}
return animationSheet;
}
private static PyxelEditData ReadJson(string jsonData)
{
PyxelEditData data = new PyxelEditData();
JSONObject obj = JSONObject.Parse(jsonData);
if (obj.ContainsKey("name"))
{
data.name = obj["name"].Str;
}
if (obj.ContainsKey("tileset"))
{
data.tileset.tileWidth = (int)obj["tileset"].Obj["tileWidth"].Number;
data.tileset.tileHeight = (int)obj["tileset"].Obj["tileHeight"].Number;
data.tileset.tilesWide = (int)obj["tileset"].Obj["tilesWide"].Number;
data.tileset.fixedWidth = obj["tileset"].Obj["fixedWidth"].Boolean;
data.tileset.numTiles = (int)obj["tileset"].Obj["numTiles"].Number;
}
if (obj.ContainsKey("animations"))
{
foreach (var item in obj["animations"].Obj)
{
data.animations[int.Parse(item.Key)] = new Animation(item.Value.Obj);
}
}
if (obj.ContainsKey("canvas"))
{
data.canvas.width = (int)obj["canvas"].Obj["width"].Number;
data.canvas.height = (int)obj["canvas"].Obj["height"].Number;
data.canvas.tileWidth = (int)obj["canvas"].Obj["tileWidth"].Number;
data.canvas.tileHeight = (int)obj["canvas"].Obj["tileHeight"].Number;
data.canvas.numLayers = (int)obj["canvas"].Obj["numLayers"].Number;
foreach (var item in obj["canvas"].Obj["layers"].Obj)
{
data.canvas.layers[int.Parse(item.Key)] = new Layer(item.Value.Obj);
}
}
return data;
}
public static string GetFileSystemPath(string path)
{
string basePath = Application.dataPath;
// if the path already begins with the Assets folder, remove that one from the base
if (path.StartsWith("Assets") || path.StartsWith("/Assets"))
{
basePath = basePath.Replace("/Assets", "");
}
return Path.Combine(basePath, path);
}
public static void SaveTextureToAssetPath(Texture2D texture, string assetPath)
{
string path = Application.dataPath + "/../" + assetPath;
File.WriteAllBytes(path, texture.EncodeToPNG());
}
public static Texture2D CreateBlankTexture(
Color color, int width = 2, int height = -1, TextureFormat format = TextureFormat.RGBA32,
bool mipmap = false, bool linear = false)
{
if (height < 0)
{
height = width;
}
// create empty texture
Texture2D texture = new Texture2D(width, height, format, mipmap, linear);
// get all pixels as an array
var cols = texture.GetPixels();
for (int i = 0; i < cols.Length; i++)
{
cols[i] = color;
}
// important steps to save changed pixel values
texture.SetPixels(cols);
texture.Apply();
texture.hideFlags = HideFlags.HideAndDontSave;
return texture;
}
static Texture2D LoadTexture(byte[] imageData)
{
var w = ReadInt32FromImageData(imageData, 3 + 15);
var h = ReadInt32FromImageData(imageData, 3 + 15 + 2 + 2);
var texture = new Texture2D(w, h, TextureFormat.ARGB32, false);
texture.hideFlags = HideFlags.HideAndDontSave;
texture.filterMode = FilterMode.Point;
texture.LoadImage(imageData);
return texture;
}
static int ReadInt32FromImageData(byte[] imageData, int offset)
{
return (imageData[offset] << 8) | imageData[offset + 1];
}
public static Texture2D CombineTextures(Texture2D aBaseTexture, Texture2D aToCopyTexture, float maxAlpha)
{
int aWidth = aBaseTexture.width;
int aHeight = aBaseTexture.height;
Texture2D aReturnTexture = new Texture2D(aWidth, aHeight, TextureFormat.RGBA32, false);
Color[] aBaseTexturePixels = aBaseTexture.GetPixels();
Color[] aCopyTexturePixels = aToCopyTexture.GetPixels();
Color[] aColorList = new Color[aBaseTexturePixels.Length];
int aPixelLength = aBaseTexturePixels.Length;
for (int p = 0; p < aPixelLength; p++)
{
float minA = aBaseTexturePixels[p].a;
float alpha = aCopyTexturePixels[p].a * maxAlpha;
aColorList[p] = Color.Lerp(aBaseTexturePixels[p], aCopyTexturePixels[p], alpha);
aColorList[p].a = Mathf.Lerp(minA, 1f, alpha);
}
aReturnTexture.SetPixels(aColorList);
aReturnTexture.Apply(false);
return aReturnTexture;
}
// ================================================================================
// extracting from zip file
// --------------------------------------------------------------------------------
private static Type zipFileClass = null;
private static System.Reflection.MethodInfo readZipFileMethod = null;
private static System.Reflection.MethodInfo extractMethod = null;
public static Dictionary<string, byte[]> GetContentsFromZipFile(string fileName)
{
Dictionary<string, byte[]> files = new Dictionary<string, byte[]>();
if (zipFileClass == null)
{
InitZipMethods();
}
if (zipFileClass != null)
{
using (var zipFile = readZipFileMethod.Invoke(null, new object[] { fileName }) as IDisposable)
{
var zipFileAsEnumeration = zipFile as IEnumerable;
foreach (var entry in zipFileAsEnumeration)
{
MemoryStream stream = new MemoryStream();
extractMethod.Invoke(entry, new object[] { stream });
files.Add(entry.ToString().Replace("ZipEntry::", ""), stream.ToArray());
}
}
}
return files;
}
private static void InitZipMethods()
{
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in allAssemblies)
{
zipFileClass = assembly.GetType("Ionic.Zip.ZipFile");
if (zipFileClass != null)
{
readZipFileMethod = zipFileClass.GetMethod("Read", new Type[] { typeof(string) });
Type zipEntryClass = assembly.GetType("Ionic.Zip.ZipEntry");
extractMethod = zipEntryClass.GetMethod("Extract", new Type[] { typeof(MemoryStream) });
return;
}
}
}
private static bool IonicZipDllIsPresent()
{
if (zipFileClass != null)
{
return true;
}
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in allAssemblies)
{
var zipClass = assembly.GetType("Ionic.Zip.ZipFile");
if (zipClass != null)
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.UI;
namespace AnimationImporter
{
public static class AnimationClipUtility
{
class AnimationClipSettings
{
SerializedProperty m_property;
private SerializedProperty Get(string property) { return m_property.FindPropertyRelative(property); }
public AnimationClipSettings(SerializedProperty prop) { m_property = prop; }
public float startTime { get { return Get("m_StartTime").floatValue; } set { Get("m_StartTime").floatValue = value; } }
public float stopTime { get { return Get("m_StopTime").floatValue; } set { Get("m_StopTime").floatValue = value; } }
public float orientationOffsetY { get { return Get("m_OrientationOffsetY").floatValue; } set { Get("m_OrientationOffsetY").floatValue = value; } }
public float level { get { return Get("m_Level").floatValue; } set { Get("m_Level").floatValue = value; } }
public float cycleOffset { get { return Get("m_CycleOffset").floatValue; } set { Get("m_CycleOffset").floatValue = value; } }
public bool loopTime { get { return Get("m_LoopTime").boolValue; } set { Get("m_LoopTime").boolValue = value; } }
public bool loopBlend { get { return Get("m_LoopBlend").boolValue; } set { Get("m_LoopBlend").boolValue = value; } }
public bool loopBlendOrientation { get { return Get("m_LoopBlendOrientation").boolValue; } set { Get("m_LoopBlendOrientation").boolValue = value; } }
public bool loopBlendPositionY { get { return Get("m_LoopBlendPositionY").boolValue; } set { Get("m_LoopBlendPositionY").boolValue = value; } }
public bool loopBlendPositionXZ { get { return Get("m_LoopBlendPositionXZ").boolValue; } set { Get("m_LoopBlendPositionXZ").boolValue = value; } }
public bool keepOriginalOrientation { get { return Get("m_KeepOriginalOrientation").boolValue; } set { Get("m_KeepOriginalOrientation").boolValue = value; } }
public bool keepOriginalPositionY { get { return Get("m_KeepOriginalPositionY").boolValue; } set { Get("m_KeepOriginalPositionY").boolValue = value; } }
public bool keepOriginalPositionXZ { get { return Get("m_KeepOriginalPositionXZ").boolValue; } set { Get("m_KeepOriginalPositionXZ").boolValue = value; } }
public bool heightFromFeet { get { return Get("m_HeightFromFeet").boolValue; } set { Get("m_HeightFromFeet").boolValue = value; } }
public bool mirror { get { return Get("m_Mirror").boolValue; } set { Get("m_Mirror").boolValue = value; } }
}
public static void SetLoop(this AnimationClip clip, bool value)
{
SerializedObject serializedClip = new SerializedObject(clip);
AnimationClipSettings clipSettings = new AnimationClipSettings(serializedClip.FindProperty("m_AnimationClipSettings"));
clipSettings.loopTime = value;
clipSettings.loopBlend = false;
serializedClip.ApplyModifiedProperties();
}
// ================================================================================
// curve bindings
// --------------------------------------------------------------------------------
public static EditorCurveBinding spriteRendererCurveBinding
{
get
{
return new EditorCurveBinding
{
path = "", // assume SpriteRenderer is at same GameObject as AnimationController
type = typeof(SpriteRenderer),
propertyName = "m_Sprite"
};
}
}
public static EditorCurveBinding imageCurveBinding
{
get
{
return new EditorCurveBinding
{
path = "", // assume Image is at same GameObject as AnimationController
type = typeof(Image),
propertyName = "m_Sprite"
};
}
}
}
}

View File

@@ -0,0 +1,156 @@
using UnityEditor;
using UnityEngine;
using System.IO;
namespace AnimationImporter
{
/// <summary>
/// Utilities for Unity's built in AssetDatabase class
/// </summary>
public static class AssetDatabaseUtility
{
public const char UnityDirectorySeparator = '/';
public const string ResourcesFolderName = "Resources";
public static string projectPath
{
get
{
return Application.dataPath.RemoveLastLetters("/Assets".Length);
}
}
/// <summary>
/// Creates the asset and any directories that are missing along its path.
/// </summary>
/// <param name="unityObject">UnityObject to create an asset for.</param>
/// <param name="unityFilePath">Unity file path (e.g. "Assets/Resources/MyFile.asset".</param>
public static void CreateAssetAndDirectories(UnityEngine.Object unityObject, string unityFilePath)
{
var pathDirectory = Path.GetDirectoryName(unityFilePath) + UnityDirectorySeparator;
// necessary fix for Windows because Path.GetDirectoryName is changing the directory separators to Windows style
pathDirectory = pathDirectory.Replace('\\', UnityDirectorySeparator);
CreateDirectoriesInPath(pathDirectory);
AssetDatabase.CreateAsset(unityObject, unityFilePath);
}
private static void CreateDirectoriesInPath(string unityDirectoryPath)
{
// Check that last character is a directory separator
if (unityDirectoryPath[unityDirectoryPath.Length - 1] != UnityDirectorySeparator)
{
var warningMessage = string.Format(
"Path supplied to CreateDirectoriesInPath that does not include a DirectorySeparator " +
"as the last character." +
"\nSupplied Path: {0}, Filename: {1}",
unityDirectoryPath);
Debug.LogWarning(warningMessage);
}
// Warn and strip filenames
var filename = Path.GetFileName(unityDirectoryPath);
if (!string.IsNullOrEmpty(filename))
{
var warningMessage = string.Format(
"Path supplied to CreateDirectoriesInPath that appears to include a filename. It will be " +
"stripped. A path that ends with a DirectorySeparate should be supplied. " +
"\nSupplied Path: {0}, Filename: {1}",
unityDirectoryPath,
filename);
Debug.LogWarning(warningMessage);
unityDirectoryPath = unityDirectoryPath.Replace(filename, string.Empty);
}
var folders = unityDirectoryPath.Split(UnityDirectorySeparator);
// Error if path does NOT start from Assets
if (folders.Length > 0 && folders[0] != "Assets")
{
var exceptionMessage = "AssetDatabaseUtility CreateDirectoriesInPath expects full Unity path, including 'Assets\\\". " +
"Adding Assets to path.";
throw new UnityException(exceptionMessage);
}
string pathToFolder = string.Empty;
foreach (var folder in folders)
{
// Don't check for or create empty folders
if (string.IsNullOrEmpty(folder))
{
continue;
}
// Create folders that don't exist
pathToFolder = string.Concat(pathToFolder, folder);
if (!UnityEditor.AssetDatabase.IsValidFolder(pathToFolder))
{
var pathToParent = System.IO.Directory.GetParent(pathToFolder).ToString();
AssetDatabase.CreateFolder(pathToParent, folder);
AssetDatabase.Refresh();
}
pathToFolder = string.Concat(pathToFolder, UnityDirectorySeparator);
}
}
/// <summary>
/// get a valid path for the AssetDatabase from absolute path or subpath
/// </summary>
/// <param name="path">absolute path or subpath like "Resources"</param>
/// <returns>path relative to the project directory</returns>
public static string GetAssetPath(string path)
{
if (path == null)
{
return null;
}
path = path.Remove(projectPath);
if (path.StartsWith("\\"))
{
path = path.Remove(0, 1);
}
if (path.StartsWith("/"))
{
path = path.Remove(0, 1);
}
if (!path.StartsWith("Assets") && !path.StartsWith("/Assets"))
{
path = Path.Combine("Assets", path);
}
return path;
}
// ================================================================================
// string extensions
// --------------------------------------------------------------------------------
private static string RemoveLastLetters(this string s, int letterCount)
{
if (string.IsNullOrEmpty(s))
{
return s;
}
if (letterCount > s.Length)
{
letterCount = s.Length;
}
return s.Remove(s.Length - letterCount);
}
private static string Remove(this string s, string exactExpression)
{
return s.Replace(exactExpression, "");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
using System.Collections;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace AnimationImporter
{
/// <summary>
/// Utility functions for ScriptableObjects.
/// </summary>
public static class ScriptableObjectUtility
{
/// <summary>
/// Loads the save data from a Unity relative path. Returns null if the data doesn't exist.
/// </summary>
/// <returns>The saved data as a ScriptableObject, or null if not found.</returns>
/// <param name="unityPathToFile">Unity path to file (e.g. "Assets/Resources/MyFile.asset")</param>
/// <typeparam name="T">The ScriptableObject type</typeparam>
public static T LoadSaveData<T> (string unityPathToFile) where T : ScriptableObject
{
// Path must contain Resources folder
var resourcesFolder = string.Concat(
AssetDatabaseUtility.UnityDirectorySeparator,
AssetDatabaseUtility.ResourcesFolderName,
AssetDatabaseUtility.UnityDirectorySeparator);
if (!unityPathToFile.Contains(resourcesFolder))
{
var exceptionMessage = string.Format(
"Failed to Load ScriptableObject of type, {0}, from path: {1}. " +
"Path must begin with Assets and include a directory within the Resources folder.",
typeof(T).ToString(),
unityPathToFile);
throw new UnityException(exceptionMessage);
}
// Get Resource relative path - Resource path should only include folders underneath Resources and no file extension
var resourceRelativePath = GetResourceRelativePath(unityPathToFile);
// Remove file extension
var fileExtension = System.IO.Path.GetExtension(unityPathToFile);
resourceRelativePath = resourceRelativePath.Replace(fileExtension, string.Empty);
return Resources.Load<T>(resourceRelativePath);
}
/// <summary>
/// Loads the saved data, stored as a ScriptableObject at the specified path. If the file or folders don't exist,
/// it creates them.
/// </summary>
/// <returns>The saved data as a ScriptableObject.</returns>
/// <param name="unityPathToFile">Unity path to file (e.g. "Assets/Resources/MyFile.asset")</param>
/// <typeparam name="T">The ScriptableObject type</typeparam>
public static T LoadOrCreateSaveData<T>(string unityPathToFile) where T : ScriptableObject
{
var loadedSettings = LoadSaveData<T>(unityPathToFile);
if (loadedSettings == null)
{
loadedSettings = ScriptableObject.CreateInstance<T>();
AssetDatabaseUtility.CreateAssetAndDirectories(loadedSettings, unityPathToFile);
}
return loadedSettings;
}
private static string GetResourceRelativePath(string unityPath)
{
var resourcesFolder = AssetDatabaseUtility.ResourcesFolderName + AssetDatabaseUtility.UnityDirectorySeparator;
var pathToResources = unityPath.Substring(0, unityPath.IndexOf(resourcesFolder));
// Remove all folders leading up to the Resources folder
pathToResources = unityPath.Replace(pathToResources, string.Empty);
// Remove the Resources folder
pathToResources = pathToResources.Replace(resourcesFolder, string.Empty);
return pathToResources;
}
}
}