Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics

Scarlet String Studios

A member registered Oct 13, 2015 · View creator page →

Creator of

Recent community posts

Poking around a bit more, I noticed a couple of related issues:

  • In Unity's built-in Localization package, the LocalizeStringEvent component updates its target string in OnEnable. That's another case where we're expecting the string to be rebuilt by an external source anyway, so we wouldn't need to double-rebuild it in STM's OnEnable.
  • If you use STMs in a vertical/horizontal layout group and check with the deep profiler, there are actually several calls to Rebuild whenever the layout group updates (e.g. LayoutGroup.SetChildAlongAxisWithScale will update the RectTransform's sizeDelta, anchorMin, and anchorMax, all of which are causing STM to rebuild).
    • Perhaps there's some way to delay the rebuild until after the canvases have updated? Not sure if that would affect some canvas calculations.
  • This might be more relevant to the other thread about SetMesh, but do you think it would be efficient to have UpdateMesh take masking into account? I think all of the active STMs with a gradient effect are being updated every frame, even when some are hidden by a parent's Mask component.

Ah, that's a good idea. Haven't experimented with PreParse before, so I'll take a look at that.

There could be other problems - e.g. in one situation, I was enabling and modifying an STM and rebuilding it at read position 0 and without autoread, so I could manually read it at a later frame.

I was able to work around that by not calling Rebuild myself, setting currentReadTime and autoRead separately, and allowing it to rebuild on enable. It worked in this case, but the downside is that I would be committing to setting autoRead to false, which may or may not be what I intended.

Generally, I think I was able to work around the situations that came up for this specific scene, but it feels like it could become awkward quite easily.

All right, good to hear. Yeah, looking at SpecialRebuild, I think it just called the main Rebuild in certain situations.

But... Would it be possible for you to do your own code, then enable SuperTextMesh after so it just naturally calls rebuild anyway? Would that work in this scenario?

Well... sort of, but it wouldn't work too well. E.g. in the scenario in my first post, the localization helper is actually on the STM object. So, my own code would be executing in OnEnable alongside STM's OnEnable, and the only workaround would be to fiddle with Script Execution Order. In this example, it would make more sense to just tick a box in the editor to say "don't auto-rebuild on enable," kinda like how there's a bool for remember read position and auto-read.

Hey - still continuing my optimization journey for consoles. I've noticed that I'm getting a lot of redundant calls to Rebuild, some of which can probably be avoided with a bool to determine whether Rebuild should be called in SuperTextMesh's OnEnable.

For example, I have a localization helper script that disables the "allow hyphens" tick box for Chinese/Japanese and re-enables it for other languages. It also applies changes like per-language font sizes (to help with sizing UI text). My localization helper performs those checks in OnEnable, and then it calls Rebuild at the end of that. But, oftentimes STM will have already called Rebuild in its own OnEnable, resulting in an extra call.

Do you think it's possible/reasonable to add a toggle for this? It seems like it could work as a simple bool, so you could toggle it by code if you're planning to manually call Rebuild that frame. However, I didn't look too closely at what SpecialRebuild does, so let me know if this is a bad idea.

Thank you! I'll try it out once the update is live.

That's probably what I'll end up doing, yeah. It's something that TextMeshPro seems to handle better, but I'm not sure how it works.

Yeah, the results in build seem to be the same as in editor.

Auto quality (40)

Manual quality (60)

Logically, it seems reasonable to decrease the quality to 40 for 1080p, but in practice I find that it looks blurry compared to 80 quality at 2160p. It's a bit hard to compare because one screenshot would be way larger than the other, but I'd say it looks closer to my second screenshot here (60 quality at 1080p).

Yeah, I suppose so! The old project page is still available here:

There are no public builds right now, but I might as well ship the full game to itch alongside other storefronts.

Interesting — the canvas is set to Screen Space - Camera. And the Canvas Scaler is set to Scale With Screen Size with a reference resolution of 3840x2160.

I never quite figured out how canvas scaling works, so I can see why it's a little confusing. To clarify my original post, those screenshots were taken in the editor with the Game view set to 1080p. I've noticed similar behavior in builds, but I figured I'd point that out, because I think some screen-related properties return different values in the editor (like Screen.width or one of those).

Here's something I've wondered about for a while — how does the Auto Quality setting determine the appropriate quality for a mesh? I've often found that the result is blurry compared to setting the quality myself.

Here's an example at 1080p, with a Canvas that's configured for 2160p.

Quality: 80 (manual)

Quality: 40 (auto) (top only)

In the second pic, the top text box is set to Auto Quality while the bottom is manually set to 80. The top gets auto-set to 40 quality, and if you view it at 100% resolution, you can see that it's a little blurry.

Here's TMP for comparison:

Now, TMP might not be a good point of reference, because the whole system seems to be different (there's no quality setting). But I think it looks like the best of the three (it might be similar to 70-ish quality).

Anyway, I could probably work around this by having a script set the quality to "auto * 1.5" or something like that, but I'm wondering if I'm setting this up incorrectly, or if I should be using some other method to determine quality.

Makes sense, sounds like that would solve it.

Yeah, I noticed that there was a built-in ScriptableObject called "comma", and I know there's a note somewhere in the documentation about using special names for certain chars. I was actually able to create a ScriptableObject named ",", so I guess it's not an invalid filename? But either way, I think the second issue about the spaces would have prevented it from working regardless.

Hope you can get it fixed, because I think this problem would apply to any language that doesn't have spaces (so at least Chinese and Japanese).

I'm testing our Chinese localization, and I'm trying to figure out if the auto-delays are working. I created one for the "," character and set it to 10 just to test, but it seems like it's still being read like a normal char.

Thinking about it now, maybe this is because there aren't any spaces in Chinese? An example sentence would be something like 星星都离我们很远,冥王星已经算近的了。 As you can see, there are no spaces between the comma character and the letters.

Do you think there can be an option to allow delays to work regardless of spaces? This would be useful for punctuation because you'd typically want to pause after any comma, period, etc., as long as it isn't the last char of the whole string.

Hey, thanks for the detailed reply. I guess that makes sense — yeah, with a quick test, I can see that CreateMesh is called when I have an STM component with a <j> tag, and it stops if I remove the tag. I think the reason the non-animated text was taking 1ms for me was because I still had a custom <w> somewhere in there (so it was still calling SetMesh).

Anyway, I guess I'll leave this be for now. Those optimizations would be great, if you're able to figure it out. So far, because this is just UI text, the frame drops don't seem to be a huge deal, but I'll see if it becomes an issue in other situations later on.

I'm working on a Switch port of my game, so I've been paying more attention to the profiler recently. I noticed that SetMesh gets called every frame, and there's a comment at like 1761 saying "TODO: make this only get called if something changed, or it's animating".

Figured I might as well ask: were you able to make progress on this? It seems like it would have a big impact on performance in some situations.

In this case, I have an ASCII art with a custom <c> gradient that's using a lot of CPU. In the editor, STM's Update method is eating up 2.5ms (sounds like a small number, but it drops the framerate on the Switch to 30 fps). Even with the ASCII disabled, the rest of the (non-animated) text is still taking 1ms on my desktop computer.

Now, the rest of the screen is static while this is happening, so the frame drops aren't super noticeable. But, it would be great if there were some optimization that could be done here, especially if calling SetMesh in Update is not really needed.

Hey - yep, what's up?

Actually, thanks! That's a good suggestion with <t=Infinity> — seems to do the same thing that I was trying to do with the <d=999>, except it's actually infinite and not just effectively infinite. I just tested it, and it's working for me.

Thanks for the suggestion. Yeah, it's a player-controlled situation (waiting for input).

I can see how that would work, but... it's quite a bit of a hack to add/remove those tags and rebuild the mesh, especially with multiple other tags in the way, and multiple pauses.

That said, it seems to work well enough if I just add a <d=99999>, and then remove it when the player presses a button (using an event to detect when we've reached this pause, of course).

What's the max value for a delay? This seems like it would work for my purposes - even if the text auto-advanced after a minute or so, it wouldn't be game-breaking; the pause is only there for drama anyway.


Ran into a problem when using <pause> tags with fully centered alignment (Anchor: Middle and Alignment: Center).

If you create a long string and put a <pause> in the middle, the bounds of the mesh will initially be based on the first half of the string, not the full string, and the text is centered accordingly. After calling Continue(), the bounds seem to expand to fit the full text, and the text jumps to its new location after being centered. I think this is expressed by the purple box in Scene view.

Presumably, the intended behavior is to account for the full, final size of the text box when determining the centered position, right? This way, the <pause> tags are invisible to the player, and the text simply continues after calling Continue(), instead of visibly moving upward to make space for the newly added text.

Btw, I considered working around this with a custom event, but I couldn't find any other way to pause a mesh from reading out aside from using <pause>.

Replying immediately, because I think that you may get the result you want right away by using the monospace numerals from japanese without any problems (0123...)

Oh! Hey, you're right. That does solve it. But yeah, it's worth solving for the other characters too.

The text in the first post is what I got from my translator, but I don't know if the half-width numerals were a stylistic choice or just an error (I'll ask him).

This time it skipped the character entirely (but you can still see the white space where the character would have been):

The font is set to Dynamic. But I think you can reproduce this with the stock Arial that comes with Unity, because that's what I did for my first recording.

Hmm, didn't quite work. It picked a different glyph this time, though:

And to be clear, the 

if (!allFonts.Contains(Rebuild_font))                
{ //if this font is not listed yet                 

is currently on line 3435.

I encountered a really, really specific bug when testing localizations. See here:

One of the characters in this line gets scrambled at 0:04, as if it's pulling the wrong glyph from an atlas or something. This was recorded with Arial as the font. Bizarrely, this only seems to happen in Japanese, and with specific spacing. As you can see, the line renders correctly when I drag the RectTransform around, and it only seems to break at a specific size. I'm guessing it has something to do with either the inline font switch, or word wrap, or both.

If you want to test it yourself, you can copy-paste this line in Japanese: 10進数の「2」はバイナリで<font=monospace>10</font>と表示するんや。

My "monospace" font is mapped to SourceCodePro-Medium:

Hey, thanks for the report. I've updated this post to clarify — the demo was moved over to its own app. And yep, 2.1.7 is the latest public release.

Thanks for checking out the demo! For what it's worth, you might be able to see some items that you can't actually collect until later (similar to a Metroidvania).

If you're talking about the Philosophical Slime's chip, you won't be able to get that until much later. However, it's possible to do the 15-sec pinwheel minigame right now. You can drink some black tea with Camellia beforehand to give yourself a speed boost, but it's not necessary. You have to hit all of the pinwheels to win the minigame, but they can be in any order.

Hi! I had moved the demo over to its own app, so you can play it here:

It might be related to this problem:

By default, I think there's a security setting on macOS that prevents you from running programs downloaded from the internet, and you have to do a workaround like the one described in the link above.

That said, I think the warning won't show up on the Steam version, because it's distributed through Steam rather than as a standalone zip file. You can try that and see if it works.

Late reply, but I finally got a Macbook to test on. I'm pretty sure the problem you experienced was just due to that particular build being corrupted. It should work correctly now (2.1.2). There were other graphical issues that have been fixed as well.

Keep in mind, the game might run slowly on the Intel UHD Graphics series and/or an i5 CPU. It works for me on a 2014 (?) Macbook Pro with an i7 and Intel Iris series integrated graphics. Either way, you can try it and see how it runs.

Yeah, that's the workaround that I ended up using. It works, but I think there is a bug buried somewhere in there about Rebuild causing the subsequent events to stop firing.

I was puzzled by this at first, because I think Rebuild was being called by some internal method to recalculate the bounds of the mesh as it was reading out (basically after it read out one line and continued on the next line). It can definitely catch you off guard, because Rebuild is called internally in various situations, and you'd expect that this wouldn't affect subsequent events as long as the read time remains the same.

Wait, where is stm.Pause() located? I see a pauseCount and a currentPauseCount, but I don't have a method called Pause().

Heads up — there might be a bug in STM 1.12.3 (Unity 2021.3.4) that causes events to be missed after calling Rebuild. Try subscribing to OnCustomEvent and testing it with something like this:

void OnCustomEvent(string eventTag, STMTextInfo info)
    Debug.Log("Executing event '" + eventTag + "'\nFrame " + Time.frameCount, gameObject);
IEnumerator PauseReading()
    stm.Rebuild(stm.currentReadTime, readAutomatically: false, executeEvents: true);
    yield return new WaitForSeconds(1f);
    stm.Rebuild(stm.currentReadTime, readAutomatically: true, executeEvents: true);        

And you can use string like this:

This is a test string<e=event1> with multiple events<e=event2>, but the subsequent events won't fire<e=event3> if we rebuild the mesh.

When I tried this in a fresh project, only event1 was fired.

In this particular case, I think I was able to work around it by just using <pause> and Continue() like you're supposed to. But I did run into some weird edge cases where Rebuild was messing things up (actually, I think my mesh was rebuilding because the bounds was changing when the line finished reading out, and that's what caused Continue() to not work when I first tried it).


Sure, I wouldn't mind. The full game is pretty long (around 100,000 words), and the demo is also pretty long, so it might take a while. The translation files aren't 100% ready yet, but you can join our Discord and DM me (eternal) if you want to talk.

Thank you for the kind words! Lemmasoft sure brings back memories.

Thanks! The entrance is over to the right on your way to the Bloodthirsty Slime, right after those two tall structures. I might rephrase the hint so that it points you in the correct direction (i.e. right and not left).

Oh hey, thanks for checking it out!

Yeah, I've realized that keyboard controls for platformers are too vague, so I think I'll give the player the option to choose an arrow key mapping or a wasd mapping at the beginning of the game. And the selection menu can show a picture of a keyboard with the relevant keys highlighted, so you know where to look. It's straightforward on a gamepad because the phone menu is just mapped to Y / Triangle.

I'll see if we can do crouch-walking in the future (I guess that would be crawling). Technically it would be easy, but I'm reluctant to add to the art backlog, so it's been low on the priority list. I know what you mean though, sometimes you get stuck under a tile and it feels like you should be able to just crawl out instead of dashing out.

Thanks, and good luck with Deadeye; seems like a really interesting game.

Got it, thanks. I'll let you know if I figure out what's going on.

You can interact by pressing the "up" arrow - same as entering/exiting your house.

Thanks. You're right, something is definitely wrong! I'll see what testing I can do on my end. I don't own a Mac, but I might be able to ask someone to check.

What's the model name/number of your computer? Or how much RAM and VRAM does it have?

Hm, probably not. Can you upload a screenshot?

Thanks for checking it out! The current update (2.0.9) features some performance improvements, so that will help with the frame dips.

Decided to finish what I started and get through the whole demo this time. That was pretty meaty - my save file is showing 2:20. There was a funny bug at the end where you could still walk around and open the menu after going into the portal, but the screen was black.

Not much else to add. The difficulty and enemy AI are nice. Feels like the king was actually trying to kill you instead of being nice and spreading his attacks around evenly.