diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ea60375
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,16 @@
+##### 1.0.4
+* Fix SpriterImporter to pass prefab instead of destroyed tmp object
+
+##### 1.0.3
+* Fix bug when no keyframe in t = 0
+* Fix bug when keyframe has no object_refs
+
+##### 1.0.2
+* Fix changing pivots when blending animations
+
+##### 1.0.1
+* Add XML Documentation to SpriterAnimator
+* Fix blend compatibility test
+* Fix read only properties in SpriterAnimator
+* Fix bug with weird behaviour on extremely short transition time
+* Move first animation init from SpriterAnimator cosntructor to Step method
\ No newline at end of file
diff --git a/README.md b/README.md
index 00bfd9c..bc4ec88 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,19 @@ Being a pure C# implementation, SpriterDotNet doesn't depend on any external lib
6. Call Step in every frame
7. Control the animation with properties
+### SpriterAnimator
+This class contains the majority of Properties and Methods necessary to control the animation.
+
+###### Properties
+* Speed - Playback speed. Negative speeds reverse the animation
+* Time - The current time in animation in milliseconds
+* Progress - The progress of animation. Ranges from 0.0f to 1.0f
+
+###### Methods
+* Play(string name) - Plays the given animation
+* Transition(string name, float totalTransitionTime) - Transitions to given animation doing a progressive blend in the given time
+* Blend - Blends two animations with the given weight factor
+
#### Points
* Override SpriterAnimator.ApplyPointTransform
diff --git a/SpriterDotNet.MonoGame/SpriterGame.cs b/SpriterDotNet.MonoGame/SpriterGame.cs
index beaa004..3252212 100644
--- a/SpriterDotNet.MonoGame/SpriterGame.cs
+++ b/SpriterDotNet.MonoGame/SpriterGame.cs
@@ -132,7 +132,6 @@ protected override void Update(GameTime gameTime)
string entity = currentAnimator.Entity.Name;
status = String.Format("{0} : {1}", entity, currentAnimator.Name);
metadata = "Variables:\n" + GetVarValues() + "\nTags:\n" + GetTagValues();
-
base.Update(gameTime);
}
diff --git a/SpriterDotNet.UnitTests/SpriterDotNet.UnitTests.csproj b/SpriterDotNet.UnitTests/SpriterDotNet.UnitTests.csproj
index 55f408f..25fc8f4 100644
--- a/SpriterDotNet.UnitTests/SpriterDotNet.UnitTests.csproj
+++ b/SpriterDotNet.UnitTests/SpriterDotNet.UnitTests.csproj
@@ -9,8 +9,9 @@
Properties
SpriterDotNet.UnitTests
SpriterDotNet.UnitTests
- v4.5.2
+ v4.5
512
+
true
diff --git a/SpriterDotNet.Unity/Assets/SpriterDotNet/Lib/SpriterDotNet.dll b/SpriterDotNet.Unity/Assets/SpriterDotNet/Lib/SpriterDotNet.dll
index ad726e2..eab6b9d 100644
Binary files a/SpriterDotNet.Unity/Assets/SpriterDotNet/Lib/SpriterDotNet.dll and b/SpriterDotNet.Unity/Assets/SpriterDotNet/Lib/SpriterDotNet.dll differ
diff --git a/SpriterDotNet.Unity/Assets/SpriterDotNet/SpriterImporter.cs b/SpriterDotNet.Unity/Assets/SpriterDotNet/SpriterImporter.cs
index 571a115..8d960df 100644
--- a/SpriterDotNet.Unity/Assets/SpriterDotNet/SpriterImporter.cs
+++ b/SpriterDotNet.Unity/Assets/SpriterDotNet/SpriterImporter.cs
@@ -22,8 +22,8 @@ public class SpriterImporter : AssetPostprocessor
public static float DeltaZ = -0.001f;
public static bool UseNativeTags = true;
-
- public static event Action EntityImported = (e,g) => { };
+
+ public static event Action EntityImported = (e, p) => { };
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath)
{
@@ -65,18 +65,18 @@ private static void CreateSpriter(string path)
CreateSprites(entity, cd, spriter, sprites);
CreateCollisionRectangles(entity, cd, spriter, metadata);
CreatePoints(entity, cd, spriter, metadata);
-
+
behaviour.EntityIndex = entity.Id;
behaviour.enabled = true;
behaviour.SpriterData = spriterData;
behaviour.ChildData = cd;
- CreatePrefab(go, rootFolder);
+ GameObject prefab = CreatePrefab(go, rootFolder);
- EntityImported(entity, go);
+ EntityImported(entity, prefab);
}
- if(UseNativeTags) CreateTags(spriter);
+ if (UseNativeTags) CreateTags(spriter);
}
private static SpriterData CreateSpriterData(Spriter spriter, string rootFolder, string name)
@@ -91,15 +91,18 @@ private static SpriterData CreateSpriterData(Spriter spriter, string rootFolder,
return data;
}
- private static void CreatePrefab(GameObject go, string folder)
+ private static GameObject CreatePrefab(GameObject go, string folder)
{
string prefabPath = folder + "/" + go.name + ".prefab";
GameObject existing = AssetDatabase.LoadAssetAtPath(prefabPath);
- if (existing != null) PrefabUtility.ReplacePrefab(go, existing, ReplacePrefabOptions.Default);
- else PrefabUtility.CreatePrefab(prefabPath, go, ReplacePrefabOptions.Default);
+ GameObject prefab;
+ if (existing != null) prefab = PrefabUtility.ReplacePrefab(go, existing, ReplacePrefabOptions.Default);
+ else prefab = PrefabUtility.CreatePrefab(prefabPath, go, ReplacePrefabOptions.Default);
GameObject.DestroyImmediate(go);
+
+ return prefab;
}
private static void CreateSprites(SpriterEntity entity, ChildData cd, Spriter spriter, GameObject parent)
diff --git a/SpriterDotNet.Unity/README.md b/SpriterDotNet.Unity/README.md
index 00bb427..3827dc0 100644
--- a/SpriterDotNet.Unity/README.md
+++ b/SpriterDotNet.Unity/README.md
@@ -6,16 +6,43 @@ This distribution comes with 2 different packages:
* SpriterDotNet.Unity.unitypackage (contains just the library)
* SpriterDotNet.Unity.Examples.unitypackage (contains the library and a couple of examples)
+## Basic Usage
+The most easy way of using SpriterDotBNet.Unity with your own animations is the following:
+* Install SpriterDotNet.Unity.Examples.unitypackage
+* Create a new scene
+* Copy your scml and all required files into the Assets folder in your project
+* Drag and drop the generated .prefab to the scene (SpriterDotNet.Unity generates a prefab for each entity in the scml)
+* Drag and drop Controller.prefab to the scene
+* (Optional) Drag and drop Canvas.prefab to the scene to have on-screen instructions about key mappings
+
+**Note that the example Controller supports ony one prefab per scene. If you add multiples, it's only going to affect one of them.**
+
+## Usage
+The usual way of using SpriterDotNet.Unity is to get a reference to a UnitySpriterAnimator from a SpriterDotNetBehaviour and controlling the animation as described in the [SpriterAnimator documentation](https://github.com/loodakrawa/SpriterDotNet/tree/develop#spriteranimator).
+
+###### Getting a reference tp UnitySpriterAnimator
+In a script attached to the same GameObject which also contains SpriterDotNetBehaviour:
+```csharp
+UnitySpriterAnimator animator = gameObject.GetComponent().Animator;
+```
+
+In a script attached to other GameObjects:
+```csharp
+UnitySpriterAnimator animator = FindObjectOfType().Animator;
+```
+
## Tags
SpriterDotNet creates Unity tags while importing Spriter assets. This behaviour can be controlled with the UseNativeTags
-flag in SpriterImporter. Unfortunately Unity doesn't support multiple tags so SpriterDotNet just sets the first tag as the
-GameObject tag. Other tags can be accessed via the Metadata property of UnitySpriterAnimator.
+flag in SpriterImporter. Unfortunately Unity doesn't support multiple tags so SpriterDotNet just sets the first tag as the GameObject tag. Other tags can be accessed via the Metadata property of UnitySpriterAnimator.
## Importer Hooks
If you want to do post processing on imported assets, SpriterImporter exposes the EntityImported event. See
TestSpriterImportHook in the examples project.
-## Usability
+## Generated ScriptableObjects
+SpriterDotNet.Unity generates one ScriptableObject per .scml file to hold all parsed scml data which is shared across all entities defined in the same scml. (For example, for squares.scml a squares.asset object gets generated). This means that for most changes to the .scml get reflected in Unity automatically.
+
+## Features
This plugin has been designed with most common scenarios in mind. However, it's hard to cover all possible use-cases.
If you have good ideas about improving functionality and/or ease of use feel free to suggest it either on [Spriter Forum](http://brashmonkey.com/forum/index.php?/topic/4166-spriterdotnet-an-implementation-for-all-c-frameworks/)
or here on GitHub (as an Enhancement Issue).
diff --git a/SpriterDotNet.Unity/SpriterDotNet.Unity.Examples.unitypackage b/SpriterDotNet.Unity/SpriterDotNet.Unity.Examples.unitypackage
index 9bc88a2..70ed289 100644
Binary files a/SpriterDotNet.Unity/SpriterDotNet.Unity.Examples.unitypackage and b/SpriterDotNet.Unity/SpriterDotNet.Unity.Examples.unitypackage differ
diff --git a/SpriterDotNet.Unity/SpriterDotNet.Unity.unitypackage b/SpriterDotNet.Unity/SpriterDotNet.Unity.unitypackage
index 6e408ea..b935e76 100644
Binary files a/SpriterDotNet.Unity/SpriterDotNet.Unity.unitypackage and b/SpriterDotNet.Unity/SpriterDotNet.Unity.unitypackage differ
diff --git a/SpriterDotNet/SpriterAnimator.cs b/SpriterDotNet/SpriterAnimator.cs
index 74b2b13..a65240d 100644
--- a/SpriterDotNet/SpriterAnimator.cs
+++ b/SpriterDotNet/SpriterAnimator.cs
@@ -11,25 +11,71 @@ namespace SpriterDotNet
{
public abstract class SpriterAnimator
{
+ ///
+ /// Occurs when the animation finishes playing or loops.
+ ///
public event Action AnimationFinished = s => { };
+
+ ///
+ /// Occurs when an animation events gets triggered.
+ ///
public event Action EventTriggered = s => { };
- public Spriter Spriter { get; private set; }
+ ///
+ /// The animated Entity.
+ ///
public SpriterEntity Entity { get; private set; }
+
+ ///
+ /// The current animation.
+ ///
public SpriterAnimation CurrentAnimation { get; private set; }
+
+ ///
+ /// The animation transitioned to or blended with the current animation.
+ ///
public SpriterAnimation NextAnimation { get; private set; }
+
+ ///
+ /// The current character map. If set to null, the default is used.
+ ///
public SpriterCharacterMap CharacterMap { get; set; }
+ ///
+ /// The name of the current animation.
+ ///
public string Name { get; private set; }
+
+ ///
+ /// Playback speed. Defaults to 1.0f. Negative values reverse the animation.
+ /// For example:
+ /// 0.5f corresponds to 50% of the default speed
+ /// 2.0f corresponds to 200% of the default speed
+ ///
public float Speed { get; set; }
- public float Length { get; set; }
+
+ ///
+ /// The legth of the current animation in milliseconds.
+ ///
+ public float Length { get; private set; }
+
+ ///
+ /// The current time in milliseconds.
+ ///
public float Time { get; set; }
+
+ ///
+ /// The current progress. Ranges from 0.0f - 1.0f.
+ ///
public float Progress
{
get { return Time / Length; }
set { Time = value * Length; }
}
+ ///
+ /// Contains all the frame metadata. Updated on every call to Step.
+ ///
public FrameMetadata Metadata { get; private set; }
private readonly IDictionary animations;
@@ -39,37 +85,53 @@ public float Progress
private float transitionTime;
private float factor;
- public SpriterAnimator(SpriterEntity entity)
+ ///
+ /// Sole constructor. Creates a new instance which animates the given entity.
+ ///
+ protected SpriterAnimator(SpriterEntity entity)
{
Entity = entity;
- Spriter = entity.Spriter;
animations = entity.Animations.ToDictionary(a => a.Name, a => a);
Speed = 1.0f;
- Play(animations.Keys.First());
Metadata = new FrameMetadata();
}
+ ///
+ /// Returns a list of all the animations for the entity
+ ///
public IEnumerable GetAnimations()
{
return animations.Keys;
}
+ ///
+ /// Register the sprite for the given folderId and fileId.
+ ///
public void Register(int folderId, int fileId, TSprite obj)
{
AddToDict(folderId, fileId, obj, sprites);
}
+ ///
+ /// Register the sound for the given folderId and fileId.
+ ///
public void Register(int folderId, int fileId, TSound obj)
{
AddToDict(folderId, fileId, obj, sounds);
}
+ ///
+ /// Plays the animation with the given name. Playback starts from the beginning.
+ ///
public virtual void Play(string name)
{
SpriterAnimation animation = animations[name];
Play(animation);
}
+ ///
+ /// Plays the given animation. Playback starts from the beginning.
+ ///
public virtual void Play(SpriterAnimation animation)
{
Progress = 0;
@@ -81,13 +143,26 @@ public virtual void Play(SpriterAnimation animation)
Length = CurrentAnimation.Length;
}
+ ///
+ /// Transitions to given animation doing a progressive blend in the given time.
+ /// Animation blending works only for animations with identical hierarchies.
+ ///
public virtual void Transition(string name, float totalTransitionTime)
{
this.totalTransitionTime = totalTransitionTime;
- transitionTime = 0;
+ transitionTime = 0.0f;
+ factor = 0.0f;
NextAnimation = animations[name];
}
+ ///
+ /// Blends two animations with the given weight factor. Factor ranges from 0.0f - 1.0f.
+ /// Animation blending works only for animations with identical hierarchies.
+ /// For example:
+ /// factor == 0.0f corresponds to 100% of the first animation and 0% of the second
+ /// factor == 0.25f corresponds to 75% of the first animation and 25% of the second
+ /// factor == 0.5f corresponds to 50% of each animation
+ ///
public virtual void Blend(string first, string second, float factor)
{
Play(first);
@@ -96,8 +171,13 @@ public virtual void Blend(string first, string second, float factor)
this.factor = factor;
}
+ ///
+ /// Advances the animation for the deltaTime increment.
+ ///
public virtual void Step(float deltaTime)
{
+ if(CurrentAnimation == null) Play(animations.Keys.First());
+
float elapsed = deltaTime * Speed;
if (NextAnimation != null && totalTransitionTime != 0.0f)
@@ -133,6 +213,9 @@ public virtual void Step(float deltaTime)
Animate(elapsed);
}
+ ///
+ /// Gets the transform information for all object types and calls the relevant apply method for each one.
+ ///
protected virtual void Animate(float deltaTime)
{
FrameData frameData;
@@ -159,7 +242,6 @@ protected virtual void Animate(float deltaTime)
foreach (SpriterSound info in metaData.Sounds)
{
-
TSound sound = GetFromDict(info.FolderId, info.FileId, sounds);
PlaySound(sound, info);
}
@@ -171,7 +253,10 @@ protected virtual void Animate(float deltaTime)
Metadata = metaData;
}
- protected bool GetSpriteIds(SpriterObject obj, out int folderId, out int fileId)
+ ///
+ /// Gets the folderId and fileId for the given SpriterObject based on the current character map or default
+ ///
+ protected virtual bool GetSpriteIds(SpriterObject obj, out int folderId, out int fileId)
{
folderId = obj.FolderId;
fileId = obj.FileId;
@@ -190,22 +275,37 @@ protected bool GetSpriteIds(SpriterObject obj, out int folderId, out int fileId)
return false;
}
+ ///
+ /// Applies the transform to the concrete sprite isntance.
+ ///
protected virtual void ApplySpriteTransform(TSprite sprite, SpriterObject info)
{
}
+ ///
+ /// Plays the concrete sound isntance.
+ ///
protected virtual void PlaySound(TSound sound, SpriterSound info)
{
}
+ ///
+ /// Applies the transforms for the point with the given name.
+ ///
protected virtual void ApplyPointTransform(string name, SpriterObject info)
{
}
+ ///
+ /// Applies the transform for the given box.
+ ///
protected virtual void ApplyBoxTransform(SpriterObjectInfo objInfo, SpriterObject info)
{
}
+ ///
+ /// Dispatches event when triggered in animation.
+ ///
protected virtual void DispatchEvent(string eventName)
{
EventTriggered(eventName);
diff --git a/SpriterDotNet/SpriterProcessor.cs b/SpriterDotNet/SpriterProcessor.cs
index 325d725..4c2c350 100644
--- a/SpriterDotNet/SpriterProcessor.cs
+++ b/SpriterDotNet/SpriterProcessor.cs
@@ -23,11 +23,7 @@ public static FrameData GetFrameData(SpriterAnimation first, SpriterAnimation se
SpriterMainlineKey secondKeyB;
GetMainlineKeys(second.MainlineKeys, targetTimeSecond, out secondKeyA, out secondKeyB);
- if (firstKeyA.BoneRefs.Length != secondKeyA.BoneRefs.Length
- || firstKeyB.BoneRefs.Length != secondKeyB.BoneRefs.Length
- || firstKeyA.ObjectRefs.Length != secondKeyA.ObjectRefs.Length
- || firstKeyB.ObjectRefs.Length != secondKeyB.ObjectRefs.Length)
- return GetFrameData(first, targetTime);
+ if (!WillItBlend(firstKeyA, secondKeyA) || !WillItBlend(firstKeyB, secondKeyB)) return GetFrameData(first, targetTime);
float adjustedTimeFirst = AdjustTime(firstKeyA, firstKeyB, first.Length, targetTime);
float adjustedTimeSecond = AdjustTime(secondKeyA, secondKeyB, second.Length, targetTimeSecond);
@@ -63,6 +59,8 @@ public static FrameData GetFrameData(SpriterAnimation first, SpriterAnimation se
SpriterObject info = Interpolate(interpolatedFirst, interpolatedSecond, factor, 1);
info.Angle = MathHelper.CloserAngleLinear(interpolatedFirst.Angle, interpolatedSecond.Angle, factor);
+ info.PivotX = MathHelper.Linear(interpolatedFirst.PivotX, interpolatedSecond.PivotX, factor);
+ info.PivotY = MathHelper.Linear(interpolatedFirst.PivotY, interpolatedSecond.PivotY, factor);
if (boneInfos != null && objectRefFirst.ParentId >= 0) ApplyParentTransform(info, boneInfos[objectRefFirst.ParentId]);
@@ -72,6 +70,25 @@ public static FrameData GetFrameData(SpriterAnimation first, SpriterAnimation se
return frameData;
}
+ private static bool WillItBlend(SpriterMainlineKey firstKey, SpriterMainlineKey secondKey)
+ {
+ if (firstKey.BoneRefs != null)
+ {
+ if (secondKey.BoneRefs == null) return false;
+ if (firstKey.BoneRefs.Length != secondKey.BoneRefs.Length) return false;
+ }
+ else if (secondKey.BoneRefs != null) return false;
+
+ if (firstKey.ObjectRefs != null)
+ {
+ if (secondKey.ObjectRefs == null) return false;
+ if (firstKey.ObjectRefs.Length != secondKey.ObjectRefs.Length) return false;
+ }
+ else if (secondKey.ObjectRefs != null) return false;
+
+ return true;
+ }
+
public static FrameData GetFrameData(SpriterAnimation animation, float targetTime, SpriterSpatial parentInfo = null)
{
SpriterMainlineKey keyA;
@@ -84,6 +101,8 @@ public static FrameData GetFrameData(SpriterAnimation animation, float targetTim
FrameData frameData = new FrameData();
+ if (keyA.ObjectRefs == null) return frameData;
+
foreach (SpriterObjectRef objectRef in keyA.ObjectRefs)
{
SpriterObject interpolated = GetObjectInfo(objectRef, animation, adjustedTime);
@@ -280,6 +299,7 @@ private static float AdjustTime(SpriterKey keyA, SpriterKey keyB, float animatio
private static void GetMainlineKeys(SpriterMainlineKey[] keys, float targetTime, out SpriterMainlineKey keyA, out SpriterMainlineKey keyB)
{
keyA = LastKeyForTime(keys, targetTime);
+ keyA = keyA ?? keys[keys.Length - 1];
int nextKey = keyA.Id + 1;
if (nextKey >= keys.Length) nextKey = 0;
keyB = keys[nextKey];