//------------------------------------------------- // NGUI: Next-Gen UI kit // Copyright © 2011-2019 Tasharen Entertainment Inc //------------------------------------------------- using UnityEngine; using System.Collections.Generic; /// /// Sprite collection is a widget that contains a bunch of sprites that don't create their own game objects and colliders. /// Its best usage is to replace the need to create individual game objects while still maintaining full visualization /// and interaction functionality of NGUI's sprites. For example: a world map with thousands of individual icons. /// The thousands of individual icons can be a single Sprite Collection. Its downside is that the sprites can't be /// interacted with in the Editor window, as this is meant to be a fast, programmable solution. /// [ExecuteInEditMode] [AddComponentMenu("NGUI/UI/Sprite Collection")] public class UISpriteCollection : UIBasicSprite { /// /// Sub-sprite entry within the collection. /// public struct Sprite { public UISpriteData sprite; public Vector2 pos; public float rot; public float width; public float height; public Color32 color; public Vector2 pivot; public Type type; public Flip flip; public bool enabled; /// /// Calculate the sprite's drawing dimensions. /// public Vector4 GetDrawingDimensions (float pixelSize) { var x0 = -pivot.x * width; var y0 = -pivot.y * height; var x1 = x0 + width; var y1 = y0 + height; if (sprite != null && type != Type.Tiled) { var padLeft = sprite.paddingLeft; var padBottom = sprite.paddingBottom; var padRight = sprite.paddingRight; var padTop = sprite.paddingTop; if (type != Type.Simple && pixelSize != 1f) { padLeft = Mathf.RoundToInt(pixelSize * padLeft); padBottom = Mathf.RoundToInt(pixelSize * padBottom); padRight = Mathf.RoundToInt(pixelSize * padRight); padTop = Mathf.RoundToInt(pixelSize * padTop); } var w = sprite.width + padLeft + padRight; var h = sprite.height + padBottom + padTop; var px = 1f; var py = 1f; if (w > 0 && h > 0 && (type == Type.Simple || type == Type.Filled)) { if ((w & 1) != 0) ++padRight; if ((h & 1) != 0) ++padTop; px = (1f / w) * width; py = (1f / h) * height; } if (flip == Flip.Horizontally || flip == Flip.Both) { x0 += padRight * px; x1 -= padLeft * px; } else { x0 += padLeft * px; x1 -= padRight * px; } if (flip == Flip.Vertically || flip == Flip.Both) { y0 += padTop * py; y1 -= padBottom * py; } else { y0 += padBottom * py; y1 -= padTop * py; } } return new Vector4(x0, y0, x1, y1); } } [HideInInspector, SerializeField] Object mAtlas; // List of individual sprites [System.NonSerialized] Dictionary mSprites = new Dictionary(); // Only valid during the OnFill process [System.NonSerialized] UISpriteData mSprite; /// /// Main texture is assigned on the atlas. /// public override Texture mainTexture { get { Material mat = null; var ia = atlas; if (ia != null) mat = ia.spriteMaterial; return (mat != null) ? mat.mainTexture : null; } set { base.mainTexture = value; } } /// /// Material comes from the base class first, and sprite atlas last. /// public override Material material { get { var mat = base.material; if (mat != null) return mat; var ia = atlas; if (ia != null) return ia.spriteMaterial; return null; } set { base.material = value; } } /// /// Atlas used by this widget. /// public INGUIAtlas atlas { get { return mAtlas as INGUIAtlas; } set { if (mAtlas as INGUIAtlas != value) { RemoveFromPanel(); mAtlas = value as UnityEngine.Object; mSprites.Clear(); MarkAsChanged(); } } } /// /// Size of the pixel -- used for drawing. /// override public float pixelSize { get { var ia = atlas; if (ia != null) return ia.pixelSize; return 1f; } } /// /// Whether the texture is using a premultiplied alpha material. /// public override bool premultipliedAlpha { get { var ia = atlas; if (ia != null) return ia.premultipliedAlpha; return false; } } // /// Sliced sprites generally have a border. X = left, Y = bottom, Z = right, W = top. /// public override Vector4 border { get { if (mSprite == null) return base.border; return new Vector4(mSprite.borderLeft, mSprite.borderBottom, mSprite.borderRight, mSprite.borderTop); } } /// /// Trimmed space in the atlas around the sprite. X = left, Y = bottom, Z = right, W = top. /// protected override Vector4 padding { get { var p = new Vector4(0, 0, 0, 0); if (mSprite != null) { p.x = mSprite.paddingLeft; p.y = mSprite.paddingBottom; p.z = mSprite.paddingRight; p.w = mSprite.paddingTop; } return p; } } /// /// Fill the draw buffers. /// public override void OnFill (List verts, List uvs, List cols) { var tex = mainTexture; if (tex == null) return; int offset = verts.Count; var drawRegion = this.drawRegion; foreach (var pair in mSprites) { var ent = pair.Value; if (!ent.enabled) continue; mSprite = ent.sprite; if (mSprite == null) continue; Color c = ent.color; c.a = finalAlpha; if (c.a == 0f) continue; var outer = new Rect(mSprite.x, mSprite.y, mSprite.width, mSprite.height); var inner = new Rect(mSprite.x + mSprite.borderLeft, mSprite.y + mSprite.borderTop, mSprite.width - mSprite.borderLeft - mSprite.borderRight, mSprite.height - mSprite.borderBottom - mSprite.borderTop); mOuterUV = NGUIMath.ConvertToTexCoords(outer, tex.width, tex.height); mInnerUV = NGUIMath.ConvertToTexCoords(inner, tex.width, tex.height); mFlip = ent.flip; var v = ent.GetDrawingDimensions(pixelSize); var u = drawingUVs; if (premultipliedAlpha) c = NGUITools.ApplyPMA(c); var start = verts.Count; switch (ent.type) { case Type.Simple: SimpleFill(verts, uvs, cols, ref v, ref u, ref c); break; case Type.Sliced: SlicedFill(verts, uvs, cols, ref v, ref u, ref c); break; case Type.Filled: FilledFill(verts, uvs, cols, ref v, ref u, ref c); break; case Type.Tiled: TiledFill(verts, uvs, cols, ref v, ref c); break; case Type.Advanced: AdvancedFill(verts, uvs, cols, ref v, ref u, ref c); break; } if (ent.rot != 0f) { var dz = ent.rot * Mathf.Deg2Rad; var halfZ = dz * 0.5f; var sinz = Mathf.Sin(halfZ); var cosz = Mathf.Cos(halfZ); var num3 = sinz * 2f; var num6 = sinz * num3; var num12 = cosz * num3; for (int i = start, imax = verts.Count; i < imax; ++i) { var pos = verts[i]; pos = new Vector3((1f - num6) * pos.x - num12 * pos.y, num12 * pos.x + (1f - num6) * pos.y, pos.z); pos.x += ent.pos.x; pos.y += ent.pos.y; verts[i] = pos; } } else { for (int i = start, imax = verts.Count; i < imax; ++i) { var pos = verts[i]; pos.x += ent.pos.x; pos.y += ent.pos.y; verts[i] = pos; } } } mSprite = null; if (onPostFill != null) onPostFill(this, offset, verts, uvs, cols); } /// /// Add a new sprite entry to the collection. /// public void Add (object obj, string spriteName, Vector2 pos, float width, float height) { AddSprite(obj, spriteName, pos, width, height, new Color32(255, 255, 255, 255), new Vector2(0.5f, 0.5f)); } /// /// Add a new sprite entry to the collection. /// public void Add (object obj, string spriteName, Vector2 pos, float width, float height, Color32 color) { AddSprite(obj, spriteName, pos, width, height, color, new Vector2(0.5f, 0.5f)); } /// /// Add a new sprite entry to the collection. /// public void AddSprite (object id, string spriteName, Vector2 pos, float width, float height, Color32 color, Vector2 pivot, float rot = 0f, Type type = Type.Simple, Flip flip = Flip.Nothing, bool enabled = true) { if (mAtlas == null) { Debug.LogError("Atlas must be assigned first"); return; } var sprite = new Sprite(); var ia = atlas; if (ia != null) sprite.sprite = ia.GetSprite(spriteName); if (sprite.sprite == null) return; sprite.pos = pos; sprite.rot = rot; sprite.width = width; sprite.height = height; sprite.color = color; sprite.pivot = pivot; sprite.type = type; sprite.flip = flip; sprite.enabled = enabled; mSprites[id] = sprite; if (enabled && !mChanged) MarkAsChanged(); } /// /// Retrieve an existing sprite. /// public Sprite? GetSprite (object id) { Sprite sp; if (mSprites.TryGetValue(id, out sp)) return sp; return null; } /// /// Remove a previously added sprite. /// public bool RemoveSprite (object id) { if (mSprites.Remove(id)) { if (!mChanged) MarkAsChanged(); return true; } return false; } /// /// Update the specified sprite. /// public bool SetSprite (object id, Sprite sp) { mSprites[id] = sp; if (!mChanged) MarkAsChanged(); return true; } /// /// Clear all sprite entries. /// [ContextMenu("Clear")] public void Clear () { if (mSprites.Count != 0) { mSprites.Clear(); MarkAsChanged(); } } /// /// Returns whether the specified sprite is present and is visible. /// public bool IsActive (object id) { Sprite sp; if (mSprites.TryGetValue(id, out sp)) return sp.enabled; return false; } /// /// Set the specified sprite's enabled state. /// public bool SetActive (object id, bool visible) { Sprite sp; if (mSprites.TryGetValue(id, out sp)) { if (sp.enabled != visible) { sp.enabled = visible; mSprites[id] = sp; if (!mChanged) MarkAsChanged(); } return true; } return false; } /// /// Set the sprite's position. /// public bool SetPosition (object id, Vector2 pos, bool visible = true) { Sprite sp; if (mSprites.TryGetValue(id, out sp)) { if (sp.pos != pos) { sp.pos = pos; sp.enabled = visible; mSprites[id] = sp; if (!mChanged) MarkAsChanged(); } else if (sp.enabled != visible) { sp.enabled = visible; mSprites[id] = sp; if (!mChanged) MarkAsChanged(); } return true; } return false; } #region Event handling public delegate void OnHoverCB (object obj, bool isOver); public delegate void OnPressCB (object obj, bool isPressed); public delegate void OnClickCB (object obj); public delegate void OnDragCB (object obj, Vector2 delta); public delegate void OnTooltipCB (object obj, bool show); public OnHoverCB onHover; public OnPressCB onPress; public OnClickCB onClick; public OnDragCB onDrag; public OnTooltipCB onTooltip; [System.NonSerialized] object mLastHover; [System.NonSerialized] object mLastPress; [System.NonSerialized] object mLastTooltip; static Vector2 Rotate (Vector2 pos, float rot) { var dz = rot * Mathf.Deg2Rad; var halfZ = dz * 0.5f; var sinz = Mathf.Sin(halfZ); var cosz = Mathf.Cos(halfZ); var num3 = sinz * 2f; var num6 = sinz * num3; var num12 = cosz * num3; return new Vector2((1f - num6) * pos.x - num12 * pos.y, num12 * pos.x + (1f - num6) * pos.y); } /// /// Return the sprite underneath the current event position. /// public object GetCurrentSpriteID () { return GetCurrentSpriteID(UICamera.lastWorldPosition); } /// /// Return the sprite underneath the current event position. /// public Sprite? GetCurrentSprite () { return GetCurrentSprite(UICamera.lastWorldPosition); } /// /// Return the sprite underneath the specified world position. /// public object GetCurrentSpriteID (Vector3 worldPos) { var pos = (Vector2)mTrans.InverseTransformPoint(worldPos); foreach (var pair in mSprites) { var ent = pair.Value; var v = pos - ent.pos; if (ent.rot != 0f) v = Rotate(v, -ent.rot); var dims = ent.GetDrawingDimensions(pixelSize); if (v.x < dims.x) continue; if (v.y < dims.y) continue; if (v.x > dims.z) continue; if (v.y > dims.w) continue; return pair.Key; } return null; } /// /// Return the sprite underneath the specified world position. /// public Sprite? GetCurrentSprite (Vector3 worldPos) { var pos = (Vector2)mTrans.InverseTransformPoint(worldPos); foreach (var pair in mSprites) { var ent = pair.Value; var v = pos - ent.pos; if (ent.rot != 0f) v = Rotate(v, -ent.rot); var dims = ent.GetDrawingDimensions(pixelSize); if (v.x < dims.x) continue; if (v.y < dims.y) continue; if (v.x > dims.z) continue; if (v.y > dims.w) continue; return pair.Value; } return null; } protected void OnClick () { if (onClick != null) { var sp = GetCurrentSpriteID(); if (sp != null) onClick(sp); } } protected void OnPress (bool isPressed) { if (onPress != null) { // Only support one event source at a time (no multi touch) if (isPressed && mLastPress != null) return; if (isPressed) { mLastPress = GetCurrentSpriteID(); if (mLastPress != null) onPress(mLastPress, true); } else if (mLastPress != null) { onPress(mLastPress, false); mLastPress = null; } } } protected void OnHover (bool isOver) { if (onHover != null) { if (isOver) { UICamera.onMouseMove += OnMove; OnMove(Vector2.zero); } else UICamera.onMouseMove -= OnMove; } } protected void OnMove (Vector2 delta) { if (!this || onHover == null) return; var sp = GetCurrentSpriteID(); if (mLastHover != sp) { if (mLastHover != null) onHover(mLastHover, false); mLastHover = sp; if (mLastHover != null) onHover(mLastHover, true); } } protected void OnDrag (Vector2 delta) { if (onDrag != null && mLastPress != null) onDrag(mLastPress, delta); } protected void OnTooltip (bool show) { if (onTooltip != null) { if (show) { if (mLastTooltip != null) onTooltip(mLastTooltip, false); mLastTooltip = GetCurrentSpriteID(); if (mLastTooltip != null) onTooltip(mLastTooltip, true); } else { onTooltip(mLastTooltip, false); mLastTooltip = null; } } } #endregion }