testss
This commit is contained in:
124
Assets/AnimationImporter/Editor/AnimationAssetPostProcessor.cs
Normal file
124
Assets/AnimationImporter/Editor/AnimationAssetPostProcessor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
154
Assets/AnimationImporter/Editor/AnimationImportJob.cs
Normal file
154
Assets/AnimationImporter/Editor/AnimationImportJob.cs
Normal 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, "");
|
||||
}
|
||||
}
|
||||
}
|
728
Assets/AnimationImporter/Editor/AnimationImporter.cs
Normal file
728
Assets/AnimationImporter/Editor/AnimationImporter.cs
Normal 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, "");
|
||||
}
|
||||
}
|
||||
}
|
432
Assets/AnimationImporter/Editor/AnimationImporterWindow.cs
Normal file
432
Assets/AnimationImporter/Editor/AnimationImporterWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
314
Assets/AnimationImporter/Editor/Aseprite/AsepriteImporter.cs
Normal file
314
Assets/AnimationImporter/Editor/Aseprite/AsepriteImporter.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
public enum AnimationTargetObjectType : int
|
||||
{
|
||||
SpriteRenderer,
|
||||
Image,
|
||||
SpriteRendererAndImage
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace AnimationImporter
|
||||
{
|
||||
public enum AssetTargetLocationType : int
|
||||
{
|
||||
SameDirectory,
|
||||
SubDirectory,
|
||||
GlobalDirectory
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace AnimationImporter
|
||||
{
|
||||
public enum ImportAnimatorController
|
||||
{
|
||||
None,
|
||||
AnimatorController,
|
||||
AnimatorOverrideController
|
||||
}
|
||||
}
|
102
Assets/AnimationImporter/Editor/Config/PreviousImportSettings.cs
Normal file
102
Assets/AnimationImporter/Editor/Config/PreviousImportSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
79
Assets/AnimationImporter/Editor/Config/SpriteNaming.cs
Normal file
79
Assets/AnimationImporter/Editor/Config/SpriteNaming.cs
Normal 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 "";
|
||||
}
|
||||
}
|
||||
}
|
14
Assets/AnimationImporter/Editor/IAnimationImporterPlugin.cs
Normal file
14
Assets/AnimationImporter/Editor/IAnimationImporterPlugin.cs
Normal 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();
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
Assets/AnimationImporter/Editor/PyxelEdit/Ionic.Zip.dll
Normal file
BIN
Assets/AnimationImporter/Editor/PyxelEdit/Ionic.Zip.dll
Normal file
Binary file not shown.
@@ -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/
|
112
Assets/AnimationImporter/Editor/PyxelEdit/PyxelEditData.cs
Normal file
112
Assets/AnimationImporter/Editor/PyxelEdit/PyxelEditData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
380
Assets/AnimationImporter/Editor/PyxelEdit/PyxelEditImporter.cs
Normal file
380
Assets/AnimationImporter/Editor/PyxelEdit/PyxelEditImporter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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, "");
|
||||
}
|
||||
}
|
||||
}
|
1014
Assets/AnimationImporter/Editor/Utilities/JSONObject.cs
Normal file
1014
Assets/AnimationImporter/Editor/Utilities/JSONObject.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user