准备
- 下载工具 https://github.com/Sarofc/ExtractorSharp
- 使用工具 下载图包、音效。
导入思路
- 2d游戏一般都会打atlas,atlas里有每个图片的rect信息,导出工具可以将此信息导出。
- unity的sprite importer支持代码处理sprite,可以直接设置spritePivot,使用导出的offset信息,可以直接批量处理所有图片。
- 使用代码生成Animation Clip。
- 使用代码生成Animator Controller。
修改 SpritePivot 参考
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.IO;
namespace Rpg.Importer
{
public class DNFSpriteImporter
{
public static void ApplyTextures(Texture[] textures, OffsetData[] offsetDatas, TextureImporterSettings textureSettings, bool useOffsetInfo)
{
if (textures.Length <= 0 || offsetDatas.Length <= 0 || offsetDatas.Length < textures.Length)
{
Debug.LogError($"offset is not equal! textures : {textures.Length} , offsetDatas : {offsetDatas.Length}");
return;
}
try
{
AssetDatabase.StartAssetEditing();
for (int i = 0; i < textures.Length; i++)
{
var path = AssetDatabase.GetAssetPath(textures[i]);
var texture = textures[i];
EditorUtility.DisplayProgressBar("import img", path, (float)i / textures.Length);
TextureImporter import = AssetImporter.GetAtPath(path) as TextureImporter;
if (textureSettings == null)
{
textureSettings = new TextureImporterSettings();
}
import.ReadTextureSettings(textureSettings);
textureSettings.textureType = TextureImporterType.Sprite;
textureSettings.npotScale = TextureImporterNPOTScale.None;
textureSettings.sRGBTexture = true;
textureSettings.spritePixelsPerUnit = 100;
textureSettings.alphaSource = TextureImporterAlphaSource.FromInput;
textureSettings.spriteMode = (int)SpriteImportMode.Single;
textureSettings.spriteMeshType = SpriteMeshType.FullRect;
textureSettings.mipmapEnabled = false;
textureSettings.wrapMode = TextureWrapMode.Repeat;
textureSettings.filterMode = FilterMode.Point;
if (useOffsetInfo)
{
textureSettings.spriteAlignment = (int)SpriteAlignment.Custom;
textureSettings.spritePivot = new Vector2(
0.5f - (float)(offsetDatas[i].OffsetX + texture.width / 2) / texture.width,
0.5f + (float)(offsetDatas[i].OffsetY + texture.height / 2) / texture.height
);
}
else
{
textureSettings.spriteAlignment = (int)SpriteAlignment.Center;
}
import.SetTextureSettings(textureSettings);
AssetDatabase.ImportAsset(path);
}
AssetDatabase.Refresh();
}
finally
{
AssetDatabase.StopAssetEditing();
EditorUtility.ClearProgressBar();
}
}
}
public class BatDNFSpriteImporterWindow : EditorWindow
{
[MenuItem("DNF/Bat_DNFSpriterImporter")]
private static void Init()
{
var window = EditorWindow.GetWindow<BatDNFSpriteImporterWindow>();
window.Show();
}
public bool useOffsetInfo = true;
public List<string> paths = new List<string>();
private Vector2 m_ScrollPos;
private void OnGUI()
{
EditorGUILayout.HelpBox("1. Drag folder(s) which contains sprites and info.json to the paths label.\n2. Click process button.", MessageType.Info);
var dragArea = GUILayoutUtility.GetRect(0f, 35f, GUILayout.ExpandWidth(true));
EditorGUI.LabelField(dragArea, "Paths : drag folder(s) here.");
m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);
for (int i = 0; i < paths.Count; i++)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"{i.ToString()}. ", GUILayout.Width(18));
paths[i] = EditorGUILayout.TextField(paths[i]);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
if (Event.current.type == EventType.DragUpdated && dragArea.Contains(Event.current.mousePosition))
{
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
}
else if (Event.current.type == EventType.DragExited && dragArea.Contains(Event.current.mousePosition))
{
var dragPaths = DragAndDrop.paths;
if (DragAndDrop.paths != null && dragPaths.Length > 0)
{
for (int i = 0; i < dragPaths.Length; i++)
{
if (!paths.Contains(dragPaths[i]))
paths.Add(dragPaths[i]);
}
}
}
if (GUILayout.Button("Clear all paths"))
{
paths.Clear();
}
useOffsetInfo = EditorGUILayout.Toggle("使用info文件", useOffsetInfo);
EditorGUILayout.Space();
if (GUILayout.Button("Process"))
{
List<Texture> texArray = new List<Texture>(48);
TextureImporterSettings settings = new TextureImporterSettings();
for (int i = 0; i < paths.Count; i++)
{
texArray.Clear();
var texDirs = Directory.GetFiles(paths[i], "*.png", SearchOption.AllDirectories);
var strDirs = Directory.GetFiles(paths[i], "*.json", SearchOption.AllDirectories);
Debug.Log("texDirs length : " + texDirs.Length + " , strDirs length : " + strDirs.Length);
for (int k = 0; k < texDirs.Length; k++)
{
texArray.Add(AssetDatabase.LoadAssetAtPath<Texture>(texDirs[k]));
}
texArray.Sort((a, b) => int.Parse(a.name) - int.Parse(b.name));
var offsets = JsonConvert.DeserializeObject<List<OffsetData>>(AssetDatabase.LoadAssetAtPath<TextAsset>(strDirs[0]).text);
// Debug.Log($"textures : {texArray.Count} , offsetDatas : {offsets.Count}");
// for (int k = 0; k < texArray.Count; k++)
// {
// var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(texArray[k]));
// Debug.Log(
// $"[id : {texArray[k].name}] \t [offset : ({offsets[i].x} , {offsets[i].y})] \t " +
// $"[pivot : ({ 0.5f - (float)(offsets[i].OffsetX + sprite.rect.width / 2) / sprite.rect.width} , " +
// $"{ 0.5f + (float)(offsets[i].OffsetY + sprite.rect.height / 2) / sprite.rect.height})]");
// }
DNFSpriteImporter.ApplyTextures(texArray.ToArray(), offsets.ToArray(), settings, useOffsetInfo);
}
}
}
}
[System.Serializable]
public class OffsetData
{
const int AddOffsetX = 229;
const int AddOffsetY = 333;
public int x;
public int y;
public int OffsetX { get { return x - AddOffsetX; } }
public int OffsetY { get { return y - AddOffsetY; } }
}
}
生成 Animation Clip 参考
using Saro.Paperdoll;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Rpg
{
public class DnfAnimationCreator : EditorWindow
{
[MenuItem("DNF/Animation Creator")]
static void DoIt()
{
GetWindow<DnfAnimationCreator>();
}
private string m_OutputPath = "Assets/Arts/Animator/AnimationCreateByScripts/clip new.anim";
private float m_FrameRate = 12f;
private SpritePack m_SpritePack;
private Paperdoll m_Paperdoll;
private void OnGUI()
{
m_OutputPath = EditorGUILayout.TextField("OutputPath", m_OutputPath);
m_SpritePack = EditorGUILayout.ObjectField("SpritePack", m_SpritePack, typeof(SpritePack), false) as SpritePack;
m_Paperdoll = EditorGUILayout.ObjectField("Actor(PaperDoll)", m_Paperdoll, typeof(Paperdoll), true) as Paperdoll;
if (m_SpritePack && m_Paperdoll)
{
if (GUILayout.Button("create aniamtion"))
{
try
{
var clip = new AnimationClip();
clip.frameRate = m_FrameRate;
clip.legacy = false;
var category = m_SpritePack.m_SpriteCategories[0];
var hashKeys = category.hashes;
var animatedKeys = new Keyframe[category.sprites.Length];
for (int i = 0; i < hashKeys.Length; i++)
{
var hash = hashKeys[i];
animatedKeys[i] = new Keyframe
{
time = 1f / m_FrameRate * i,
value = PaperdollUtility.ConvertIntToFloat(hash),
};
}
var curve = new AnimationCurve(animatedKeys);
for (int i = 0; i < animatedKeys.Length; i++)
{
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
}
var paths = new List<string>();
m_Paperdoll.equipSlots.ForEach(slot =>
{
slot.PaperdollParts.ForEach(p =>
{
var path = p.name;
var cur = p.transform;
while (cur.parent)
{
cur = cur.parent;
if (cur == m_Paperdoll.transform) break;
path = cur.name + "/" + path;
}
paths.Add(path);
});
}
);
for (int i = 0; i < paths.Count; i++)
{
var animatedBindings = EditorCurveBinding.FloatCurve(paths[i], typeof(PaperdollPart), "m_SpriteHash");
AnimationUtility.SetEditorCurve(clip, animatedBindings, curve);
}
AssetDatabase.CreateAsset(clip, m_OutputPath);
AssetDatabase.Refresh();
}
catch (System.Exception e)
{
Debug.LogError(e);
}
}
}
}
}
}
生成 Animator Controller 参考
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using UnityEditor.Animations;
namespace Rpg
{
public class DnfAnimatorBuilder : EditorWindow
{
[MenuItem("DNF/AnimatorBuilder")]
private static void ShowWindow()
{
var window = GetWindow<DnfAnimatorBuilder>();
window.titleContent = new GUIContent("AnimatorBuilder");
window.Show();
}
public List<AnimationClip> animClips = new List<AnimationClip>();
public string savePath = "Assets/Arts/Animator/";
public string animatorName = "Test.controller";
private Vector2 m_ScrollPos;
private void OnGUI()
{
EditorGUILayout.HelpBox("1. Drag folder(s) to paths label.\n2. Click process button.", MessageType.Info);
savePath = EditorGUILayout.TextField(savePath);
animatorName = EditorGUILayout.TextField(animatorName);
var dragArea = GUILayoutUtility.GetRect(0f, 35f, GUILayout.ExpandWidth(true));
EditorGUI.LabelField(dragArea, "Animation Clips : drag animation(s) here.");
m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);
for (int i = 0; i < animClips.Count; i++)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"{i.ToString()}. ", GUILayout.Width(18));
EditorGUILayout.ObjectField(animClips[i], typeof(AnimationClip), false);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
if (Event.current.type == EventType.DragUpdated && dragArea.Contains(Event.current.mousePosition))
{
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
}
else if (Event.current.type == EventType.DragExited && dragArea.Contains(Event.current.mousePosition))
{
var dragPaths = DragAndDrop.objectReferences;
if (DragAndDrop.paths != null && dragPaths.Length > 0)
{
for (int i = 0; i < dragPaths.Length; i++)
{
var animClip = dragPaths[i] as AnimationClip;
if (animClip)
{
if (!animClips.Contains(animClip))
{
animClips.Add(animClip);
}
}
}
}
}
if (GUILayout.Button("Clear all animations"))
{
animClips.Clear();
}
EditorGUILayout.Space();
if (GUILayout.Button("Process"))
{
BuildAnimator(savePath + animatorName, animClips);
}
}
private void BuildAnimator(string controllerPath, List<AnimationClip> clips)
{
/**************************************************************
/* create animator controller
/**************************************************************/
var controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
var baseLayer = controller.layers[0].stateMachine;
baseLayer.anyStatePosition = new Vector3(0f, 0f);
baseLayer.entryPosition = new Vector3(0f, 50f);
baseLayer.exitPosition = new Vector3(1000f, 0f);
/**************************************************************
/* create states
/**************************************************************/
var nullstate = baseLayer.AddState("null", new Vector2(0f, 100f));
baseLayer.defaultState = nullstate;
var startPos = new Vector3(400f, 0f);
for (int i = 0; i < clips.Count; i++)
{
var state = baseLayer.AddState(clips[i].name, startPos);
state.motion = clips[i];
var transition = state.AddExitTransition();
transition.hasExitTime = true;
transition.exitTime = 1f;
transition.duration = 0f;
startPos.y += 50f;
}
AssetDatabase.Refresh();
}
}
}
小技巧
- AssetDatabase.StartAssetEditing 能加速批量导入。