From 5021c83afa1d6a6b1f20278541a298f87d0c194b Mon Sep 17 00:00:00 2001 From: EliyaFishman <45822259+EliyaFishman@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:09:15 +0200 Subject: [PATCH] Initial bread2unity commit --- Assets/Editor/bread2unity/AnimationCreator.cs | 205 ++++++++++ ...prite.cs.meta => AnimationCreator.cs.meta} | 2 +- Assets/Editor/bread2unity/AnimationFixer.cs | 381 ++++++++++++++++++ ...imation.cs.meta => AnimationFixer.cs.meta} | 2 +- Assets/Editor/bread2unity/BCCAD.cs | 158 +++++--- Assets/Editor/bread2unity/BccadPrefab.cs | 134 ++++++ ...IDataModel.cs.meta => BccadPrefab.cs.meta} | 2 +- Assets/Editor/bread2unity/BccadTest.cs | 61 +++ Assets/Editor/bread2unity/BccadTest.cs.meta | 11 + Assets/Editor/bread2unity/Bread2Unity.cs | 117 +++++- Assets/Editor/bread2unity/ByteBuffer.cs | 52 +++ Assets/Editor/bread2unity/ByteBuffer.cs.meta | 11 + Assets/Editor/bread2unity/Model/Animation.cs | 35 ++ .../bread2unity/Model/Animation.cs.meta | 11 + .../Editor/bread2unity/Model/BCCADSprite.cs | 70 ++++ .../bread2unity/Model/BCCADSprite.cs.meta | 11 + Assets/Editor/bread2unity/Model/DataModel.cs | 52 +++ .../bread2unity/Model/DataModel.cs.meta | 11 + Assets/Editor/bread2unity/Model/IAnimation.cs | 24 -- Assets/Editor/bread2unity/Model/IDataModel.cs | 13 - Assets/Editor/bread2unity/Model/ISprite.cs | 31 -- Assets/Editor/bread2unity/PrefabCreator.cs | 83 ++++ .../Editor/bread2unity/PrefabCreator.cs.meta | 11 + Assets/Editor/bread2unity/PrefabData.cs | 18 + Assets/Editor/bread2unity/PrefabData.cs.meta | 11 + Assets/Editor/bread2unity/SpriteCreator.cs | 103 +++++ .../Editor/bread2unity/SpriteCreator.cs.meta | 11 + 27 files changed, 1498 insertions(+), 133 deletions(-) create mode 100644 Assets/Editor/bread2unity/AnimationCreator.cs rename Assets/Editor/bread2unity/{Model/ISprite.cs.meta => AnimationCreator.cs.meta} (83%) create mode 100644 Assets/Editor/bread2unity/AnimationFixer.cs rename Assets/Editor/bread2unity/{Model/IAnimation.cs.meta => AnimationFixer.cs.meta} (83%) create mode 100644 Assets/Editor/bread2unity/BccadPrefab.cs rename Assets/Editor/bread2unity/{Model/IDataModel.cs.meta => BccadPrefab.cs.meta} (83%) create mode 100644 Assets/Editor/bread2unity/BccadTest.cs create mode 100644 Assets/Editor/bread2unity/BccadTest.cs.meta create mode 100644 Assets/Editor/bread2unity/ByteBuffer.cs create mode 100644 Assets/Editor/bread2unity/ByteBuffer.cs.meta create mode 100644 Assets/Editor/bread2unity/Model/Animation.cs create mode 100644 Assets/Editor/bread2unity/Model/Animation.cs.meta create mode 100644 Assets/Editor/bread2unity/Model/BCCADSprite.cs create mode 100644 Assets/Editor/bread2unity/Model/BCCADSprite.cs.meta create mode 100644 Assets/Editor/bread2unity/Model/DataModel.cs create mode 100644 Assets/Editor/bread2unity/Model/DataModel.cs.meta delete mode 100644 Assets/Editor/bread2unity/Model/IAnimation.cs delete mode 100644 Assets/Editor/bread2unity/Model/IDataModel.cs delete mode 100644 Assets/Editor/bread2unity/Model/ISprite.cs create mode 100644 Assets/Editor/bread2unity/PrefabCreator.cs create mode 100644 Assets/Editor/bread2unity/PrefabCreator.cs.meta create mode 100644 Assets/Editor/bread2unity/PrefabData.cs create mode 100644 Assets/Editor/bread2unity/PrefabData.cs.meta create mode 100644 Assets/Editor/bread2unity/SpriteCreator.cs create mode 100644 Assets/Editor/bread2unity/SpriteCreator.cs.meta diff --git a/Assets/Editor/bread2unity/AnimationCreator.cs b/Assets/Editor/bread2unity/AnimationCreator.cs new file mode 100644 index 000000000..3d8faffd7 --- /dev/null +++ b/Assets/Editor/bread2unity/AnimationCreator.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Bread2Unity; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using Animation = Bread2Unity.Animation; + +namespace Bread2Unity +{ + public static class AnimationCreator + { + private class BccadCurve : AnimationCurve + { + private float _prev; + + public new void AddKey(float time, float value) + { + if (keys.Length != 0 && !(Math.Abs(value - _prev) > 0.000001)) return; + AddKey(new Keyframe(time, value, float.PositiveInfinity, float.PositiveInfinity)); + _prev = value; + } + + public void CopyLastKey(float time) + { + Keyframe lastKey = keys.LastOrDefault(); + + base.AddKey(time, keys.Length > 0 ? lastKey.value : 1); + } + } + + public static void CreateAnimation(BccadPrefab bccadPrefab, BCCAD bccad, PrefabData prefabData, + List sprites) + { + var gameObject = bccadPrefab.ParentObject; + var rootPrefabName = gameObject.transform.parent.gameObject.name; + var spritesFolderPath = + $"Assets\\Resources\\Sprites\\Games\\{char.ToUpperInvariant(rootPrefabName[0]) + rootPrefabName.Substring(1)}"; + + var animationsFolderPath = spritesFolderPath + $"/anim/{prefabData.Name}"; + if (!Directory.Exists(animationsFolderPath)) + { + Directory.CreateDirectory(animationsFolderPath); + } + + var controller = + AnimatorController.CreateAnimatorControllerAtPath( + AssetDatabase.GenerateUniqueAssetPath( + $"{animationsFolderPath}/{prefabData.Name}Controller.controller")); + var bccadSprites = bccad.sprites; + + //get all of the parts associated with the game object + var steps = prefabData.Animations.SelectMany(animation => animation.Steps); + var bccadSpritesOfPrefab = steps.Select(step => step.BccadSprite).ToList(); + + + foreach (var animation in prefabData.Animations) + { + var clip = CreateAnimationClip(bccadPrefab, animation, sprites, bccadSpritesOfPrefab); + + AssetDatabase.CreateAsset(clip, + AssetDatabase.GenerateUniqueAssetPath( + $"{animationsFolderPath}/{animation.Name}.anim")); + controller.AddMotion(clip); + } + + var animator = gameObject.AddComponent(); + animator.runtimeAnimatorController = controller; + } + + private static AnimationClip CreateAnimationClip(BccadPrefab bccadPrefab, Animation animation, + List sprites, + IReadOnlyCollection spritesAssociatedWithPrefab) + { + var animationClip = new AnimationClip(); + var prefab = bccadPrefab.ParentObject; + for (int childIndex = 0; childIndex < prefab.transform.childCount; childIndex++) + { + var child = prefab.transform.GetChild(childIndex).gameObject; + + var partsOfGameObject = spritesAssociatedWithPrefab.SelectMany(sprite => sprite.parts) + .Where(part => bccadPrefab.RegionToChild[part.RegionIndex] == child).ToList(); + + var enabledCurve = new BccadCurve(); + + var xTransformCurve = new BccadCurve(); + var yTransformCurve = new BccadCurve(); + var zTransformCurve = new BccadCurve(); + + var rotationCurve = new BccadCurve(); + + var flipXCurve = new BccadCurve(); + var flipYCurve = new BccadCurve(); + + var scaleXCurve = new BccadCurve(); + var scaleYCurve = new BccadCurve(); + + var spriteFrames = new List(); + + var currentTime = 0f; + + for (int stepIndex = 0; stepIndex < animation.Steps.Count; stepIndex++) + { + var currentStep = animation.Steps[stepIndex]; + var bccadSprite = currentStep.BccadSprite; + // Find the index of part of the game object + var partIndex = bccadSprite.parts.Select((value, index) => new { value, index }) + .Where(pair => bccadPrefab.RegionToChild[pair.value.RegionIndex] == child) + .Select(pair => pair.index).DefaultIfEmpty(-1) + .FirstOrDefault(); + + enabledCurve.AddKey(currentTime, partIndex == -1 ? 0 : 1); + + if (partIndex != -1) + { + var bccadSpritePart = bccadSprite.parts[partIndex]; + + var sprite = sprites[bccadSpritePart.RegionIndex.Index]; + var width = bccadSpritePart.StretchX / bccadPrefab.WidthRatio; + var height = bccadSpritePart.StretchY / bccadPrefab.HeightRatio; + var x = (bccadSpritePart.PosX - 512f) / + SpriteCreator.PixelsPerUnit + sprite.bounds.size.x * 0.5f * width; + var y = -(bccadSpritePart.PosY - 512f) / SpriteCreator.PixelsPerUnit - + sprite.bounds.size.y * 0.5f * height; + var z = -0.00001f * partIndex; + + xTransformCurve.AddKey(currentTime, x); + yTransformCurve.AddKey(currentTime, y); + zTransformCurve.AddKey(currentTime, z); + + scaleXCurve.AddKey(currentTime, width); + scaleYCurve.AddKey(currentTime, height); + + if (spriteFrames.Count == 0 || spriteFrames.Last().value != sprite) + { + var spriteKeyframe = new ObjectReferenceKeyframe + { + time = currentTime, + value = sprite + }; + spriteFrames.Add(spriteKeyframe); + } + + flipXCurve.AddKey(currentTime, bccadSpritePart.FlipX ? 1 : 0); + flipYCurve.AddKey(currentTime, bccadSpritePart.FlipY ? 1 : 0); + + rotationCurve.AddKey(currentTime, -bccadSpritePart.Rotation); + } + + // Increase the time for the next frame + currentTime += currentStep.Delay / 30f; + } + + if (childIndex == 0) + { + enabledCurve.CopyLastKey(currentTime); + } + + var spriteBinding = new EditorCurveBinding + { + type = typeof(SpriteRenderer), + propertyName = "m_Sprite", + path = $"{prefab.name} {childIndex}" + }; + + var animateActive = childIndex == 0 || spritesAssociatedWithPrefab.Any(sprite => + sprite.parts.All(part => bccadPrefab.RegionToChild[part.RegionIndex] != child)); + if (animateActive) + animationClip.SetCurve(child.name, typeof(GameObject), "m_IsActive", enabledCurve); + if ((from part in partsOfGameObject select part.FlipX).Distinct().Count() > 1) + animationClip.SetCurve(child.name, typeof(SpriteRenderer), "m_FlipX", flipXCurve); + if ((from part in partsOfGameObject select part.FlipY).Distinct().Count() > 1) + animationClip.SetCurve(child.name, typeof(SpriteRenderer), "m_FlipY", flipYCurve); + if ((from part in partsOfGameObject select part.RegionIndex.Index).Distinct().Count() > 1) + AnimationUtility.SetObjectReferenceCurve(animationClip, spriteBinding, spriteFrames.ToArray()); + if ((from part in partsOfGameObject select part.PosX).Distinct().Count() > 1 || + (from part in partsOfGameObject select part.PosY).Distinct().Count() > 1) + { + animationClip.SetCurve(child.name, typeof(Transform), "localPosition.x", xTransformCurve); + animationClip.SetCurve(child.name, typeof(Transform), "localPosition.y", yTransformCurve); + animationClip.SetCurve(child.name, typeof(Transform), "localPosition.z", zTransformCurve); + } + + if ((from part in partsOfGameObject select part.Rotation).Distinct().Count() > 1) + { + animationClip.SetCurve(child.name, typeof(Transform), "localEulerAngles.z", rotationCurve); + } + + if ((from part in partsOfGameObject select part.StretchX).Distinct().Count() > 1 || + (from part in partsOfGameObject select part.StretchY).Distinct().Count() > 1) + { + animationClip.SetCurve(child.name, typeof(Transform), "localScale.x", scaleXCurve); + animationClip.SetCurve(child.name, typeof(Transform), "localScale.y", scaleYCurve); + } + } + + animationClip.frameRate = 30; //fps + animationClip.name = animation.Name; + + return animationClip; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/ISprite.cs.meta b/Assets/Editor/bread2unity/AnimationCreator.cs.meta similarity index 83% rename from Assets/Editor/bread2unity/Model/ISprite.cs.meta rename to Assets/Editor/bread2unity/AnimationCreator.cs.meta index c70703e1a..af88ca0d7 100644 --- a/Assets/Editor/bread2unity/Model/ISprite.cs.meta +++ b/Assets/Editor/bread2unity/AnimationCreator.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d4aae79bea7b7234f9ce059ade5fce08 +guid: c8ef7864bd34c1f46b262e15cfff32c9 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Editor/bread2unity/AnimationFixer.cs b/Assets/Editor/bread2unity/AnimationFixer.cs new file mode 100644 index 000000000..b254b3b0c --- /dev/null +++ b/Assets/Editor/bread2unity/AnimationFixer.cs @@ -0,0 +1,381 @@ +using System; +using UnityEngine; +using UnityEditor; +using System.IO; +using Object = UnityEngine.Object; + +namespace Bread2Unity +{ + public static class AnimationFixer + { + private static int CurveCounter = 0; + static System.DateTime StartTime; + private const float TangentDegreeDifferent = 10; + + [MenuItem("AnimationTool/ProcessForConstant %e")] + static void Execute() + { + CurveCounter = 0; + StartTime = System.DateTime.Now; + + UnityEngine.Object[] selectedObjects = Selection.GetFiltered(SelectionMode.DeepAssets); + + foreach (UnityEngine.Object selectedObj in selectedObjects) + { + string path = AssetDatabase.GetAssetPath(selectedObj); + if (selectedObj is AnimationClip) + { + Debug.Log("is AnimationClip"); + ProcessCurveForConstant(path); + CurveCounter++; + } + else + { + if (path.ToLower().EndsWith(".fbx", System.StringComparison.Ordinal)) + { + Object[] fbxObjects = AssetDatabase.LoadAllAssetsAtPath(path); + foreach (var subObj in fbxObjects) + { + if (subObj is AnimationClip + && !subObj.name.StartsWith("__preview__", System.StringComparison.Ordinal)) + { + Debug.Log("Copy animation clip : " + path + " clip name: " + + (subObj as AnimationClip).name); + string newClipPath = CopyAnimation(subObj as AnimationClip); + ProcessCurveForConstant(newClipPath); + CurveCounter++; + } + } + } + } + } + + AssetDatabase.SaveAssets(); + Debug.Log("Cruve sum: " + CurveCounter + " Time: " + + ((System.DateTime.Now - StartTime).TotalMilliseconds / 1000) + "s."); + } + + static string CopyAnimation(AnimationClip sourceClip) + { + string path = AssetDatabase.GetAssetPath(sourceClip); + path = Path.Combine(Path.GetDirectoryName(path), sourceClip.name) + ".anim"; + string newPath = AssetDatabase.GenerateUniqueAssetPath(path); + + AnimationClip newClip = new AnimationClip(); + EditorUtility.CopySerialized(sourceClip, newClip); + AssetDatabase.CreateAsset(newClip, newPath); + //AssetDatabase.Refresh(); + Debug.Log("CopyAnimation: " + newPath); + return newPath; + } + + static void ProcessCurveForConstant(string clipPath) + { + Debug.Log("Processing : " + clipPath); + ProcessCurveForConstant(AssetDatabase.LoadAssetAtPath(clipPath)); + } + + public static void ProcessCurveForConstant(AnimationClip animationClip) + { + AnimationUtility.GetCurveBindings(animationClip); + EditorUtility.SetDirty(animationClip); + + var soClip = new SerializedObject(animationClip); + float sampleRate = soClip.FindProperty("m_SampleRate").floatValue; + float oneKeyframeTime = (float)((int)((1.0f / sampleRate) * 1000)) / 1000 + 0.001f; + + string[] editorCurveSetNames = new string[] { "m_EditorCurves", "m_EulerEditorCurves" }; + //string[] editorCurveSetNames = new string[] { "m_EditorCurves" }; + + foreach (var editorCurveSetName in editorCurveSetNames) + { + var curCurveSet = soClip.FindProperty(editorCurveSetName); + int curCurveSetLenght = curCurveSet.arraySize; + if (curCurveSetLenght == 0) + { + Debug.Log("Can not fine editor curves in " + editorCurveSetName); + continue; + } + + for (int curveSetIndex = 0; curveSetIndex < curCurveSetLenght; curveSetIndex++) + { + var curCurveInfo = curCurveSet.GetArrayElementAtIndex(curveSetIndex); + Debug.Log(editorCurveSetName + " index : " + curveSetIndex + " attribute: " + + curCurveInfo.FindPropertyRelative("attribute").stringValue); + + var curCurve = curCurveInfo.FindPropertyRelative("curve"); + var curCurveData = curCurve.FindPropertyRelative("m_Curve"); + int curCurveDatalength = curCurveData.arraySize; + Debug.Log("curve lenght:" + curCurveDatalength); + + for (int curveDataIndex = 3; curveDataIndex < curCurveDatalength; curveDataIndex++) + { + ProcessOneKeyframeOfEditorCurve(curveDataIndex, curCurveData, oneKeyframeTime); + } + } + } + + string[] curveSetNames = new string[] + { "m_PositionCurves", "m_RotationCurves", "m_ScaleCurves", "m_FloatCurves" }; + + foreach (var curveSetName in curveSetNames) + { + var curCurveSet = soClip.FindProperty(curveSetName); + int curCurveSetLenght = curCurveSet.arraySize; + if (curCurveSetLenght == 0) + { + Debug.Log("Can not fine curves in " + curveSetName); + continue; + } + + bool isHaveAttribute = curveSetName == "m_FloatCurves"; + + for (int curveSetIndex = 0; curveSetIndex < curCurveSetLenght; curveSetIndex++) + { + var curCurveInfo = curCurveSet.GetArrayElementAtIndex(curveSetIndex); + if (isHaveAttribute) + { + Debug.Log(curveSetName + " index : " + curveSetIndex + " attribute: " + + curCurveInfo.FindPropertyRelative("attribute").stringValue); + } + else + { + Debug.Log(curveSetName + " index : " + curveSetIndex); + } + + var curCurve = curCurveInfo.FindPropertyRelative("curve"); + var curCurveData = curCurve.FindPropertyRelative("m_Curve"); + int curCurveDatalength = curCurveData.arraySize; + Debug.Log("curve lenght:" + curCurveDatalength); + + for (int curveDataIndex = 3; curveDataIndex < curCurveDatalength; curveDataIndex++) + { + ProcessOneKeyframeCurve(curveDataIndex, curCurveData, oneKeyframeTime); + } + } + } + + soClip.ApplyModifiedProperties(); + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + const int kBrokenMask = 1 << 0; + const int kLeftTangentMask = 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4; + const int kRightTangentMask = 1 << 5 | 1 << 6 | 1 << 7 | 1 << 8; + + static void SetKeyBroken(SerializedProperty keyframeInfo, bool broken) + { + var tangentModeProp = keyframeInfo.FindPropertyRelative("tangentMode"); + if (broken) + tangentModeProp.intValue |= kBrokenMask; + else + tangentModeProp.intValue &= ~kBrokenMask; + } + + static void SetKeyLeftTangentMode(SerializedProperty keyframeInfo, AnimationUtility.TangentMode tangentMode) + { + var tangentModeProp = keyframeInfo.FindPropertyRelative("tangentMode"); + tangentModeProp.intValue &= ~kLeftTangentMask; + tangentModeProp.intValue |= (int)tangentMode << 1; + } + + static void SetKeyRightTangentMode(SerializedProperty keyframeInfo, AnimationUtility.TangentMode tangentMode) + { + var tangentModeProp = keyframeInfo.FindPropertyRelative("tangentMode"); + tangentModeProp.intValue &= ~kRightTangentMask; + tangentModeProp.intValue |= (int)tangentMode << 5; + } + + static float CalculateLinearTangent(SerializedProperty keyframeInfo, SerializedProperty toKeyframeInfo) + { + float curTime = keyframeInfo.FindPropertyRelative("time").floatValue; + float toTime = toKeyframeInfo.FindPropertyRelative("time").floatValue; + float curValue = keyframeInfo.FindPropertyRelative("value").floatValue; + float toValue = toKeyframeInfo.FindPropertyRelative("value").floatValue; + + float dt = toTime - curTime; + if (Mathf.Abs(dt) < float.Epsilon) + return 0.0f; + + return (toValue - curValue) / dt; + } + + static void ProcessOneKeyframeOfEditorCurve(int curveDataIndex, SerializedProperty curCurveData, + float oneKeyframeTime) + { + var keyframe1 = curCurveData.GetArrayElementAtIndex(curveDataIndex - 3); + var keyframe2 = curCurveData.GetArrayElementAtIndex(curveDataIndex - 2); + var keyframe3 = curCurveData.GetArrayElementAtIndex(curveDataIndex - 1); + var keyframe4 = curCurveData.GetArrayElementAtIndex(curveDataIndex); + + float time1 = keyframe1.FindPropertyRelative("time").floatValue; + float time2 = keyframe2.FindPropertyRelative("time").floatValue; + float time3 = keyframe3.FindPropertyRelative("time").floatValue; + float time4 = keyframe4.FindPropertyRelative("time").floatValue; + + SerializedProperty outSlope1 = keyframe1.FindPropertyRelative("outSlope"); + SerializedProperty inSlope2 = keyframe2.FindPropertyRelative("inSlope"); + SerializedProperty outSlope2 = keyframe2.FindPropertyRelative("outSlope"); + SerializedProperty inSlope3 = keyframe3.FindPropertyRelative("inSlope"); + SerializedProperty outSlope3 = keyframe3.FindPropertyRelative("outSlope"); + SerializedProperty inSlope4 = keyframe4.FindPropertyRelative("inSlope"); + + float outTangetDegree1 = Mathf.Rad2Deg * Mathf.Atan(outSlope1.floatValue * oneKeyframeTime); + float inTangetDegree2 = Mathf.Rad2Deg * Mathf.Atan(inSlope2.floatValue * oneKeyframeTime); + float outTangetDegree3 = Mathf.Rad2Deg * Mathf.Atan(outSlope3.floatValue * oneKeyframeTime); + float inTangetDegree4 = Mathf.Rad2Deg * Mathf.Atan(inSlope4.floatValue * oneKeyframeTime); + + float AngleDiff1 = Mathf.Abs(outTangetDegree1 - inTangetDegree2); + float AngleDiff2 = Mathf.Abs(outTangetDegree3 - inTangetDegree4); + //check constant keyframe + if ((time2 - time1 <= oneKeyframeTime) + && (time3 - time2 <= oneKeyframeTime) + && (time4 - time3 <= oneKeyframeTime) + && AngleDiff1 > TangentDegreeDifferent + && AngleDiff2 > TangentDegreeDifferent) + { + Debug.Log(string.Format("index:{0},{1},{2},{3}", curveDataIndex - 3, curveDataIndex - 2, + curveDataIndex - 1, curveDataIndex)); + + var keyframeValue = keyframe1.FindPropertyRelative("value"); + switch (keyframeValue.propertyType) + { + case SerializedPropertyType.Float: + { + SetKeyBroken(keyframe1, true); + SetKeyRightTangentMode(keyframe1, AnimationUtility.TangentMode.Linear); + + SetKeyBroken(keyframe2, true); + SetKeyLeftTangentMode(keyframe2, AnimationUtility.TangentMode.Linear); + SetKeyRightTangentMode(keyframe2, AnimationUtility.TangentMode.Constant); + inSlope2.floatValue = CalculateLinearTangent(keyframe2, keyframe1); + outSlope2.floatValue = float.PositiveInfinity; + + SetKeyBroken(keyframe3, true); + SetKeyLeftTangentMode(keyframe3, AnimationUtility.TangentMode.Constant); + SetKeyRightTangentMode(keyframe3, AnimationUtility.TangentMode.Linear); + inSlope3.floatValue = float.PositiveInfinity; + outSlope3.floatValue = CalculateLinearTangent(keyframe3, keyframe4); + + SetKeyBroken(keyframe4, true); + SetKeyLeftTangentMode(keyframe4, AnimationUtility.TangentMode.Linear); + } + break; + } + } + } + + static Vector3 ConstantVector3 = + new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + + static Vector4 ConstantVector4 = new Vector4(float.PositiveInfinity, float.PositiveInfinity, + float.PositiveInfinity, float.PositiveInfinity); + + static Quaternion ConstantQuaternion = + new Quaternion(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 1); + + static void ProcessOneKeyframeCurve(int curveDataIndex, SerializedProperty curCurveData, float oneKeyframeTime) + { + var keyframe1 = curCurveData.GetArrayElementAtIndex(curveDataIndex - 3); + var keyframe2 = curCurveData.GetArrayElementAtIndex(curveDataIndex - 2); + var keyframe3 = curCurveData.GetArrayElementAtIndex(curveDataIndex - 1); + var keyframe4 = curCurveData.GetArrayElementAtIndex(curveDataIndex); + + float time1 = keyframe1.FindPropertyRelative("time").floatValue; + float time2 = keyframe2.FindPropertyRelative("time").floatValue; + float time3 = keyframe3.FindPropertyRelative("time").floatValue; + float time4 = keyframe4.FindPropertyRelative("time").floatValue; + + SerializedProperty outSlope1 = keyframe1.FindPropertyRelative("outSlope"); + SerializedProperty inSlope2 = keyframe2.FindPropertyRelative("inSlope"); + SerializedProperty outSlope2 = keyframe2.FindPropertyRelative("outSlope"); + SerializedProperty inSlope3 = keyframe3.FindPropertyRelative("inSlope"); + SerializedProperty outSlope3 = keyframe3.FindPropertyRelative("outSlope"); + SerializedProperty inSlope4 = keyframe4.FindPropertyRelative("inSlope"); + + //check constant keyframe + if ((time2 - time1 <= oneKeyframeTime) + && (time3 - time2 <= oneKeyframeTime) + && (time4 - time3 <= oneKeyframeTime) + ) + { + var keyframeValue = keyframe1.FindPropertyRelative("value"); + + Debug.Log(string.Format("index:{0},{1},{2},{3}", curveDataIndex - 3, curveDataIndex - 2, + curveDataIndex - 1, curveDataIndex)); + + switch (keyframeValue.propertyType) + { + case SerializedPropertyType.Float: + inSlope2.floatValue = float.PositiveInfinity; + outSlope2.floatValue = float.PositiveInfinity; + inSlope3.floatValue = float.PositiveInfinity; + outSlope3.floatValue = float.PositiveInfinity; + break; + case SerializedPropertyType.Vector3: + inSlope2.vector3Value = ConstantVector3; + outSlope2.vector3Value = ConstantVector3; + inSlope3.vector3Value = ConstantVector3; + outSlope3.vector3Value = ConstantVector3; + break; + case SerializedPropertyType.Vector4: + inSlope2.vector4Value = ConstantVector4; + outSlope2.vector4Value = ConstantVector4; + inSlope3.vector4Value = ConstantVector4; + outSlope3.vector4Value = ConstantVector4; + break; + case SerializedPropertyType.Quaternion: + inSlope2.quaternionValue = ConstantQuaternion; + outSlope2.quaternionValue = ConstantQuaternion; + inSlope3.quaternionValue = ConstantQuaternion; + outSlope3.quaternionValue = ConstantQuaternion; + break; + } + } + } + + public static void MyFix(AnimationClip clip) + { + var soClip = new SerializedObject(clip); + + string[] editorCurveSetNames = new string[] { "m_EditorCurves", "m_EulerEditorCurves", "m_RotationCurves" }; + //string[] editorCurveSetNames = new string[] { "m_EditorCurves" }; + + foreach (var editorCurveSetName in editorCurveSetNames) + { + var curCurveSet = soClip.FindProperty(editorCurveSetName); + + for (int curveSetIndex = 0; curveSetIndex < curCurveSet.arraySize; curveSetIndex++) + { + var curCurveInfo = curCurveSet.GetArrayElementAtIndex(curveSetIndex); + var curCurve = curCurveInfo.FindPropertyRelative("curve"); + var curCurveData = curCurve.FindPropertyRelative("m_Curve"); + int curCurveDataLength = curCurveData.arraySize; + Debug.Log("curve lenght:" + curCurveDataLength); + for (int index = 0; index < curCurveDataLength; index++) + { + var keyframe = curCurveData.GetArrayElementAtIndex(index); + if (editorCurveSetName.Equals("m_RotationCurves")) + { + var infiniteQuaternion = new Quaternion( + float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, + float.PositiveInfinity); + keyframe.FindPropertyRelative("inSlope").quaternionValue = infiniteQuaternion; + keyframe.FindPropertyRelative("outSlope").quaternionValue = infiniteQuaternion; + } + else if (keyframe.FindPropertyRelative("attribute") + .stringValue.Contains("m_LocalRotation")) + { + keyframe.FindPropertyRelative("inSlope").floatValue = float.PositiveInfinity; + keyframe.FindPropertyRelative("outSlope").floatValue = float.PositiveInfinity; + } + } + } + } + + soClip.ApplyModifiedProperties(); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/IAnimation.cs.meta b/Assets/Editor/bread2unity/AnimationFixer.cs.meta similarity index 83% rename from Assets/Editor/bread2unity/Model/IAnimation.cs.meta rename to Assets/Editor/bread2unity/AnimationFixer.cs.meta index c022dfd3f..8d35a5ae8 100644 --- a/Assets/Editor/bread2unity/Model/IAnimation.cs.meta +++ b/Assets/Editor/bread2unity/AnimationFixer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c33bcfd692627dd4a97ac1cd1d930420 +guid: c5f9faab3428c1a479eecff16161bafa MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Editor/bread2unity/BCCAD.cs b/Assets/Editor/bread2unity/BCCAD.cs index ff9efdc46..dabc9710c 100644 --- a/Assets/Editor/bread2unity/BCCAD.cs +++ b/Assets/Editor/bread2unity/BCCAD.cs @@ -1,65 +1,131 @@ using System; -using System.Collections; -using System.Collections.Generic; - +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using Bread2Unity; using UnityEngine; +using Animation = Bread2Unity.Animation; + namespace Bread2Unity { - public class BCCAD : IDataModel + public class BCCAD : DataModel { - - public BCCAD Read(byte[] bytes) + public static BCCAD Read(byte[] bytes) { - sheetW = BitConverter.ToUInt16(bytes, 4); - sheetH = BitConverter.ToUInt16(bytes, 6); - - - // int max = (bytes[8] * 2) + 12; - int max = 64 * bytes[8] + 12; - - // note this doesn't account for empty sprites, but I'll get there when i get there - for (int i = 12; i < max; i += 2) // 16 bit bytes, skip every 2nd byte + BCCAD bccad = new BCCAD(); + var byteBuffer = new ByteBuffer(bytes); + byteBuffer.ReadInt(); //timestamp + bccad.sheetW = byteBuffer.ReadUShort(); + bccad.sheetH = byteBuffer.ReadUShort(); + // Sprites + var spritesNum = byteBuffer.ReadInt(); + for (int i = 0; i < spritesNum; i++) { - ISprite spriteParts_ = new ISprite(); - int compare = 0; - for (int j = 0; j < bytes[i]; j++) + BccadSprite bccadSprite = new BccadSprite(); + var partsNum = byteBuffer.ReadInt(); + for (int j = 0; j < partsNum; j++) { - int ind = i + 4 + (64 * j); + SpritePart part = new SpritePart(); + var region = new Region + { + regionX = byteBuffer.ReadUShort(), + regionY = byteBuffer.ReadUShort(), + regionW = byteBuffer.ReadUShort(), + regionH = byteBuffer.ReadUShort() + }; + var result = bccad.regions.FindIndex(x => Equals(x, region)); + if (result == -1) + { + bccad.regions.Add(region); + part.RegionIndex = new RegionIndex(bccad.regions.Count - 1, 0); + } + else + { + var repeatedNumber = bccadSprite.parts.Count(p => p.RegionIndex.Index == result); + part.RegionIndex = new RegionIndex(result, repeatedNumber); + + } + part.PosX = byteBuffer.ReadShort(); + part.PosY = byteBuffer.ReadShort(); + part.StretchX = byteBuffer.ReadFloat(); + part.StretchY = byteBuffer.ReadFloat(); + part.Rotation = byteBuffer.ReadFloat(); + part.FlipX = byteBuffer.ReadByte() != 0; + part.FlipY = byteBuffer.ReadByte() != 0; + //Multicolor and screen color + part.Multicolor = new Color(Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f, + Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f, + Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f); + part.ScreenColor = new Color(Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f, + Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f, + Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f); - ISpritePart part = new ISpritePart(); - part.regionX = BitConverter.ToUInt16(bytes, ind + 0); - part.regionY = BitConverter.ToUInt16(bytes, ind + 2); - part.regionW = BitConverter.ToUInt16(bytes, ind + 4); - part.regionH = BitConverter.ToUInt16(bytes, ind + 6); - part.posX = BitConverter.ToInt16(bytes, ind + 8); - part.posY = BitConverter.ToInt16(bytes, ind + 10); - part.stretchX = BitConverter.ToSingle(bytes, ind + 12); - part.stretchY = BitConverter.ToSingle(bytes, ind + 14); - part.rotation = BitConverter.ToSingle(bytes, ind + 16); - part.flipX = bytes[ind + 18] != (byte)0; - part.flipY = bytes[ind + 20] != (byte)0; - // im sure the values between 20 and 28 are important so remind me to come back to these - part.opacity = bytes[ind + 28]; + part.Opacity = byteBuffer.ReadByte(); + for (int k = 0; k < 12; k++) + { + byteBuffer.ReadByte(); + } - Debug.Log("offset: " + ind + ", val: " + part.regionX); - - spriteParts_.parts.Add(part); - - compare += 64; + part.designation = byteBuffer.ReadByte(); + part.unknown = byteBuffer.ReadShort(); + part.tlDepth = byteBuffer.ReadFloat(); + part.blDepth = byteBuffer.ReadFloat(); + part.trDepth = byteBuffer.ReadFloat(); + part.brDepth = byteBuffer.ReadFloat(); + bccadSprite.parts.Add(part); } - sprites.Add(spriteParts_); + bccad.sprites.Add(bccadSprite); + } + + // Animations + var animationsNum = byteBuffer.ReadInt(); + for (int i = 0; i < animationsNum; i++) + { + var anim = new Animation(); + var nameBuilder = new StringBuilder(); + var length = Convert.ToInt32(byteBuffer.ReadByte()); + for (int j = 0; j < length; j++) + { + nameBuilder.Append(Convert.ToChar(byteBuffer.ReadByte())); + } - i += compare; + for (int j = 0; j < 4 - ((length + 1) % 4); j++) + { + byteBuffer.ReadByte(); + } + + anim.Name = nameBuilder.ToString(); + anim.InterpolationInt = byteBuffer.ReadInt(); + var stepsNum = byteBuffer.ReadInt(); + for (int j = 0; j < stepsNum; j++) + { + var spriteIndex = byteBuffer.ReadUShort(); + var step = new AnimationStep + { + Delay = byteBuffer.ReadUShort(), + TranslateX = byteBuffer.ReadShort(), + TranslateY = byteBuffer.ReadShort(), + Depth = byteBuffer.ReadFloat(), + StretchX = byteBuffer.ReadFloat(), + StretchY = byteBuffer.ReadFloat(), + Rotation = byteBuffer.ReadFloat(), + Color = new Color(Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f, + Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f, + Convert.ToInt32(byteBuffer.ReadByte() & 0xff) / 255f), + BccadSprite = bccad.sprites[spriteIndex] + }; + byteBuffer.ReadByte(); + byteBuffer.ReadByte(); + byteBuffer.ReadByte(); + step.Opacity = Convert.ToByte(Convert.ToInt32(byteBuffer.ReadShort()) & 0xFF); + anim.Steps.Add(step); + } + bccad.animations.Add(anim); } - return new BCCAD() - { - }; + return bccad; } - - - /// sprites length bytes start = 12 } } \ No newline at end of file diff --git a/Assets/Editor/bread2unity/BccadPrefab.cs b/Assets/Editor/bread2unity/BccadPrefab.cs new file mode 100644 index 000000000..e9d38a227 --- /dev/null +++ b/Assets/Editor/bread2unity/BccadPrefab.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Bread2Unity; +using UnityEngine; + +namespace Bread2Unity +{ + public class BccadPrefab + { + public GameObject ParentObject { get; } + public readonly Dictionary RegionToChild = new Dictionary(); + private readonly List _children = new List(); + private readonly PrefabData _data; + private readonly BCCAD _bccad; + public float HeightRatio { get; } + public float WidthRatio { get; } + + public BccadPrefab(PrefabData data, BCCAD bccad, Texture2D texture) + { + ParentObject = new GameObject(data.Name); + _data = data; + HeightRatio = (float)texture.height / bccad.sheetH; + WidthRatio = (float)texture.width / bccad.sheetW; + _bccad = bccad; + CalculateParts(); + } + + private void CalculateParts() + { + var defaultSprite = _bccad.sprites[_data.SpriteIndex]; + if (_data.Animations.Count == 0) + { + for (var index = 0; index < defaultSprite.parts.Count; index++) + { + var part = defaultSprite.parts[index]; + var child = new GameObject($"{_data.Name} {index}"); + child.transform.SetParent(ParentObject.transform); + _children.Add(child); + RegionToChild.Add(part.RegionIndex, child); + } + return; + } + // Get all regions + var anim = _data.Animations; + var bccadSprites = anim.SelectMany(a => a.Steps).Select(step => step.BccadSprite).Distinct().ToList(); + var availableRegions = bccadSprites.SelectMany(sprite => sprite.parts).Select(part => part.RegionIndex) + .Distinct().ToList(); + + var childIndex = 0; + while (availableRegions.Count > 0) + { + var child = new GameObject($"{_data.Name} {childIndex}"); + child.transform.SetParent(ParentObject.transform); + _children.Add(child); + var nextRegion = availableRegions[0]; + availableRegions.RemoveAt(0); + var regionsList = new List { nextRegion }; + while (true) + { + var notAdjacentRegions = FindNotAdjacentRegions(regionsList, bccadSprites, availableRegions); + var maybeNotAdjacentRegion = availableRegions.Select(reg => (RegionIndex?)reg) + .FirstOrDefault(region => notAdjacentRegions.Contains((RegionIndex)region)); + if (maybeNotAdjacentRegion is { } notAdjacentRegion) + { + availableRegions.Remove(notAdjacentRegion); + regionsList.Add(notAdjacentRegion); + } + else + { + break; + } + } + + foreach (var r in regionsList) + { + RegionToChild.Add(r, child); + } + + childIndex++; + } + } + + private static List FindNotAdjacentRegions(List regions, List sprites, List availableRegions) + { + var notAdjacentRegions = new List(availableRegions); + foreach (var sprite in sprites) + { + var regionsOfSprite = sprite.parts.Select(part => part.RegionIndex).ToArray(); + if (regionsOfSprite.Intersect(regions).Any()) + { + foreach (var r in regionsOfSprite) + { + notAdjacentRegions.Remove(r); + } + } + + } + + return notAdjacentRegions; + } + + public List> GetHiddenParts() + { + var sprite = _bccad.sprites[_data.SpriteIndex]; + var pairs = new List>(); + var gameObjects = new List(_children); + foreach (var part in sprite.parts) + { + var child = RegionToChild[part.RegionIndex]; + gameObjects.Remove(child); + } + + foreach (var gameObject in gameObjects) + { + //find a random part associated with the game object + var region = RegionToChild.FirstOrDefault(keyValuePair => keyValuePair.Value == gameObject) + .Key; + var parts = _data.Animations.SelectMany(anim => anim.Steps).Select(s => s.BccadSprite) + .SelectMany(bccadSprite => bccadSprite.parts); + + var partIndexPair = parts + .Select((value, index) => new { value, index }) + .First(pair => pair.value.RegionIndex.Equals(region)); + var index = partIndexPair.index; + var part = partIndexPair.value; + pairs.Add(new Tuple(index, part, gameObject)); + + } + + return pairs; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/IDataModel.cs.meta b/Assets/Editor/bread2unity/BccadPrefab.cs.meta similarity index 83% rename from Assets/Editor/bread2unity/Model/IDataModel.cs.meta rename to Assets/Editor/bread2unity/BccadPrefab.cs.meta index ff789390c..d0c29f6a5 100644 --- a/Assets/Editor/bread2unity/Model/IDataModel.cs.meta +++ b/Assets/Editor/bread2unity/BccadPrefab.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ddf1fd563dabc6040a863537a081843a +guid: 7be9a40b9a8419b43aa454fa09e9ce78 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Editor/bread2unity/BccadTest.cs b/Assets/Editor/bread2unity/BccadTest.cs new file mode 100644 index 000000000..a43a37f52 --- /dev/null +++ b/Assets/Editor/bread2unity/BccadTest.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace Bread2Unity +{ + public static class BccadTest + { + public static void TestBccad() + { + const string folderPath = + "C:\\Users\\Eliya\\games\\3DS\\PackEnglishV12\\PackEnglishV12\\PackHack\\ExtractedRomFS\\cellanim"; + var allFiles = Directory.GetFiles(folderPath, "*.bccad", SearchOption.AllDirectories); + var problematicFiles = new HashSet(); + foreach (var file in allFiles) + { + var bccad = BCCAD.Read(File.ReadAllBytes(file)); + var name = Path.GetFileName(file); + for (var spriteIndex = 0; spriteIndex < bccad.sprites.Count; spriteIndex++) + { + var sprite = bccad.sprites[spriteIndex]; + /*for (var partIndex = 0; partIndex < sprite.parts.Count; partIndex++) + { + var part = sprite.parts[partIndex]; + if (part.Multicolor != Color.white) + Debug.Log($"multycolor not white at {name} sprite: {spriteIndex} part: {partIndex}"); + + if (part.ScreenColor != Color.black) + Debug.Log($"screen color not black at {name} sprite: {spriteIndex} part: {partIndex}"); + }*/ + var v = sprite.parts.GroupBy(p => p.FlipX) + .Any(a => a.Select(part => part.RegionIndex.Index).Distinct().Count() < a.Count()); + // if (sprite.parts.Select(part => part.Region).Distinct().Count() < sprite.parts.Count) + if(v) + // Debug.Log($"duplicate regions {name} sprite: {spriteIndex}"); + problematicFiles.Add(name); + } + + /*for (var animIndex = 0; animIndex < bccad.animations.Count; animIndex++) + { + var anim = bccad.animations[animIndex]; + if (anim.Interpolated) Debug.Log($"interpolated {name} anim: {animIndex}"); + for (var stepIndex = 0; stepIndex < anim.Steps.Count; stepIndex++) + { + var step = anim.Steps[stepIndex]; + if(step.Color != Color.white) + Debug.Log($"step color not white at {name} anim: {animIndex} step: {stepIndex}"); + if (step.Opacity != 255) + Debug.Log($"step opacity not 255 at {name} anim: {animIndex} step: {stepIndex}"); + } + }*/ + } + + foreach (var filename in problematicFiles) + { + Debug.Log(filename); + } + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/BccadTest.cs.meta b/Assets/Editor/bread2unity/BccadTest.cs.meta new file mode 100644 index 000000000..06a766ca3 --- /dev/null +++ b/Assets/Editor/bread2unity/BccadTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af00b07ba074d9c44ab931b3b98e5605 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/Bread2Unity.cs b/Assets/Editor/bread2unity/Bread2Unity.cs index 6cba9f92c..2ec7e1024 100644 --- a/Assets/Editor/bread2unity/Bread2Unity.cs +++ b/Assets/Editor/bread2unity/Bread2Unity.cs @@ -1,47 +1,131 @@ -using System.Collections; +using System; using System.Collections.Generic; using System.IO; - +using System.Linq; using UnityEngine; using UnityEditor; -using Starpelly; +using Bread2Unity; namespace Bread2Unity { - public class Bread2Unity : EditorWindow + public class Bread2UnityGUI : EditorWindow { - public const string editorFolderName = "bread2unity"; + public const string EditorFolderName = "bread2unity"; + private GameObject _prefab; + private DataModel animation; + private List _prefabDataList = new List(); + private List _animationsIndexes; + private Vector2 _scrollPosition; + [MenuItem("Tools/bread2unity")] public static void ShowWindow() { - EditorWindow.GetWindow("bread2unity"); + GetWindow("bread2unity"); + } + + public void CreateGUI() + { + _animationsIndexes = new List(); } public void OnGUI() { - Texture logo = (Texture)AssetDatabase.LoadAssetAtPath($"Assets/Editor/{editorFolderName}/logo.png", typeof(Texture)); - GUILayout.Box(logo, new GUILayoutOption[] { GUILayout.ExpandWidth(true), GUILayout.Height(60) }); + Texture logo = + (Texture)AssetDatabase.LoadAssetAtPath($"Assets/Editor/{EditorFolderName}/logo.png", typeof(Texture)); + GUILayout.Box(logo, GUILayout.ExpandWidth(true), GUILayout.Height(60)); GUILayout.Space(30); GUIStyle desc = EditorStyles.label; desc.wordWrap = true; desc.fontStyle = FontStyle.BoldAndItalic; - GUILayout.Box("bread2unity is a tool built with the purpose of converting RH Megamix and Fever animations to unity. And to generally speed up development by a lot." + - "\nCreated by Starpelly.", desc); + GUILayout.Box( + "bread2unity is a tool built with the purpose of converting RH Megamix animations to unity. And to generally speed up development by a lot.", + desc); - GUILayout.Space(120); + GUILayout.Space(60); + EditorGUIUtility.labelWidth = 100; - if (GUILayout.Button("Test")) + GUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Prefab"); + _prefab = (GameObject)EditorGUILayout.ObjectField(_prefab, typeof(GameObject), false); + GUILayout.EndHorizontal(); + + EditorGUILayout.Separator(); + + _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.MaxHeight(60)); + var plusGuiStyle = new GUIStyle(GUI.skin.button) { - string path = EditorUtility.OpenFilePanel("Open BCCAD File", null, "bccad"); - if (path.Length != 0) + fontSize = 35, + alignment = TextAnchor.MiddleCenter + }; + var minusGuiStyle = new GUIStyle(GUI.skin.button) + { + fontSize = 35, + alignment = TextAnchor.MiddleCenter + }; + for (var i = 0; i < _prefabDataList.Count; i++) + { + var prefabData = _prefabDataList[i]; + GUILayout.BeginHorizontal(); + prefabData.Name = EditorGUILayout.TextField("Name", prefabData.Name); + _animationsIndexes[i] = EditorGUILayout.TextField("Animations", _animationsIndexes[i]); + prefabData.SpriteIndex = + EditorGUILayout.IntField("Sprite Index", prefabData.SpriteIndex); + if (GUILayout.Button("-", minusGuiStyle, GUILayout.Height(15), GUILayout.Width(15))) { - var fileContent = File.ReadAllBytes(path); - new BCCAD().Read(fileContent); + _prefabDataList.RemoveAt(i); + _animationsIndexes.RemoveAt(i); + Repaint(); } + + GUILayout.EndHorizontal(); + } + + EditorGUILayout.EndScrollView(); + + EditorGUILayout.Separator(); + + if (GUILayout.Button("+", plusGuiStyle, GUILayout.Height(40), GUILayout.Width(40))) + { + _prefabDataList.Add(new PrefabData("", 0)); + _animationsIndexes.Add(""); + } + + EditorGUILayout.Separator(); + + if (GUILayout.Button("Create Prefabs (WIP)") && _prefab != null) + { + //Choose png and bccad files + var bccadFilePath = EditorUtility.OpenFilePanel("Open BCCAD File", null, "bccad"); + var bccadFileFolder = Path.GetDirectoryName(bccadFilePath); + var pngFilePath = EditorUtility.OpenFilePanel("Open Texture File", bccadFileFolder, "png"); + if (bccadFilePath != null && pngFilePath != null) + { + var bccad = BCCAD.Read(File.ReadAllBytes(bccadFilePath)); + var spriteTexture = SpriteCreator.ComputeSprites(bccad, pngFilePath, _prefab.name); + //Create prefab from prefab data + for (int i = 0; i < _prefabDataList.Count; i++) + { + List animationIndexes; + var prefabData = _prefabDataList[i]; + if (_animationsIndexes[i].Equals("")) + animationIndexes = new List(); + else + animationIndexes = _animationsIndexes[i].Split(',').Select(int.Parse).ToList(); + prefabData.Animations = + animationIndexes.Select(index => bccad.animations[index]).ToList(); + } + + PrefabCreator.CreatePrefab(_prefab, bccad, _prefabDataList, spriteTexture); + } + } + + if (GUILayout.Button("bccad test")) + { + BccadTest.TestBccad(); } GUILayout.BeginHorizontal(); @@ -49,6 +133,7 @@ namespace Bread2Unity { Application.OpenURL("https://github.com/rhmodding/bread"); } + GUILayout.EndHorizontal(); } } diff --git a/Assets/Editor/bread2unity/ByteBuffer.cs b/Assets/Editor/bread2unity/ByteBuffer.cs new file mode 100644 index 000000000..ca54127b1 --- /dev/null +++ b/Assets/Editor/bread2unity/ByteBuffer.cs @@ -0,0 +1,52 @@ +using System; + +namespace Bread2Unity +{ + public class ByteBuffer + { + private readonly byte[] _bytes; + + public int ReadPoint{ get; private set; } + + public ByteBuffer(byte[] bytes) + { + _bytes = bytes; + ReadPoint = 0; + } + + public ushort ReadUShort() + { + var result = BitConverter.ToUInt16(_bytes, ReadPoint); + ReadPoint += sizeof(ushort); + return result; + } + + public short ReadShort() + { + var result = BitConverter.ToInt16(_bytes, ReadPoint); + ReadPoint += sizeof(short); + return result; + } + + public float ReadFloat() + { + float result = BitConverter.ToSingle(_bytes, ReadPoint); + ReadPoint += sizeof(float); + return result; + } + + public byte ReadByte() + { + byte result = _bytes[ReadPoint]; + ReadPoint += sizeof(byte); + return result; + } + + public int ReadInt() + { + int result = BitConverter.ToInt32(_bytes, ReadPoint); + ReadPoint += sizeof(int); + return result; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/ByteBuffer.cs.meta b/Assets/Editor/bread2unity/ByteBuffer.cs.meta new file mode 100644 index 000000000..e6bef32e7 --- /dev/null +++ b/Assets/Editor/bread2unity/ByteBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1883cd0d74b590740936a6ffc3580eb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/Model/Animation.cs b/Assets/Editor/bread2unity/Model/Animation.cs new file mode 100644 index 000000000..3e8489ec2 --- /dev/null +++ b/Assets/Editor/bread2unity/Model/Animation.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Bread2Unity +{ + public class Animation + { + public List Steps = new List(); + public string Name; + public int InterpolationInt = 0; + + public bool Interpolated => (InterpolationInt & 0b1) > 0; + } + + public class AnimationStep + { + public ushort Delay; + + public BccadSprite BccadSprite; + + public short TranslateX; + public short TranslateY; + + public float Depth; + + public float StretchX; + public float StretchY; + + public float Rotation; + + public byte Opacity; + public Color Color; + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/Animation.cs.meta b/Assets/Editor/bread2unity/Model/Animation.cs.meta new file mode 100644 index 000000000..8842f4b96 --- /dev/null +++ b/Assets/Editor/bread2unity/Model/Animation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 994f68ab1a3b68b49a0ae01b0bf37f0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/Model/BCCADSprite.cs b/Assets/Editor/bread2unity/Model/BCCADSprite.cs new file mode 100644 index 000000000..91316a789 --- /dev/null +++ b/Assets/Editor/bread2unity/Model/BCCADSprite.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Bread2Unity +{ + public class BccadSprite + { + public List parts = new List(); + } + + public class SpritePart + { + public RegionIndex RegionIndex; + + public short PosX; + public short PosY; + + public float StretchX; + public float StretchY; + + public float Rotation; + + public bool FlipX; + public bool FlipY; + + public byte Opacity; + + public byte designation; + public short unknown; + public float tlDepth; + public float blDepth; + public float trDepth; + public float brDepth; + public Color Multicolor; + public Color ScreenColor; + public Color GetColor() => new Color(Multicolor.r, Multicolor.g, Multicolor.b, Opacity / 255f); + } + + public struct RegionIndex + { + public int Index { get; } + public int RepeatedNumber { get; } + + public RegionIndex(int index, int repeatedNumber) + { + RepeatedNumber = repeatedNumber; + Index = index; + } + + public bool Equals(RegionIndex other) + { + return Index == other.Index && RepeatedNumber == other.RepeatedNumber; + } + + public override bool Equals(object obj) + { + return obj is RegionIndex other && Equals(other); + } + + public override int GetHashCode() + { + int hash = 23; + hash = hash * 31 + Index; + hash = hash * 31 + RepeatedNumber; + return hash; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/BCCADSprite.cs.meta b/Assets/Editor/bread2unity/Model/BCCADSprite.cs.meta new file mode 100644 index 000000000..38bd4544a --- /dev/null +++ b/Assets/Editor/bread2unity/Model/BCCADSprite.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea35991e78a2918439cc249d1e4e293e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/Model/DataModel.cs b/Assets/Editor/bread2unity/Model/DataModel.cs new file mode 100644 index 000000000..22cbd82ea --- /dev/null +++ b/Assets/Editor/bread2unity/Model/DataModel.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Bread2Unity +{ + public class DataModel + { + public List regions = new List(); + public List sprites = new List(); + public List animations = new List(); + public int sheetW; + public int sheetH; + } + + public class Region + { + public ushort regionX; + public ushort regionY; + public ushort regionW; + public ushort regionH; + + public override string ToString() + { + return $"regionX: {regionX} regionY: {regionY} regionW: {regionW} regionH: {regionH}"; + } + + protected bool Equals(Region other) + { + return regionX == other.regionX && regionY == other.regionY && regionW == other.regionW && + regionH == other.regionH; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Region)obj); + } + + public override int GetHashCode() + { + int hash = 23; + hash = hash * 31 + regionX; + hash = hash * 31 + regionY; + hash = hash * 31 + regionW; + hash = hash * 31 + regionH; + return hash; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/DataModel.cs.meta b/Assets/Editor/bread2unity/Model/DataModel.cs.meta new file mode 100644 index 000000000..0a55dbc1b --- /dev/null +++ b/Assets/Editor/bread2unity/Model/DataModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c90d75428898ac74e819c39b7249f133 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/Model/IAnimation.cs b/Assets/Editor/bread2unity/Model/IAnimation.cs deleted file mode 100644 index cdde15c07..000000000 --- a/Assets/Editor/bread2unity/Model/IAnimation.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -namespace Bread2Unity -{ - public class IAnimation - { - public List steps; - } - - public class IAnimationStep - { - public ushort spriteIndex; - public ushort delay; - - public float stretchX; - public float stretchY; - - public float rotation; - - public byte opacity; - } -} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/IDataModel.cs b/Assets/Editor/bread2unity/Model/IDataModel.cs deleted file mode 100644 index 0c9634edf..000000000 --- a/Assets/Editor/bread2unity/Model/IDataModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Bread2Unity -{ - public class IDataModel - { - public List sprites = new List(); - public List animations = new List(); - public int sheetW; - public int sheetH; - } -} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/Model/ISprite.cs b/Assets/Editor/bread2unity/Model/ISprite.cs deleted file mode 100644 index 9b0ca7e20..000000000 --- a/Assets/Editor/bread2unity/Model/ISprite.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Bread2Unity -{ - public class ISprite - { - public List parts = new List(); - } - - public class ISpritePart - { - public ushort regionX; - public ushort regionY; - public ushort regionW; - public ushort regionH; - - public short posX; - public short posY; - - public float stretchX; - public float stretchY; - - public float rotation; - - public bool flipX; - public bool flipY; - - public byte opacity; - } -} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/PrefabCreator.cs b/Assets/Editor/bread2unity/PrefabCreator.cs new file mode 100644 index 000000000..f8a1a6561 --- /dev/null +++ b/Assets/Editor/bread2unity/PrefabCreator.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Bread2Unity; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; +using Animation = Bread2Unity.Animation; + +namespace Bread2Unity +{ + public static class PrefabCreator + { + public static void CreatePrefab(GameObject prefab, BCCAD bccad, + List prefabDataList, Texture2D texture) + { + var prefabName = prefab.name; + var spritesFolderPath = + $"Sprites\\Games\\{char.ToUpperInvariant(prefabName[0]) + prefabName.Substring(1)}"; + var prefabAssetPath = AssetDatabase.GetAssetPath(prefab); + var root = PrefabUtility.LoadPrefabContents(prefabAssetPath); + foreach (var prefabData in prefabDataList) + { + var defaultSprite = bccad.sprites[prefabData.SpriteIndex]; + var bccadPrefab = new BccadPrefab(prefabData, bccad, texture); + var newPrefab = bccadPrefab.ParentObject; + newPrefab.transform.SetParent(root.transform); + var sprites = Resources.LoadAll(spritesFolderPath).ToList() + .FindAll(s => s.name.Contains(texture.name)); + for (var index = 0; index < defaultSprite.parts.Count; index++) + { + var spritePart = defaultSprite.parts[index]; + var gameObjectPart = bccadPrefab.RegionToChild[spritePart.RegionIndex]; + ApplySpriteSettingsFromBccad(gameObjectPart, spritePart, bccadPrefab, + sprites[spritePart.RegionIndex.Index], + index); + } + + foreach (var (partIndex, spritePart, hiddenGameObject) in bccadPrefab.GetHiddenParts()) + { + hiddenGameObject.SetActive(false); + ApplySpriteSettingsFromBccad(hiddenGameObject, spritePart, bccadPrefab, + sprites[spritePart.RegionIndex.Index], partIndex); + } + + if (prefabData.Animations.Count > 0) + AnimationCreator.CreateAnimation(bccadPrefab, bccad, prefabData, sprites); + + PrefabUtility.SaveAsPrefabAsset(root, AssetDatabase.GetAssetPath(prefab)); + } + } + + private static void ApplySpriteSettingsFromBccad(GameObject gameObjectPart, SpritePart spritePart, + BccadPrefab prefab, Sprite sprite, int index) + { + var spriteRenderer = (SpriteRenderer)gameObjectPart.AddComponent(typeof(SpriteRenderer)); + + spriteRenderer.sprite = sprite; + spriteRenderer.color = spritePart.GetColor(); + spriteRenderer.flipX = spritePart.FlipX; + spriteRenderer.flipY = spritePart.FlipY; + spriteRenderer.enabled = true; + gameObjectPart.transform.SetParent(prefab.ParentObject.transform); + + // Bread draws sprites from the edge, and unity from the middle. + var width = spritePart.StretchX / prefab.WidthRatio; + var height = spritePart.StretchY / prefab.HeightRatio; + // var pixelsPerUnit = 73; + + var position = + new Vector3( + (spritePart.PosX - 512f) / + SpriteCreator.PixelsPerUnit + sprite.bounds.size.x * 0.5f * width, + -(spritePart.PosY - 512f) / SpriteCreator.PixelsPerUnit - + sprite.bounds.size.y * 0.5f * height, + -0.00001f * index); + var rotation = Quaternion.AngleAxis(spritePart.Rotation, new Vector3(0, 0, -1)); + gameObjectPart.transform.localPosition = position; + gameObjectPart.transform.localRotation = rotation; + gameObjectPart.transform.localScale = new Vector3(width, height, 1f); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/PrefabCreator.cs.meta b/Assets/Editor/bread2unity/PrefabCreator.cs.meta new file mode 100644 index 000000000..51ce34868 --- /dev/null +++ b/Assets/Editor/bread2unity/PrefabCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 59809bd6fc62dce4aab4eb333ffbdc64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/PrefabData.cs b/Assets/Editor/bread2unity/PrefabData.cs new file mode 100644 index 000000000..7c7547c8d --- /dev/null +++ b/Assets/Editor/bread2unity/PrefabData.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Bread2Unity; + +namespace Bread2Unity +{ + public class PrefabData + { + public string Name; + public List Animations; + public int SpriteIndex; + + public PrefabData(string name, int spriteIndex) + { + Name = name; + SpriteIndex = spriteIndex; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/PrefabData.cs.meta b/Assets/Editor/bread2unity/PrefabData.cs.meta new file mode 100644 index 000000000..5dc6865d6 --- /dev/null +++ b/Assets/Editor/bread2unity/PrefabData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6807e41eb8dc0ae46b2e5b728af55e2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/bread2unity/SpriteCreator.cs b/Assets/Editor/bread2unity/SpriteCreator.cs new file mode 100644 index 000000000..31fdd872a --- /dev/null +++ b/Assets/Editor/bread2unity/SpriteCreator.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; + +using Bread2Unity; + +namespace Bread2Unity +{ + public class SpriteCreator : MonoBehaviour + { + public const int PixelsPerUnit = 100; + public static Texture2D ComputeSprites(BCCAD bccad, string texturePath, string prefabName) + { + var textureName = Path.GetFileName(texturePath); + var spritesFolder = + $"Assets\\Resources\\Sprites\\Games\\{char.ToUpperInvariant(prefabName[0]) + prefabName.Substring(1)}\\"; + if (!Directory.Exists(spritesFolder)) + { + Directory.CreateDirectory(spritesFolder); + } + + var destTexturePath = + spritesFolder + + $"{textureName}"; + var newTexture = new Texture2D(bccad.sheetW, bccad.sheetH); + newTexture.LoadImage(File.ReadAllBytes(texturePath)); + var rotatedTexture = RotateTexture(newTexture); + rotatedTexture.name = textureName.Substring(0, textureName.Length - ".png".Length); + File.WriteAllBytes(destTexturePath, rotatedTexture.EncodeToPNG()); + AssetDatabase.ImportAsset(destTexturePath); + var ti = AssetImporter.GetAtPath(destTexturePath) as TextureImporter; + + if (ti != null) + { + ti.isReadable = true; + // constants + ti.textureType = TextureImporterType.Sprite; + ti.spriteImportMode = SpriteImportMode.Multiple; + ti.spritePixelsPerUnit = PixelsPerUnit; + ti.filterMode = FilterMode.Point; + ti.textureCompression = TextureImporterCompression.Uncompressed; + var newData = new List(); + var rectCtr = 0; + var heightRatio = (float)rotatedTexture.height / bccad.sheetH; + var widthRatio = (float)rotatedTexture.width / bccad.sheetW; + foreach (var r in bccad.regions) + { + var smd = new SpriteMetaData + { + pivot = new Vector2(0.5f, 0.5f), + alignment = 0, + name = rotatedTexture.name + "_" + rectCtr, + rect = new Rect(r.regionX * widthRatio, + rotatedTexture.height - (r.regionH + r.regionY) * heightRatio, r.regionW * widthRatio, + r.regionH * heightRatio) + }; + + newData.Add(smd); + rectCtr++; + } + + ti.spritesheet = newData.ToArray(); + } + + AssetDatabase.ImportAsset(destTexturePath, ImportAssetOptions.ForceUpdate); + return rotatedTexture; + } + + public static Texture2D RotateTexture(Texture2D image) + { + Texture2D + target = new Texture2D(image.height, image.width, image.format, + false); //flip image width<>height, as we rotated the image, it might be a rect. not a square image + + Color32[] pixels = image.GetPixels32(0); + pixels = RotateTextureGrid(pixels, image.width, image.height); + target.SetPixels32(pixels); + target.Apply(); + + //flip image width<>height, as we rotated the image, it might be a rect. not a square image + + return target; + } + + + private static Color32[] RotateTextureGrid(Color32[] tex, int wid, int hi) + { + Color32[] ret = new Color32[wid * hi]; //reminder we are flipping these in the target + + for (int y = 0; y < hi; y++) + { + for (int x = 0; x < wid; x++) + { + ret[(hi - 1) - y + x * hi] = tex[x + y * wid]; //juggle the pixels around + } + } + + return ret; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/bread2unity/SpriteCreator.cs.meta b/Assets/Editor/bread2unity/SpriteCreator.cs.meta new file mode 100644 index 000000000..d2f0d00f4 --- /dev/null +++ b/Assets/Editor/bread2unity/SpriteCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a4430af4dfb91f4f9720962a6eef059 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: