Para realizar buenas prácticas en el desarrollo de videojuegos se deben agrupar las imágenes que compongan nuestro proyecto en sprites.
Para ello Unity nos presenta una herramienta un tanto trabajosa de utilizar, puesto que hay que recortar todas y cada una de las partes de éstos sprites de forma manual, para luego poder trabajar con ellos. Por éste motivo aquí presento una herramienta muy útil para el uso de sprites en Unity:
En un primer lugar os presento ShoeBox: Se trata de una herramienta para crear sprites en la cual tan sólo tienes que arrastrar todas las imágenes que se quieran agrupar y el sólo genera el sprite en formato png y un documento xml para luego facilitar el proceso de utilizar el sprite:
Para procesar estos archivos xml en Unity y que reconozca directamente nuestros sprites, debemos implementar el siguiente script (con que esté presente en el proyecto es suficiente):
[code]
using System;
using System.IO;
using System.Linq;
using System.Xml;
using UnityEditor;
using UnityEngine;
public class TextureAtlasSlicer : EditorWindow {
[MenuItem(«CONTEXT/TextureImporter/Slice Sprite Using XML»)]
public static void SliceUsingXML(MenuCommand command) {
var textureImporter = command.context as TextureImporter;
var window = CreateInstance<TextureAtlasSlicer>();
window.importer = textureImporter;
window.ShowUtility();
}
[MenuItem(«Assets/Slice Sprite Using XML»)]
public static void TextureAtlasSlicerWindow() {
var window = CreateInstance<TextureAtlasSlicer>();
window.Show();
}
[MenuItem(«CONTEXT/TextureImporter/Slice Sprite Using XML», true)]
public static bool ValidateSliceUsingXML(MenuCommand command) {
var textureImporter = command.context as TextureImporter;
//valid only if the texture type is ‘sprite’ or ‘advanced’.
return textureImporter && textureImporter.textureType == TextureImporterType.Sprite ||
textureImporter.textureType == TextureImporterType.Advanced;
}
public TextureImporter importer;
public TextureAtlasSlicer() {
title = «Texture Atlas Slicer»;
}
[SerializeField]
private TextAsset xmlAsset;
public SpriteAlignment spriteAlignment = SpriteAlignment.Center;
public Vector2 customOffset = new Vector2(0.5f, 0.5f);
public void OnSelectionChange() {
UseSelectedTexture();
}
private Texture2D selectedTexture;
private void UseSelectedTexture() {
if (Selection.objects.Length > 1) {
selectedTexture = null;
} else {
selectedTexture = Selection.activeObject as Texture2D;
}
if (selectedTexture != null) {
var assetPath = AssetDatabase.GetAssetPath(selectedTexture);
importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (importer) {
var extension = Path.GetExtension(assetPath);
var pathWithoutExtension = assetPath.Remove(assetPath.Length – extension.Length, extension.Length);
var xmlPath = pathWithoutExtension + «.xml»;
var textAsset = AssetDatabase.LoadAssetAtPath(xmlPath, typeof (TextAsset));
if (textAsset != null) {
xmlAsset = textAsset as TextAsset;
} else {
xmlAsset = null;
subTextures = null;
}
ParseXML();
} else {
xmlAsset = null;
subTextures = null;
}
} else {
importer = null;
xmlAsset = null;
subTextures = null;
}
Repaint();
}
private SubTexture[] subTextures;
private int wantedWidth, wantedHeight;
private void ParseXML() {
try {
var document = new XmlDocument();
document.LoadXml(xmlAsset.text);
var root = document.DocumentElement;
if (root == null || root.Name != «TextureAtlas») {
return;
}
subTextures = root.ChildNodes
.Cast<XmlNode>()
.Where(childNode => childNode.Name == «SubTexture»)
.Select(childNode => new SubTexture {
width = Convert.ToInt32(childNode.Attributes[«width»].Value),
height = Convert.ToInt32(childNode.Attributes[«height»].Value),
x = Convert.ToInt32(childNode.Attributes[«x»].Value),
y = Convert.ToInt32(childNode.Attributes[«y»].Value),
name = childNode.Attributes[«name»].Value
}).ToArray();
wantedWidth = 0;
wantedHeight = 0;
foreach (var subTexture in subTextures) {
var right = subTexture.x + subTexture.width;
var bottom = subTexture.y + subTexture.height;
wantedWidth = Mathf.Max(wantedWidth, right);
wantedHeight = Mathf.Max(wantedHeight, bottom);
}
} catch (Exception) {
subTextures = null;
}
}
public void OnEnable() {
UseSelectedTexture();
}
public void OnGUI() {
if (importer == null) {
EditorGUILayout.LabelField(«Please select a texture to slice.»);
return;
}
EditorGUI.BeginDisabledGroup(focusedWindow != this);
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(«Texture», Selection.activeObject, typeof (Texture), false);
EditorGUI.EndDisabledGroup();
if (importer.textureType != TextureImporterType.Sprite &&
importer.textureType != TextureImporterType.Advanced) {
EditorGUILayout.LabelField(«The Texture Type needs to be Sprite or Advanced!»);
}
EditorGUI.BeginDisabledGroup((importer.textureType != TextureImporterType.Sprite &&
importer.textureType != TextureImporterType.Advanced));
{
EditorGUI.BeginChangeCheck();
xmlAsset = EditorGUILayout.ObjectField(«XML Source», xmlAsset, typeof (TextAsset), false) as TextAsset;
if (EditorGUI.EndChangeCheck()) {
ParseXML();
}
spriteAlignment = (SpriteAlignment) EditorGUILayout.EnumPopup(«Pivot», spriteAlignment);
EditorGUI.BeginDisabledGroup(spriteAlignment != SpriteAlignment.Custom);
EditorGUILayout.Vector2Field(«Custom Offset», customOffset);
EditorGUI.EndDisabledGroup();
var needsToResizeTexture = wantedWidth > selectedTexture.width || wantedHeight > selectedTexture.height;
if (xmlAsset != null && needsToResizeTexture) {
EditorGUILayout.LabelField(
string.Format(«Texture size too small.»
+ » It needs to be at least {0} by {1} pixels!»,
wantedWidth,
wantedHeight));
EditorGUILayout.LabelField(«Try changing the Max Size property in the importer.»);
}
if (subTextures == null || subTextures.Length == 0) {
EditorGUILayout.LabelField(«Could not find any SubTextures in XML.»);
}
EditorGUI.BeginDisabledGroup(xmlAsset == null || needsToResizeTexture || subTextures == null ||
subTextures.Length == 0);
if (GUILayout.Button(«Slice»)) {
PerformSlice();
}
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
}
private struct SubTexture {
public int width;
public int height;
public int x;
public int y;
public string name;
}
private void PerformSlice() {
if (importer == null) {
return;
}
var textureHeight = selectedTexture.height;
var needsUpdate = false;
if (importer.spriteImportMode != SpriteImportMode.Multiple) {
needsUpdate = true;
importer.spriteImportMode = SpriteImportMode.Multiple;
}
var wantedSpriteSheet = (from subTexture in subTextures
let actualY = textureHeight – (subTexture.y + subTexture.height)
select new SpriteMetaData {
alignment = (int) spriteAlignment,
border = new Vector4(),
name = subTexture.name,
pivot = GetPivotValue(spriteAlignment, customOffset),
rect = new Rect(subTexture.x, actualY, subTexture.width, subTexture.height)
}).ToArray();
if (!needsUpdate && !importer.spritesheet.SequenceEqual(wantedSpriteSheet)) {
needsUpdate = true;
importer.spritesheet = wantedSpriteSheet;
}
if (needsUpdate) {
EditorUtility.SetDirty(importer);
try {
AssetDatabase.StartAssetEditing();
AssetDatabase.ImportAsset(importer.assetPath);
EditorUtility.DisplayDialog(«Success!», «The sprite was sliced successfully.», «OK»);
} catch (Exception exception) {
Debug.LogException(exception);
EditorUtility.DisplayDialog(«Error», «There was an exception while trying to reimport the image.» +
«\nPlease check the console log for details.», «OK»);
} finally {
AssetDatabase.StopAssetEditing();
}
} else {
EditorUtility.DisplayDialog(«Nope!», «The sprite is already sliced according to this XML file.», «OK»);
}
}
//SpriteEditorUtility
public static Vector2 GetPivotValue(SpriteAlignment alignment, Vector2 customOffset) {
switch (alignment) {
case SpriteAlignment.Center:
return new Vector2(0.5f, 0.5f);
case SpriteAlignment.TopLeft:
return new Vector2(0.0f, 1f);
case SpriteAlignment.TopCenter:
return new Vector2(0.5f, 1f);
case SpriteAlignment.TopRight:
return new Vector2(1f, 1f);
case SpriteAlignment.LeftCenter:
return new Vector2(0.0f, 0.5f);
case SpriteAlignment.RightCenter:
return new Vector2(1f, 0.5f);
case SpriteAlignment.BottomLeft:
return new Vector2(0.0f, 0.0f);
case SpriteAlignment.BottomCenter:
return new Vector2(0.5f, 0.0f);
case SpriteAlignment.BottomRight:
return new Vector2(1f, 0.0f);
case SpriteAlignment.Custom:
return customOffset;
default:
return Vector2.zero;
}
}
}
[/code]
Una vez tenemos este script en nuestro proyecto tan sólo tenemos que procesar los sprites en el inspector de la imagen (este menú aparece en lateral derecho al hacer click sobre una imagen):
Vinculamos el archivo xml a la imagen y ya tenemos listo el sprite para poder utilizarlo.