Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Topics - AtomicBob

Pages: [1] 2
NGUI 3 Support / Multiple import when modifying an atlas
« on: March 24, 2016, 04:21:03 PM »
I'm in the process of fixing some paint points for our artists and a few of the issues revolve around updating the NGUI atlas. We've started using crunch compression for our atlas because the quality is acceptable and the size is excellent (2 MB for a 4k atlas, versus 64 MB truecolor and 16 MB compressed). However, the crunch process takes a while, about 80 seconds on some machines.

The problem is that this longer import time is greatly exacerbated by NGUI. When deleting a sprite from the atlas, for example, the atlas is reimported 5 times. I looked into a bit and it looks like the general flow is:
  • Make atlas readable and extract sprites
  • Make atlas non-readable
  • Make atlas readable so it can be encoded to PNG
  • Overwrite the atlas file on disk and refresh assets
  • Make atlas non-readable

It seems only one of these reimports is actually necessary. Do you think you'd be able to optimize this? It appears you can work with raw texture data without marking the texture readable. If so, you can use GetRawTextureData, create a temp texture in memory, and LoadRawTextureData to get the atlas in memory for modifications. Then overwrite the file on disk (which shouldn't require the original file being readable either), and reimport once at the very end.

  1. Importing 'Assets/GUI/Atlas/GUIAtlas.png'
  2. 0. AtlasProcessor.OnPreprocessTexture() at Assets/GUI/Editor/AtlasProcessor.cs:10
  3. 1. UnityEditor.AssetDatabase.ImportAsset()
  4. 2. NGUIEditorTools.MakeTextureReadable() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:457
  5. 3. NGUIEditorTools.ImportTexture() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:512
  6. 4. NGUIEditorTools.ImportTexture() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:532
  7. 5. UIAtlasMaker.ExtractSprites() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:493
  8. 6. UIAtlasMaker.OnGUI() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:1013
  10. Importing 'Assets/GUI/Atlas/GUIAtlas.png'
  11. 0. AtlasProcessor.OnPreprocessTexture() at Assets/GUI/Editor/AtlasProcessor.cs:10
  12. 1. UnityEditor.AssetDatabase.ImportAsset()
  13. 2. NGUIEditorTools.MakeTextureAnAtlas() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:499
  14. 3. NGUIEditorTools.ImportTexture() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:513
  15. 4. NGUIEditorTools.ImportTexture() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:532
  16. 5. UIAtlasMaker.ExtractSprites() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:530
  17. 6. UIAtlasMaker.OnGUI() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:1013
  19. Importing 'Assets/GUI/Atlas/GUIAtlas.png'
  20. 0. AtlasProcessor.OnPreprocessTexture() at Assets/GUI/Editor/AtlasProcessor.cs:10
  21. 1. UnityEditor.AssetDatabase.ImportAsset()
  22. 2. NGUIEditorTools.MakeTextureReadable() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:457
  23. 3. NGUIEditorTools.ImportTexture() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:512
  24. 4. UIAtlasMaker.UpdateTexture() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:570
  25. 5. UIAtlasMaker.UpdateAtlas() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:689
  26. 6. UIAtlasMaker.OnGUI() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:1027
  28. Importing 'Assets/GUI/Atlas/GUIAtlas.png'
  29. 0. AtlasProcessor.OnPreprocessTexture() at Assets/GUI/Editor/AtlasProcessor.cs:10
  30. 1. UnityEditor.AssetDatabase.Refresh()
  31. 2. UIAtlasMaker.UpdateTexture() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:582
  32. 3. UIAtlasMaker.UpdateAtlas() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:689
  33. 4. UIAtlasMaker.OnGUI() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:1027
  35. Importing 'Assets/GUI/Atlas/GUIAtlas.png'
  36. 0. AtlasProcessor.OnPreprocessTexture() at Assets/GUI/Editor/AtlasProcessor.cs:10
  37. 1. UnityEditor.AssetDatabase.ImportAsset()
  38. 2. NGUIEditorTools.MakeTextureAnAtlas() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:499
  39. 3. NGUIEditorTools.ImportTexture() at Assets/Plugins/NGUI/Scripts/Editor/NGUIEditorTools.cs:513
  40. 4. UIAtlasMaker.UpdateTexture() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:583
  41. 5. UIAtlasMaker.UpdateAtlas() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:689
  42. 6. UIAtlasMaker.OnGUI() at Assets/Plugins/NGUI/Scripts/Editor/UIAtlasMaker.cs:1027

NGUI 3 Support / Event Delegate Bug
« on: July 25, 2014, 12:31:21 PM »
I have a method Show ( Action onHide = null ) that I call using a UIButton's OnClick delegate. I don't specify any parameter and let the Event Delegate pass null. This works in the first scene that's loaded, but not in subsequent scenes. It looks to be a bug/side-effect of the serialize-deserialize process Unity goes through when changing scenes. The mParameters array has a single element in my case with a value of null and a type of void. The serialization process ends up converting the null element to UnityEngine.Object instead of void. This causes the next invocation to throw an InvalidParameterException.

Reproduction should be very straightforward:
1) Assign a UIButton's OnClick to a method that takes a reference type parameter that doesn't derive from UnityEngine.Object
2) Play a scene and call DontDestroyOnLoad on the button's GameObject
3) Click the button in your first scene (so the EventDelegate caches stuff)
4) Move to another scene
5) Click the button again in the new scene
6) Exception

NGUI 3 Support / Feature Requests
« on: July 08, 2014, 06:11:41 PM »
While working on a new GUI system for a game, I came across a few things that I thought worthy of mentioning.

  • Support for 'sticky selection' - I have a GUI system that will support a mouse and keyboard and controllers simultaneously. It ended up being problematic that you could potentially clear the current selection with the mouse (e.g. by clicking nothing) and then try to use the controller. With nothing selected UIKeyNavigation doesn't work. Maintaining a selection at all times didn't end up being too hard, but in order to keep things visually consistent I wanted to make sure the selected object is always highlighted as well. I ended up getting this working quite well, but it was a pain in the ass and I had to write a few ugly hacks to do it. Maybe there's already an easy way to do this, but I wasn't able to find it.

  • The ability to treat axes more like buttons for UICamera Vertical and Horizontal. In UICamera, when the vertical and horizontal axes are read there is a hard-coded delay of .25 seconds between sending OnKey events. This works great for holding something down, but the delay is very noticeable if you try to do something like tap down on the dpad repeatedly. The way I solved this is by setting the gravity on an axis to match the sensitivity so the axis value will always go back to zero in a single frame. This allows me to do a simple sign check on the axis to see if it has been pressed this frame. Alternatively, I think a better way would be to watch the axis value and just reset the timer each time it moves closer to 0. This means the user has released the button/axis and the next time it moves away from zero is a new press.

  • Support for non-trigger colliders. I'm integrating GUI into an existing game and the other devs are adamant that 'raycasts hit triggers' stay disabled. NGUI seems to work just fine when I change all my colliders to not be triggers. If there's no other issues involved, this may be as simple as setting auto-generated colliders to match the 'raycasts hit triggers' setting (such as the colliders created for UIPopupLists).

  • OnKey repeating. If I hold the left arrow with a slider selected, it would be nice if it continued to move.

  • Add the rest of the possible notifications to UIForwardEvents. I know it's marked as legacy, but it still provides functionality not found elsewhere (Event Triggers don't forward arguments, and don't call private methods the way UICamera.Notify/SendMessage does). It's missing OnKey, OnDragOver, OnDragOut, OnDragStart, OnDragEnd, and OnTooltip.

  • Ability to set the OnKey step size for UISliders. It's currently hardcoded to .125. I'd like to be able to fine tune it on a per-slider basis.

  • UIKeyNavigation should chain when disabled. Currently, they kind of break if you have dynamically enabled elements and overrides. For example, I have a horizontal skill tree and an 'Apply' button below the tree. Intuitively, I expect that pressing down enough times will take me to the Apply button. The problem is that relative to the leftmost and rightmost items in the skill tree, the Apply button is at an angle greater than 45 degrees, so pressing down won't reach it. So for the entire bottom row of skills, I have the down override set to the Apply button. When any of those bottom skills are disabled (locked, unreachable in the tree) you again can't reach the Apply button from the far left or right skills. It seems like a better algorithm for moving selection would try to move like it currently does, but keep track of disabled UIKeyNavigation if it would be reachable and closest, if no new selection is found to move to, use the disabled one and recalculate from there.

NGUI 3 Support / Small Feature Requests
« on: June 09, 2014, 02:48:59 PM »
Just a couple small requests:

1) Editor-only Anchors - I've been making better use of the anchoring system lately and I really like it. Once objects are set up properly, making layout changes is incredibly easy. The only thing I'd like to see different is the ability to completely disable anchoring at runtime. I know they can be set to "OnEnable", but I only use the anchoring in the Editor and I've actually had it cause some small problems with updating anchors OnEnable. Sometimes I want a objects to be positioned or sized based on other objects that they aren't truly 'anchored' to.

2) Automatically resized collider on scroll bar foreground - Currently, the foreground sprite on a scroll bar is 'clipped', not resized. This means the foreground collider is actually the entire scroll bar. It'd be better if the collider matched the sprite. It can be done manually, but it seems like a more intuitive default behavior.

NGUI 3 Support / On Screen Keyboard
« on: May 15, 2014, 09:59:44 AM »
I'm attempting to implement an on screen keyboard capable of leveraging NGUIs UIInput. Since NGUI uses the Unity Input class directly, it's difficult to send fake presses through SendMessage or reflection like I was hoping. It seems I'll have to fool Unity itself into handling keyboard events that I create in code.

Anyone have any clever ideas how I would go about doing that?

I'm attempting to move an object with several widgets on it to another panel temporarily at runtime. It seems like it should be very straightforward, but I can't get it to work. Here's my best guess at how it's supposed to work:

  1. widget.cachedTransform.parent = newPanel.cachedTransform;
  2. oldPanel.RemoveWidget(widget);
  3. newPanel.AddWidget(widget);

Even setting both panels as dirty doesn't work. Anyone know the right way to do this?

EDIT: It does work as expected if you disable the widget(s) before changing the parent, then reenable it afterward. Ditto with the panel's GameObject. I'm not sure this is the intended method, though.

NGUI 3 Support / 3.5.5 Issues with UIButton and UIButtonColor
« on: April 03, 2014, 05:46:43 PM »
Just upgraded to get around a bug with tweening (similar to and now I'm having a problem with UIButtons.

UIButtons have a nasty habit changing the sprite in unexpected ways. If you set the sprite.spriteName from code, it doesn't always take effect and sometimes it changes back and forth between sprites on mouseover. If you set button.normalSprite similar things happen. If you set both, it seems to work ok.

It should be pretty easy to reproduce, but if not I'll email a text case.

Also, it'd be nice to be able to use UIButton for the color and OnClick delegate without using 'image button' functionality at all. I know I could use EventTrigger and UIButtonColor separately, but it's nice to have it all on once script and EventTriggers are kinda large visually for just OnClick.

As far as I can tell, it's impossible to reset a tween after it has been played in reverse. The easiest way to explain is probably just a code example. I have a popup window that tweens in, waits for a couple seconds, and tweens back out (in reverse). If it's called while already active, it queues the new message and shows it after the current one finishes. If the user leaves the screen it's on, the queue is simply emptied. This means it's possible for the tween to have been disabled in some random state and needs to be reset to it's initial state before being shown.

  1. using System.Collections.Generic;
  2. using UnityEngine;
  4. public class PopupTest : MonoBehaviour
  5. {
  6.         #region Editor Interface
  8.         [SerializeField] private TweenScale tweener;
  9.         [SerializeField] private int secondsToDisplay;
  11.         #endregion
  13.         #region Private Fields
  15.         private static PopupTest instance;
  16.         private static Queue<string> queue = new Queue<string>();
  17.         private static bool isActive = false;
  19.         #endregion
  21.         #region MonoBehaviour Methods
  23.         private void Awake ()
  24.         {
  25.                 //Enforce the singleton pattern
  26.                 if ( instance != null )
  27.                 {
  28.                         Debug.LogError("Multiple instances of the " + GetType() + " created. There can be only one.");
  29.                 }
  30.                 instance = this;
  31.         }
  33.         private void OnDisable ()
  34.         {
  35.                 isActive = false;
  36.                 NGUITools.SetActiveChildren(gameObject, false);
  38.                 tweener.onFinished.Clear();
  39.                 tweener.enabled = false;
  40.                 queue.Clear();
  41.         }
  43.         private void OnDestroy ()
  44.         {
  45.                 instance = null;
  46.         }
  48.         #endregion
  50.         #region Public Interface
  52.         public static void Display ()
  53.         {
  54.                 if ( !instance.gameObject.activeInHierarchy ) return;
  56.                 queue.Enqueue("Test");
  58.                 if ( !isActive )
  59.                 {
  60.                         instance.Show();
  61.                 }
  62.         }
  64.         #endregion
  66.         #region Private Methods
  68.         private void Show ()
  69.         {
  70.                 queue.Dequeue();
  72.                 EventDelegate.Add(tweener.onFinished, Hide, true);
  73.                 tweener.delay = 0;
  75.                 tweener.Play(true);
  76.                 Debug.Log("Scale (play1):" + transform.localScale);
  77.                 tweener.ResetToBeginning();
  78.                 Debug.Log("Scale (reset):" + transform.localScale);
  79.                 tweener.Play(true);
  80.                 Debug.Log("Scale (play2):" + transform.localScale);
  82.                 isActive = true;
  83.                 NGUITools.SetActiveChildren(gameObject, true);
  84.         }
  86.         private void Hide ()
  87.         {
  88.                 EventDelegate.Add(tweener.onFinished, HideComplete, true);
  89.                 tweener.delay = secondsToDisplay;
  90.                 tweener.Play(false);
  91.         }
  93.         private void HideComplete ()
  94.         {
  95.                 isActive = false;
  96.                 NGUITools.SetActiveChildren(gameObject, false);
  98.                 if ( queue.Count > 0 )
  99.                 {
  100.                         Show();
  101.                 }
  102.         }
  104.         #endregion
  105. }

The issues are with Show(). Since the tween could have been disabled while tweening in reverse, I have to play it forward once otherwise ResetToBeginning actually resets it to the end. However, it still doesn't work. If the tween is disabled in the middle of the tween playing in reverse, I can't get it to reset back to the beginning and it will just appear instantly instead of tweening.

Incidentally, if you remove the first Play(true) from Show() the tween will throw an exception when a message is queued.

NGUI 3 Support / 3.4.9 ScrollView Issues
« on: February 13, 2014, 01:50:36 PM »
I dynamically populate a ScrollView at one point, and I noticed 2 oddities.

1) ResetPosition doesn't work if the object is disabled in the hierarchy. This is a problem for me since when I go to a new screen, I populate the ScrollView before I enabled the screen. I can work around this, but it'd be much more convenient to be able to reset scrolling position while the object is disabled.

2) In an attempt to work around 1) I noticed several methods throw exceptions if the ScrollView has never been enabled. For example, MoveAbsolute throws an exception because mTrans is null. MoveRelative throw one because panel is null.

When creating or enabling a UIWidget in a hierarchy that doesn't contain a UIRoot, it creates one and re-parents the hierarchy under it. This causes problems. We don't use a UIRoot because we do some more complicated scaling the results in pixel perfect UIs on a couple target devices, and then scales to fit based on both width and height on other devices. We also have a couple of stand alone colliders that intercept and process events. I've added UIWidgets to these colliders so it gets sorted correctly for events. The problem is, when the UIWidgets on these colliders are enabled (i.e. when the GameObject is enabled), they basically destroy hierarchy. There are two outcomes. 1) The widget doesn't find a UIRoot on the root object, so it creates a new UI and re-parents the current hierarchy under it. Problem is, the re-parenting resets all layers in the game to Default. We use a couple layers for masking events and this breaks all interaction. 2) The widget finds a disabled UIRoot on the root object and resets it's scale to 1,1,1 for whatever reason. Our custom scaling script was originally on the root object (effectively replacing UIRoot with a custom one) so this object has a specific scale set. Resetting the scale blows up the game.

I ended up setting up the hierarchy with a disabled dummy UIRoot as the root object, the GameScaler under that, then the UICamera under that. Like so:

  • Root (disabled UIRoot)
    • GameScaler (GameScaler)
      • Camera (UICamera)

And this doesn't break things.

NGUI 3 Support / 3.0.9 f4 UIWidget Not Updating BoxColliders
« on: January 22, 2014, 05:18:49 PM »
Title basically says it all. Add a UIWidget and a BoxCollider to a GameObject, check Auto Adjust To Match, and the BoxCollider never changes. I'm not sure if it's supposed to update the BoxCollider as the widget changes or when the game starts, but I can't seem to get it to update at all.

NGUI 3 Support / EventDelegate.Remove Failing
« on: January 22, 2014, 04:31:25 PM »
I've run into multiple circumstances now where EventDelegate.Remove fails. It returns false and the previously Added callback will continue to get called. I haven't had time to dig down and figure out the problem, but I've noticed the actual reference to the onFinished list has changed and is empty. Explicitly specifying that it's not a one-shot callback doesn't help.

A small code example:
  1. TweenPosition registrationTwn;
  2. List<EventDelegate> registrationRef;
  4. public void CancelButtonPressed ()
  5. {
  6.         isCanceled = true;
  8.         registrationTwn = tween;
  9.         registrationRef = tween.onFinished;
  10.         EventDelegate.Add(tween.onFinished, TweenOutFinished);
  11.         //EventDelegate.Add(tween.onFinished, TweenOutFinished, false); //Same results
  12.         tween.Play(false);
  13. }
  15. private void TweenOutFinished ()
  16. {
  17.         Debug.Log("tween Equal Original: " + ReferenceEquals(registrationTwn, tween)); //True
  18.         Debug.Log("onFinished Equal Original: " + ReferenceEquals(registrationRef, tween.onFinished)); //False
  20.         Debug.Log(
  21.         EventDelegate.Remove(tween.onFinished, TweenOutFinished) //Fails
  22.         );
  24.         UIManager.ToggleUIElement(UIElement.InputBlocker, false);
  26.         friendList.enabled = false;
  27.         root.SetActive(false);
  29.         if ( FriendsSelectedCallback != null )
  30.                 FriendsSelectedCallback(( isCanceled ? null : selected ));
  31. }

In this case, it would be sufficient to make it one-shot, but I'm running into the same problem in cases where that's not an option.

NGUI 3 Support / 3.0.9 f4 Problem With Colliders On Scroll Views
« on: January 22, 2014, 01:51:52 PM »
When creating scrolling lists, it's common to have 2 different types of colliders: ones on the list elements, if they are buttons, and a "catch-all" collider that allows the player to scroll from anywhere in the list, not just over the elements. With the recent changes, if the catch-all collider is on the parent object of the Scroll View it will overlap and block some of the element colliders. Some elements will be clickable and others will not.

This only occurs with the Camera Event Type set to UI. It appears to work perfectly with it set to World (presumably it respects z-value), but in this case list element colliders aren't clipped when scrolling so it's more work to go that route. For now, it's not difficult to work around this, but it's cleaner not to need a dedicated GameObject just for the collider when it can be put on the existing parent object.

This doesn't work (random elements won't be clickable):
  • Friends List (w/Collider)
    • Scroll View
      • Grid
        • Element
        • Element
        • Element
        • Element

But this does:
  • Friends List
    • Collider
    • Scroll View
      • Grid
        • Element
        • Element
        • Element
        • Element

I'm emailing a test project for this in case it's not very clear.

The new caret and highlighting on UILabels don't draw properly when the Render Q on a UIPanel is set to Explicit. This is because they use UITextures which creates a new draw call. The UITextures will have the same z-value as the rest of the widgets which causes z fighting. I suppose the easiest thing here is if the parent UIPanel is set to Explicit, provide an option for a manual z offset on the textures.

Two other less important things with UILabels: 1) Possibly provide an option to specify a sprite or texture to use (customization, plus makes it possible to use an atlased sprite instead of a texture). 2) The depth set on the UITextures only takes the depth of the UILabel into account and ignores other widgets. This isn't necessarily bad as long it's clear this is what happens, but it's currently not. Plus, I imagine the cost of creating a new draw call which will happen with Automatic UIPanels is greater than the cost of finding the maximum depth on the panel. Alternatively, an option to specify the depth that will be used along with the z-offset would do the trick.

NGUI 3 Support / 3.0.8 f7 UITexture Draw Order Oddity
« on: January 07, 2014, 04:08:03 PM »
When using an Explicit Render Q on UIPanels that have more than one draw call causes some strange behavior that could probably be considered a bug. In short, both depth and z-value have to be set for things to draw correctly.

I have a few UISprites and a UITexture that draws on top of the sprites. In the past, all I had to do was set the z-value for the UITexture to be above the UISprites. Now, if the UITexture is above the UISprites, but it's depth is lower than any of the UISprites, none of the UISprites with a depth higher than the UITexture will draw. This was pretty difficult to figure out as a bunch of seeming unrelated UISprites simply disappeared when upgrading.

Similarly, if the UITexture has a depth higher than the UISprites, but the same z-value, it simply doesn't draw. All the UISprites with lower depths will show up.

So, in order to get UISprites and UITextures on the same UIPanel to all draw properly, you have to position all the UITextures on a different Z and set the depth on all the UITextures higher than the UISprites.

It seems like the relative depth of widgets in different draw calls should be ignored.

Pages: [1] 2