See http://www.tasharen.com/forum/index.php?topic=14818.0 for backgroundOur game has been needing to use tightly packed sprites as much as possible to reduce fill rate and texture usage (Unity is very efficient if you let it use 'tight' packing).
The only downside was that NGUI did not support tight sprites due to most of the internals being based around quads. However, NGUI did not seem to care if the quads were rectangular; nor did it care if they were continuous quads or not. Just that the amount of vertices was a multiple of 4. Unity tightly packed sprites are more like traditional meshes. They are based on being multiples of 3 since they're based on triangles, not quads.
During the process of trying to convert NGUI to work with "multiple of three" meshes, I realized that I could instead just add an extra vertex to each triangle to make it technically a quad- even though the second triangle of the quad would be perceptually invisible. ArenMook confirmed my suspicion, that the few extra vertices shouldn't really cause any noticeable performance hit.
ArenMook, if you want to adopt this into NGUI, feel free to do so. If anyone finds concrete ways to improve the code, please reply to this thread. There may be a few minor bugs.
Note to users: Currently, "Type" is forced to "Simple" since the OnFill math for doing fancier types (Filled, etc) would be pretty complex and outside of my purview.UI2DSpriteTight.cs (rev 5)
using UnityEngine;
[ExecuteInEditMode]
[AddComponentMenu("NGUI/UI/NGUI Unity2D Sprite (Tight)")]
public class UI2DSpriteTight : UI2DSprite
{
protected override void Awake()
{
base.Awake();
if (sprite2D != null && (!sprite2D.packed || sprite2D.packingMode != SpritePackingMode.Tight))
{
Debug.LogWarning(GetType() + " should generally only be used on tightly packed sprites!");
}
if (type != Type.Simple)
{
type = Type.Simple;
Debug.LogWarning(GetType() + " does not support complex sprite types! Forcing simple type.");
}
}
public override void OnFill(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color> cols)
{
var spr = sprite2D;
if (spr == null) return;
var colFlat = drawingColor;
var sprUVs = spr.uv;
var sprVerts = spr.vertices;
var tris = spr.triangles;
var texRect = spr.rect;
var flip = FlipScalar;
var size
= new Vector2
(mWidth, mHeight
); var scale
= new Vector2
(size
.x / texRect
.width * flip
.x, size
.y / texRect
.height * flip
.y) * spr
.pixelsPerUnit; var pivotOff = -size / 2F + Vector2.Scale(size, pivotOffset);
if (FixedAspect) scale.x = scale.y = Mathf.Min(scale.x, scale.y);
for (int i = 0; i < tris.Length; ++i)
{
var index = tris[i];
var vertBase = Vector2.Scale(sprVerts[index], scale);
var vert = vertBase - pivotOff;
var uv = sprUVs[index];
var col = mApplyGradient ? (Color.Lerp(mGradientBottom, mGradientTop, (vertBase.y + size.y * 0.5F) / size.y) * color).GammaToLinearSpace() : colFlat;
verts.Add(vert);
uvs.Add(uv);
cols.Add(col);
if (i % 3 == 0)
{
verts.Add(vert);
uvs.Add(uv);
cols.Add(col);
}
}
if (onPostFill != null)
onPostFill(this, verts.size, verts, uvs, cols);
}
private Vector2 FlipScalar
{
get
{
switch (mFlip)
{
case Flip
.Horizontally: return new Vector2
(-1F, 1F
); case Flip
.Vertically: return new Vector2
(1F,
-1F
); case Flip
.Both: return new Vector2
(-1F,
-1F
); default: return Vector2.one;
}
}
}
public override Type type
{
get
{
return Type.Simple;
}
set
{
if (base.type != Type.Simple) base.type = Type.Simple;
}
}
private bool? fixedAspect = null;
protected bool FixedAspect
{
get
{
if (fixedAspect == null)
{
var valueField = GetType().BaseType.GetField("mFixedAspect", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var value = valueField.GetValue(this) as bool?;
fixedAspect = (value == null) ? false : value;
}
return fixedAspect.Value;
}
}
#if UNITY_EDITOR
private Sprite lastSprite;
protected override void OnUpdate()
{
base.OnUpdate();
if (mType != Type.Simple) mType = Type.Simple;
if (Application.isPlaying && sprite2D != lastSprite)
{
lastSprite = sprite2D;
OnInit();
}
}
#endif
}
UI2DSpriteTightEditor.cs (rev 2)
using UnityEditor;
using UnityEngine;
using UnityEngine.Sprites;
[CanEditMultipleObjects]
[CustomEditor
(typeof(UI2DSpriteTight
),
true)] public class UI2DSpriteTightEditor : UI2DSpriteEditor
{
UI2DSpriteTight mySprite;
protected override void OnEnable()
{
base.OnEnable();
mySprite = target as UI2DSpriteTight;
}
public override void OnPreviewGUI(Rect rect, GUIStyle background)
{
if (mySprite != null && mySprite.sprite2D != null)
{
Texture2D tex = mySprite.mainTexture as Texture2D;
NGUIEditorTools.DrawSprite(tex, rect, mySprite.color, CalculateTextureRect(mySprite.sprite2D), mySprite.border);
}
}
private static Rect CalculateTextureRect(Sprite spr)
{
int w = spr.texture.width, h = spr.texture.height;
var padding = DataUtility.GetPadding(spr); // (x=left, y=bottom, z=right, w=top)
var uv = DataUtility.GetOuterUV(spr);
new Vector2
(w
* uv
.x - padding
.x, h
* uv
.y - padding
.y),
new Vector2
(w
* (uv
.z - uv
.x) + padding
.x + padding
.z, h
* (uv
.w - uv
.y) + padding
.y + padding
.w) );
}
}
Note to ArenMook: My call to OnInit() in OnUpdate() fixes a bug that affects the parent class (UI2DSprite) as well, so you may want to adopt it. It happens when changing mSprite directly (via inspector serialization) when the game is running. It manifests as totally wrong UV values but correct vertex geometry.