Initial bread2unity commit

This commit is contained in:
EliyaFishman 2022-11-28 20:09:15 +02:00
parent b68e9cd967
commit 5021c83afa
27 changed files with 1498 additions and 133 deletions

View file

@ -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<Sprite> 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>();
animator.runtimeAnimatorController = controller;
}
private static AnimationClip CreateAnimationClip(BccadPrefab bccadPrefab, Animation animation,
List<Sprite> sprites,
IReadOnlyCollection<BccadSprite> 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<ObjectReferenceKeyframe>();
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;
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: d4aae79bea7b7234f9ce059ade5fce08 guid: c8ef7864bd34c1f46b262e15cfff32c9
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View file

@ -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<UnityEngine.Object>(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<AnimationClip>(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();
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c33bcfd692627dd4a97ac1cd1d930420 guid: c5f9faab3428c1a479eecff16161bafa
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View file

@ -1,65 +1,131 @@
using System; using System;
using System.Collections; using System.Linq;
using System.Collections.Generic; using System.Runtime.CompilerServices;
using System.Text;
using Bread2Unity;
using UnityEngine; using UnityEngine;
using Animation = Bread2Unity.Animation;
namespace Bread2Unity namespace Bread2Unity
{ {
public class BCCAD : IDataModel public class BCCAD : DataModel
{ {
public static BCCAD Read(byte[] bytes)
public BCCAD Read(byte[] bytes)
{ {
sheetW = BitConverter.ToUInt16(bytes, 4); BCCAD bccad = new BCCAD();
sheetH = BitConverter.ToUInt16(bytes, 6); var byteBuffer = new ByteBuffer(bytes);
byteBuffer.ReadInt(); //timestamp
bccad.sheetW = byteBuffer.ReadUShort();
// int max = (bytes[8] * 2) + 12; bccad.sheetH = byteBuffer.ReadUShort();
int max = 64 * bytes[8] + 12; // Sprites
var spritesNum = byteBuffer.ReadInt();
// note this doesn't account for empty sprites, but I'll get there when i get there for (int i = 0; i < spritesNum; i++)
for (int i = 12; i < max; i += 2) // 16 bit bytes, skip every 2nd byte
{ {
ISprite spriteParts_ = new ISprite(); BccadSprite bccadSprite = new BccadSprite();
int compare = 0; var partsNum = byteBuffer.ReadInt();
for (int j = 0; j < bytes[i]; j++) 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.Opacity = byteBuffer.ReadByte();
part.regionX = BitConverter.ToUInt16(bytes, ind + 0); for (int k = 0; k < 12; k++)
part.regionY = BitConverter.ToUInt16(bytes, ind + 2); {
part.regionW = BitConverter.ToUInt16(bytes, ind + 4); byteBuffer.ReadByte();
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];
Debug.Log("offset: " + ind + ", val: " + part.regionX); part.designation = byteBuffer.ReadByte();
part.unknown = byteBuffer.ReadShort();
spriteParts_.parts.Add(part); part.tlDepth = byteBuffer.ReadFloat();
part.blDepth = byteBuffer.ReadFloat();
compare += 64; 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
} }
} }

View file

@ -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<RegionIndex, GameObject> RegionToChild = new Dictionary<RegionIndex, GameObject>();
private readonly List<GameObject> _children = new List<GameObject>();
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<RegionIndex> { 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<RegionIndex> FindNotAdjacentRegions(List<RegionIndex> regions, List<BccadSprite> sprites, List<RegionIndex> availableRegions)
{
var notAdjacentRegions = new List<RegionIndex>(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<Tuple<int, SpritePart, GameObject>> GetHiddenParts()
{
var sprite = _bccad.sprites[_data.SpriteIndex];
var pairs = new List<Tuple<int, SpritePart, GameObject>>();
var gameObjects = new List<GameObject>(_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<int, SpritePart, GameObject>(index, part, gameObject));
}
return pairs;
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: ddf1fd563dabc6040a863537a081843a guid: 7be9a40b9a8419b43aa454fa09e9ce78
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View file

@ -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<string>();
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);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af00b07ba074d9c44ab931b3b98e5605
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,47 +1,131 @@
using System.Collections; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
using Starpelly; using Bread2Unity;
namespace 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<PrefabData> _prefabDataList = new List<PrefabData>();
private List<string> _animationsIndexes;
private Vector2 _scrollPosition;
[MenuItem("Tools/bread2unity")] [MenuItem("Tools/bread2unity")]
public static void ShowWindow() public static void ShowWindow()
{ {
EditorWindow.GetWindow<Bread2Unity>("bread2unity"); GetWindow<Bread2UnityGUI>("bread2unity");
}
public void CreateGUI()
{
_animationsIndexes = new List<string>();
} }
public void OnGUI() public void OnGUI()
{ {
Texture logo = (Texture)AssetDatabase.LoadAssetAtPath($"Assets/Editor/{editorFolderName}/logo.png", typeof(Texture)); Texture logo =
GUILayout.Box(logo, new GUILayoutOption[] { GUILayout.ExpandWidth(true), GUILayout.Height(60) }); (Texture)AssetDatabase.LoadAssetAtPath($"Assets/Editor/{EditorFolderName}/logo.png", typeof(Texture));
GUILayout.Box(logo, GUILayout.ExpandWidth(true), GUILayout.Height(60));
GUILayout.Space(30); GUILayout.Space(30);
GUIStyle desc = EditorStyles.label; GUIStyle desc = EditorStyles.label;
desc.wordWrap = true; desc.wordWrap = true;
desc.fontStyle = FontStyle.BoldAndItalic; 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." + GUILayout.Box(
"\nCreated by Starpelly.", desc); "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"); fontSize = 35,
if (path.Length != 0) 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); _prefabDataList.RemoveAt(i);
new BCCAD().Read(fileContent); _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<int> animationIndexes;
var prefabData = _prefabDataList[i];
if (_animationsIndexes[i].Equals(""))
animationIndexes = new List<int>();
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(); GUILayout.BeginHorizontal();
@ -49,6 +133,7 @@ namespace Bread2Unity
{ {
Application.OpenURL("https://github.com/rhmodding/bread"); Application.OpenURL("https://github.com/rhmodding/bread");
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
} }

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1883cd0d74b590740936a6ffc3580eb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,35 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Bread2Unity
{
public class Animation
{
public List<AnimationStep> Steps = new List<AnimationStep>();
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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 994f68ab1a3b68b49a0ae01b0bf37f0c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,70 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Bread2Unity
{
public class BccadSprite
{
public List<SpritePart> parts = new List<SpritePart>();
}
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea35991e78a2918439cc249d1e4e293e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,52 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Bread2Unity
{
public class DataModel
{
public List<Region> regions = new List<Region>();
public List<BccadSprite> sprites = new List<BccadSprite>();
public List<Animation> animations = new List<Animation>();
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c90d75428898ac74e819c39b7249f133
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,24 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Bread2Unity
{
public class IAnimation
{
public List<IAnimationStep> steps;
}
public class IAnimationStep
{
public ushort spriteIndex;
public ushort delay;
public float stretchX;
public float stretchY;
public float rotation;
public byte opacity;
}
}

View file

@ -1,13 +0,0 @@
using System.Collections;
using System.Collections.Generic;
namespace Bread2Unity
{
public class IDataModel
{
public List<ISprite> sprites = new List<ISprite>();
public List<IAnimation> animations = new List<IAnimation>();
public int sheetW;
public int sheetH;
}
}

View file

@ -1,31 +0,0 @@
using System.Collections;
using System.Collections.Generic;
namespace Bread2Unity
{
public class ISprite
{
public List<ISpritePart> parts = new List<ISpritePart>();
}
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;
}
}

View file

@ -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<PrefabData> 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<Sprite>(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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59809bd6fc62dce4aab4eb333ffbdc64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using Bread2Unity;
namespace Bread2Unity
{
public class PrefabData
{
public string Name;
public List<Animation> Animations;
public int SpriteIndex;
public PrefabData(string name, int spriteIndex)
{
Name = name;
SpriteIndex = spriteIndex;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6807e41eb8dc0ae46b2e5b728af55e2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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<SpriteMetaData>();
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a4430af4dfb91f4f9720962a6eef059
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: