Search Unity

TextMesh Pro Support custom TMP_SpriteAsset loading callback?

Discussion in 'UGUI & TextMesh Pro' started by jayatubi, Feb 12, 2019.

  1. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    Could the TMP_Text provide an interface which I could specify a custom TMP_SpriteAsset loading callback so I could load my sprites from external sources such as assetbundles?

    For example:

    Code (csharp):
    1.  
    2. TMP_Text.SetSpriteAssetLoadingCallback((atlasname,spritename)=>
    3. {
    4.     return MyAssetManager.LoadSpriteFromAssetBundle(atlasname, spritename);
    5. });
    6.  
     
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    You can already assign a Default Sprite Asset in the TMP Settings. You can also assign a Sprite Asset per text object as well. As such when your bundle is loaded, you could assign the appropriate Sprite Asset at that time.

    Edit - The defaultSpriteAsset property in the TMP Settings is read-only right now but that can easily be changed if necessary.

    For testing, you can make those changes on your end in the TMP_Settings.cs file.
     
  3. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    The sprite assets may be multiple. I need to know the content within the <sprite> tag to identify the demanded assetbundle. That is not easy for me to append the default/fallback asset after each text object initialized. It would be better if I could directly alter the loading process if there would be a custom callback.

    Furthermore, if a text reference two different sprite assets such as
    <sprite=atlas1 name="sprite1"> stay with <sprite=atlas2 name="sprite2">
    , I think that is not possible to handle these two assets with a single one default/fallback asset.
     
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Would it make sense for these sprites to be given a unique name where they would be referenced by name instead?

    For instance, "<sprite name="Atlas1_sprite1> stay with <sprite name="Atlas2_sprite2">".

    As these new sprite assets are loaded from bundles, you would assign them to the fallback list of the default sprite asset thus making it possible for them to be found using the tag example above.

    You could also assign unique unicode values to each of those sprites and then reference them in the text using UTF16 or UTF32 character escape sequence. "This is \U0001F600 stay with \u0600 ..."
     
  5. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    If this could be added:
    Code (CSharp):
    1. // Load Sprite Asset
    2. if (tempSpriteAsset == null)
    3. {
    4.     tempSpriteAsset = Resources.Load<TMP_SpriteAsset>(TMP_Settings.defaultSpriteAssetPath + new string(m_htmlTag, m_xmlAttribute[0].valueStartIndex, m_xmlAttribute[0].valueLength));
    5. }
    6.  
    7. /* CHANGES BEGIN */
    8. if (tempSpriteAsset == null && externalLoadingCallback != null)
    9. {
    10.     tempSpriteAsset = externalLoadingCallback(new string(m_htmlTag, m_xmlAttribute[0].valueStartIndex, m_xmlAttribute[0].valueLength));
    11. }
    12. /* CHANGES END */
    13.  
    14. if (tempSpriteAsset == null)
    15.     return false;
    And when the game initializes:
    Code (CSharp):
    1. TMP_Text.externalLoadingCallback = (spriteAssetName) =>
    2. {
    3.       return MyAssetManager.LoadSpriteAsset(spriteAssetName);
    4. };
    It would perfect for me to load sprite asset from anywhere.
     
  6. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    The number of sprites in my project may be huge. Thus it is not possible to put all the sprites into one same texture. Them need to be split into different textures.
     
  7. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Using a name or unicode makes it possible to have these sprites in any number of sprite assets.

    You would simply add each new sprite asset in the list of fallback of the default sprite asset.

    P.S. Not opposed to adding some callback but I believe it might be easier to use unique names or Unicode values to reference these sprites no matter in which sprite asset they are located as long as these sprite assets are assigned as fallbacks.
     
    Last edited: Feb 12, 2019
  8. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    That sounds great but I have no idea about the mechanism yet. Could you please share a link about this?
     
  9. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Creating an example to post... give me a few minutes.
     
  10. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    Do you mean to dynamic create a new sprite asset at runtime and then add all the sprites from external sources?

    If so which could be a solution but may not be acceptable for my project. The reason is to preload all the sprite assets could cause a lot of memory usage. I just want to load the sprite on demand. My asset manager handles the asset reference and it could release unused assets when necessary.
     
  11. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Your sprite assets would still be loaded on demand. Once loaded, you would dynamically assign them as fallback to the default sprite asset or to some other sprite asset already referenced by the default sprite asset.

    Using the following raw text produces:

    "- You attack with your <sprite name="Wooden Shield> for <sprite name="Five"> <sprite name="Heart>!
    - You earn 10 <sprite name="Coin> <sprite=0>"

    upload_2019-2-11_20-8-48.png

    Two sprite assets are assigned to the default sprite asset as seen below.

    upload_2019-2-11_20-10-8.png

    This second "DropCap Numbers" sprite asset could have been assigned to the first "Game Icons #1" sprite asset which would have still resulted in its sprites being found in the fallback chain.

    Here is this "Game Icons #1" sprite asset where you can see the names of the sprites.

    upload_2019-2-11_20-13-48.png


    How these sprite assets are structured and reference each other is up to you.

    When a sprite with name is requested, TMP will first search the Sprite Asset assigned to the text object (if any) and recursively through all of its potential fallbacks as well starting with the first before moving on to the 2nd. Then if the sprite with name is still not found, it search the sprite asset assigned as default in the TMP Settings and in the same recursive fashion all of its potential fallbacks as well.

    You could load / assign sprite assets individually or even load a bundle that contains several sprite assets already pre-configured. Again how you structure these fallback references is entirely up to you.
     
    Whatever560 likes this.
  12. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    Wow, great example. Thanks very much!

    However, I found the fallback assets would involve the dependency to the sprite textures. How could the fallback to be loaded on demand?
     
  13. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    You are most welcome.

    Give the above some thoughts but also know that I will also give thoughts to adding a potential callback as you suggested for both font and sprite assets.
     
  14. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    Glad to see that.
     
  15. jayatubi

    jayatubi

    Joined:
    Dec 9, 2013
    Posts:
    143
    A little requirement. If the callback could be specified it would be better to pass both the asset name and the text component. In my particular case I need to know which object is using this sprite so I could keep a trace to the resource usage.
     
  16. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    Hi @Stephan_B ! Thanks for your solution with sprite fallback, though it does not seems to work in my setup and can't figure why :

    this works `<sprite name="Symbols_0">`

    This works : `<sprite="Skills1" name="10001">`

    this doesn't work (not interpreted) `<sprite name="10001">`

    Here are screens of the corresponding setup

    upload_2019-2-12_18-51-25.png
     
    Last edited: Feb 13, 2019
  17. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I think the issue is simply related to the use of digits. In order to avoid allocations, a hashcode value is computed for the name of the sprites and using 10001, likely causes some issues. But I'll try to take a closer look tomorrow.
     
  18. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    Thanks for the feedback, I'll avoid pure digits in the future, though I tried changing the names to "Skill_XXXXX" to try avoiding the issue you pointed out. It seems that the behaviour does not change (also open/closing unity seems to update things in the assets)
     
  19. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I just tried this on my end and it worked as intended.

    I would suggest (for testing) to create a new project and install the latest release / preview of TMP which is version 1.4.0-preview.1b and see if you get the same behavior.[/QUOTE]
     
  20. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    I'm still using unity 2017 LTS. I'll try to reinstall the latest TMP Pro for 2017.3 to see if there is any improvement

    [ EDIT FIXED ] : I did the following :
    - Deleted all previous Text Mesh pro files (including my settings file)
    - Reimported latest 2017.3 package ( TextMesh Pro - Release 1.2.2-2017.3.unitypackage )
    - Let the original "Sprite Assets" folder fallback name alike, just in case it was hard coded somewhere in this version.
    - It works :), thanks for your time @Stephan_B
     
    Last edited: Feb 14, 2019
  21. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Glad that worked.

    BTW: A lot of changes / fixes have gone in to the package manager releases of TMP for Unity 2018.1 and especially the release 2018.3 with Dynamic SDF support. So the older version(s) could still contains issues related to some of that.
     
  22. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    Hi @Stephan_B, I'm reopening this issue as my concern is quite related. I previously spoted that unknown sprite names are not interpreted by TMP and the text is let as-is. I feel its a good default behaviour to debug.

    However in production it would be preferable to have a real sprite fallback, when unknown names are encountered, how could we achieve this?

    [Edit]
    There is a " Replacement character" for dynamic font system. Could there be a Replacement Sprite, empty by default to keep the behaviour, that could be set PER sprite asset?

    Thanks again,
    Best
     
    Last edited: Apr 1, 2020
  23. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Good suggestion. Let me play around with this and see what I can come up with.
     
  24. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    Glad to hear it. Keep us posted, I'd do the testing right away. BTW we migrated to latest 2019.3 from our 2017 LTS
     
  25. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I'll be sure to provide the update in this post.

    P.S. Be sure to use the latest release of TMP for Unity 2019.x which is current 2.1.0-preview.8. Preview 9 should be out within the next 7 days or less.

    P.S.S. Lots of changes between TMP pre package manager and now. Hopefully you already went through the migration which isn't fun but all better afterwards. Be sure to ready the sticky post at the top related to this if you have not already and follow those directions.
     
  26. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    Yes we went through all of this some times ago and everything is ok know. Thanks for the thorough guidelines you gave in the according post.
    If you want a feedback, as we have a very large project, we used a bit of the automated tools within Unity, but replacing through search&replace in a text editor all the script references in the meta files was safer & git friendly.
    However the automated tools is probably the best for most small projects and code allergic game makers.

    Right now we are on the 2.0.1 from May 2019. We'll try the upgrade, sure the changelogs appear huge ;)
     
  27. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Added support for defining a missing sprite character in the TMP Settings.

    This will require that you assign the specific unicode to one of your sprites in the relevant sprite asset. The Unicode Standards state that Unicode = 0 should be used for the .notdef / missing character.

    This new functionality will only work when referencing the sprite via the name attribute such as <sprite name="Some Name"> where in the event "Some Name" is not found, the missing sprite character will be displayed instead.

    This new functionality is not currently available when referencing a sprite by index. This is something that I will be adding in the future.

    I have edited the emojiOne example / includes sprite asset to provide an example of this as seen below

    upload_2020-4-6_16-29-33.png

    Given my lack of artistic skills, I strongly recommend you design your own missing sprite character ;)

    This new functionality will be in Preview 9.
     
    Whatever560 likes this.
  28. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    Awesome Stephan,
    Many thanks for this functionality. We'll use it right away
     
  29. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    513
    It works perfectly well
     
  30. fwalker

    fwalker

    Joined:
    Feb 5, 2013
    Posts:
    255
    Stephan,
    I have a situation where if we have a missing sprite we would like to default to a string instead. That's not currently possible with this system, is it?
    Any suggestion on how I can add support for this?
     
  31. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Not automatically. You would need to manually check if the sprite asset contains the requested sprite and if not present then replace it by the string.

    What is the use case for this? Do you have an example of what the missing sprite would get replaced by?
     
  32. BergOnTheJob

    BergOnTheJob

    Joined:
    Oct 9, 2019
    Posts:
    10
    It would be very helpful if the emoji typed from a device's native keyboard could recognize this "Missing Sprite Unicode" feature. In my current Android and iOS builds, missing emoji still get the default empty character squares.

    My project is using Unity 2019.4.23f1 with TMP 2.1.1
     
  33. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    In version 2.1.6, missing Emojis will get the sprite described in my previous post above.

    What do you mean? Do you mean show the missing emoji on the native keyboard?
     
  34. BergOnTheJob

    BergOnTheJob

    Joined:
    Oct 9, 2019
    Posts:
    10
    I mean, even from the default EmojiOne setup, and from my attempt to setup my own SpriteAsset, I cannot get Emoji that are not included in the sprite sheet to show up as the designated missing sprite character when I choose one of these missing emoji from a mobile device's native keyboard. Instead I get the white square outline glyph for a missing text character. This is on Android and iOS builds.

    Edit:
    The only way I can find that obeys the Missing Sprite Character on android or iOS device is if I actually type in the TMPro field, <sprite name="[glyph name]">, then if the name is not found it will throw up the missing sprite character. But simply choosing the emoji from the keyboard throws up the white square outline missing text character.
     
    Last edited: Aug 6, 2021
  35. BergOnTheJob

    BergOnTheJob

    Joined:
    Oct 9, 2019
    Posts:
    10
    Also, I'm not allowed to type a letter in the Missing Sprite Unicode field in the inspector. So the way unicodes are given by standard, and even saved in the glyph, like "1f0cf" can't be used. Instead you must put the decimal equivalent, 127183. That is very unintuitive. :(
     
  36. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Just to make sure I understand correctly.

    You want to display the missing sprite glyph .notdef when a user types an emoji on some device native keyboard when it is missing?

    The challenge with the above is that emojis in this case are referenced by Unicode code point where as such, it is more difficult to identify them as an emoji.

    Having said that, I could add additional filtering as Emojis live in specific Unicode Code Point ranges where if the requested Unicode character happens to be in that range, I could try to display the missing sprite glyph instead. A potential complication with that is that Emojis can exist in font file where in this case, displaying the missing square would most likely be more appropriate.

    Can you provide more insight on the use case for this (again just so I understand clearly the goal of this feature)?
     
  37. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I will consider changing both the Missing Character and Sprite Unicode field use hex values.
     
    BergOnTheJob likes this.
  38. BergOnTheJob

    BergOnTheJob

    Joined:
    Oct 9, 2019
    Posts:
    10
    That is correct.


    Sure, I'm creating a game that has a chat feature. We want the player to be able to type emoji in the chat from the mobile devices native keyboard. We don't want the white square to show up if we haven't included a particular emoji. We actually need it to not display anything, so I am trying to use the missing character feature to catch this, and will hopefully be piping in a zero'ed out glyph for a "nothing" sprite that takes up no space.
     
  39. Deedlithh

    Deedlithh

    Joined:
    Dec 21, 2015
    Posts:
    8

    Hi Stephan_B,

    You said that it can be easy to change the property of defaultSpriteAsset, how ?

    I explain my problem :
    I use 1 asset by platforms because the icons is different on each.
    So i have this :
    upload_2021-9-23_15-43-10.png
    When i switch platform, i want to do this on the buildConfig for change the defaultSpriteAsset on the TMP Settings :

    (Example for Switch platform)
    Code (CSharp):
    1. TMP_SpriteAsset spriteAsset = AssetDatabase.LoadAssetAtPath(pathAsset + "Switch/ControllerAssets.asset", typeof(TMP_SpriteAsset)) as TMP_SpriteAsset;
    2. TMP_Settings.defaultSpriteAsset = spriteAsset;
    But for now it's impossible because it's read-only.

    Do you have a solution ?
    Thank you.
     
  40. tweedie

    tweedie

    Joined:
    Apr 24, 2013
    Posts:
    311
    Can I ask why there isn't just a
    TMP.LoadSpriteAsset(SpriteAsset asset)
    method?

    Ultimately, the ideal thing would just be having the ability to load additional SpriteAssets when we like, and that's not complicated; we're just adding to the existing list.

    Between AssetBundles, DLC and mods, there are plenty of use cases where appending a new, separate SpriteAsset would be better than adding a fallback, because we might be calling code that knows ahead of time what SpriteAsset an icon is in, so it doesn't make sense to "fall back" through the existing atlases. It's also just more in-line with how I'd expect an API to work, personally.

    Also, crucially, assigning fallbacks to existing SpriteAssets at runtime modifies assets in the project, which persist in edit mode and could accidentally create dependencies between asset bundles. (I would really prefer to not be at all dependent on Resources, and load things myself, which means I could also load runtime-only duplicates of the editor assets).

    As such, a simple entry point into the API like this would be very valuable. I will also take the opportunity to say that a similar way to simply construct a SpriteAsset at runtime would be very valuable; e.g. if you pack an atlas dynamically at runtime for a user's configurable key / controller bindings.
     
    Last edited: May 18, 2022
    ko0zi and nurf like this.