Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Message
Author
User avatar
XT9K
Newbie
Posts: 14
Joined: Sun Nov 01, 2020 10:50 pm
Contact:

Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#1 Post by XT9K »

UPDATE: I've added a {para} for adding line breaks into your script. Turns out some text effects like FadeIn will continue off the screen if your line is too long. But we can tell the text displayable holding our wrappers to go to a new line by inserting the {para} tag I've included. I've uploaded the fix to the GitHub. You can find the new tag near the bottom of the kinetic_text_tag.rpy file. Sorry again for missing this before release.
UPDATE 2: I have updated the Github with a fix that should allow for better compatibility with TextButton styling. Allowing you to have these effects applied to text buttons while still having hover, idle, insensitive styling applied. Though it comes with some caveats. For more on it, see the Advanced section of the readme on the GitHub.
UPDATE 3: Special thinks to user _ticlock for the suggestion of using imagebutton instead of textbuttons. I have added an example of this to the GitHub. I will continue to have the text.py available however for anyone who would like the automatic style inheritance that it allows for. But text.py can otherwise be considered completely optional!!
UPDATE 4:After a friend's suggestion, I have also made the code available for download off of itch.io for those who'd want to download it through there or donate. itch.io Completely optional though. Get it however you'd like.
UPDATE 5:After updating to Ren'py 7.4, I can confirm that all of my effects and code still work. So no need to fear about these breaking these if you decide to update.
UPDATE 6:Decided to update a few things. Bit more than I'd like to cram in this section. So I've made a devlog on Itch for those who'd like to read up on the changes. TL;DR Added a new glitch effect tag and improved the documentation on the various tag arguments.
UPDATE 7: Updated to version 3.0! Included is a new text tag that allows you to make use of ATL to create new effects! Can read about it on the itch post.

Some of you may have noticed a couple of days ago I was looking into how it might be possible to move text around the screen. I feel it's safe to say I found my answer... along with something much... much more powerful. To go over some of what is possible with this.
We have wavy text
ExampleWaves.gif
Scared Text
ExampleScared.gif
Rotating text
ExampleRotation.gif
Fade-ins
ExampleFadeIn.gif
On the fly styling
ExampleChaos.gif
ExampleChaos.gif (169.92 KiB) Viewed 11216 times
Text Replacement
ExampleSwap.gif
ExampleSwap.gif (48.73 KiB) Viewed 11216 times
Text reacting to the mouse
ExampleMove.gif
Make use of ATL to make all new ones!
driptext.gif
3dtext.gif
You can combine effects as well!!!
MainCombo.gif
And this works wherever you can place text!!
ExampleMenu.gif
And many more I'm sure have yet to be found!!

I bestow upon you what I'm dubbing: Kinetic Text Tags.
The code for this is available at this GitHub but if you'll indulge me I'd like to explain how it works so others can get the most out of it. Afterward, I will go over some of its current limitations and areas it can be further expanded. This is my first time sharing anything like this as well so please let me know if I could have explained things better or ways I can improve. But given the sheer diversity of what you can do with this, I'll keep this mostly conceptual. Comments on how to code this and create your own are available in the kinetic_text_tags.rpy itself.
How it works:
  • To explain this I will start from where I began and build up from there. For starters, the problem with getting text to move is that the Text class does a lot of work for positioning each character. While this saves us a lot of headache in terms of making dialogue, it is very stubborn when it comes to allowing letters to move freely.
  • To overcome this, I created a custom displayable and use a text tag to apply it to the text. Going through every letter and applying a WrapperClass to a Text displayable that will handle one letter (or whatever length of text you'd like it applied to). This is then inserted back into the text string as an individual displayable.
  • Sadly, the Text displayable within our WrapperClass, our single letter, doesn't react much to Transforms. thankfully, our WrapperClass can specify where it would like to place the Text's render in the blit call! Allowing us to manipulate its position! And the Text displayable that holds our Wrappers and the other letters on the say screen (or wherever this is applied) considers all of our Wrappers as unique displayables. And when the Text class has a displayable inside it, it only checks the size of the displayable and allocates enough space for it in the line it's drawing, allowing us to display our letter of text however we wish. But this comes with a problem.
  • Sadly, our Text parent will send our Wrapper NONE of its style properties. Since our WrapperClass is registered as a displayable within it, it doesn't think to inform it of its style properties, because why would it, it's not a Text string. But even if we tried to mask ourselves as a text string somehow (which would be very hacky), we'd only end up being asserted a position again more than likely. Regrettably, the only way I work around this is to define the current style again WITHIN the text tag. But you might realize that even if we have those tags within our custom tag, we still need a way to apply it to every letter individually. Which is where we can be especially clever.
  • The Text displayable class will interpret any string we give it and have it search for tags within it. Meaning that all we need to do to apply our style tags to our letters is to include the tags around our characters WITHIN the letter's string itself. So if we had the character "s" and wanted to make it bold, all we'd have to do to apply this is to make it "{b}s". The child Text class within WrapperClass will apply the style for us! We just need to keep track of each property to apply while we're dividing up our original string.
  • But you might realize that if that's all we need to do to apply a style... well then our WrapperClass could change our Text class after making it. With that we could change not only the style of the text within our WrapperClass, but could even change the letters or even words we want it to display as well. And while the online Documentation doesn't mention this, the Text class does have a function .set_text(), which will apply all changes we could make. Giving us complete control over the Text!!

    That's most of what I have to say about how it works. Feel free to let me know if you'd want any of this explained further. I've been looking a lot at how Renpy handles Text lately and while I'm still getting my head around some of it, I've started to get a good feel for how it all works. I go over how you can make your own effects more within the code itself so you can get a feel for how to make use of it. But here I'll go over some of the implementation details and things to be aware of when making your own that didn't fit well within the code.
    Limitations, Notes and Misc.:
    • You also can nest your classes, applying multiple effects at the same time! However, it is advised that you don't apply too many at once!! As each letter will be a Text displayable within a Wrapper within a Text within a wrapper within a Text... The call stack would likely go pretty deep and can start to hurt performance depending on how intense your effects are. In some instances, it may be a better idea to just have one class that applies all the effects itself so as to improve performance. Or you can implement what I have called the Omega Tag. Which you send all of the effects you'd like stacked and allow it to nest them directly without needing to go through as many Texts. Making it so the callback will just be Wrapper > Wrapper > ... > Text instead. Still might make some lag but definitely a substantial improvement. Again, use whatever method works best for you.
    • There are some style tags that should be skipped. Renpy's documentation says you should skip {p}, {w}, {nw} and {fast} tags. I also feel that the {cps}, {clear}, {#} and {k} tags also deserve to go through untouched. Finally, there were some tags I did not feel up for implementing and I leave to the reader to attempt to implement if you so choose. These include the {space}, {vspace}, {image}, {a}, {horiz}, {vert}, and ruby tags ({art}, {rt}, {rb}, etc). I also did not feel up for testing every kind of style that can be applied in the Style class. I don't doubt there are many more combinations possible with those, but I am not fully prepared to be that rigorous in my testing. Outlines seem like they might be bugged??? I don't know enough about them to fully make use of them and test them. Please let me know of any issues you find and I'll see if I can fix them.
    • If you'd rather a list of text tags I currently have supported, it's {b}, {u}, {s}, {i}, {font}, {color}, {size}, {=custom_style}, {plain}, {outlinecolor} and {alpha} as well as my custom tags.
    • It should be noted that when a line is sent to the History tab, the History tab will strip it of text tags. Including ours. However, this behavior can be changed. You can either redefine the gui.history_allow_tags directly or just change the line in the default history screen to

      Code: Select all

      $ what = renpy.filter_text_tags(h.what, allow=[gui.history_allow_tags, "your_tag_here"]) 
      You may also realize this can be used to have different text show up in the history tag than what is shown in the say screen. While I fear what some may do with this power, I can only advise restraint. Otherwise, we might end up conditioning players to check that screen a lot more than is comfortable and break immersion. Please don't ruin visual novel players for the rest of us. The history tab is meant to be an accessibility feature, not a weapon. Might be a fun puzzle, but I feel there is already enough power within this system to implement them in other ways.
  • The online documentation never mentions it, but there are functions within renpy to support Matrix math that can be applied to renders. Allowing for projections and rotations in 3D space. The example rotation I included is based on the method viewtopic.php?f=51&t=59587 used since it is likely faster. But I have tested it with the matrix functions and they do work. Though there are some quirks to it I'm still working out. If I come up with anything using them I'll likely add them to the GitHub. But for now, I'll leave an example of the RotateText class using it in a large block comment for people to play with as it does allow for rotations in the x and y axis! I'll also mention in there what the other matrix functions are and their parameters for those interested but not wanting to dig into renpy to find them.
  • I should acknowledge that if it is applied to a text button in the menu, it does not update the colors for hovering and such. The best workaround available is to use an imagebutton instead. Examples are included in the screens.rpy on the github.
  • Style tag warning! If, for example, you have "Here is some {b}nice {bt}{b}wavy{/b} smaller{/bt}{/b} text" Where you have a bold begin outside of one of our custom text tags but want it to end inside the tag, you'll have to include an additional {/b} outside the tag like above to be sure that it ends it for the whole text. In you don't, the bold will apply for the rest of the text! So either be sure to end it again outside the tag or make it so your custom tag function will add the cancel tag back in regardless.
  • I believe I attempted most of the effects I was interested in testing. I honestly can't name one that didn't work (even if rotation still needs some polishing). The only thing I thought of that I didn't try was wrapping the text in a Drag n Drop class. But I haven't messed with those much yet. So just something to try later. Though I feel I've only scratched the surface of what's possible with this and am super interested in seeing what people come up with.
  • Renaming characters on the fly can already be done with a Character that has the dynamic flag. Please see the documentation for more info https://www.renpy.org/doc/html/dialogue ... er-objects. That said, I felt it was useful to show as an example.
Final Thoughts
That's all I have to say about it for now. Again the code is available here. I'm really really excited to see what people come up with using this!! Visual Novels are so often reliant on their art looking good, but I feel that good looking text is often a part of the process that is often overlooked. While using these in excess would likely hurt your game more than help, I think you can add a lot to how your characters talk with these! And I could only imagine how this would be used in a Doki Doki Literature Club type game. So do be sure to share what you come up with!! And maybe even mention me in the credits if it helped you out. It'd really make my day to learn that this could help some projects out~
Last edited by XT9K on Sun Jul 25, 2021 1:46 pm, edited 7 times in total.

User avatar
RicharDann
Veteran
Posts: 286
Joined: Thu Aug 31, 2017 11:47 am
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#2 Post by RicharDann »

Just the other day I was checking out a game that uses a system similar to this and I thought it would be cool if this could be made in Ren'Py. I am impressed at how well it can be implemented, it adds a lot of flavor and personality to dialogue.

I'm not too familiar with the way text tags are handled but I'll look into it and see if I can come up with something, either way this is incredibly helpful, so thank you for all the hard work you clearly put into this.

Excellent work!
The most important step is always the next one.

User avatar
ghostclown
Regular
Posts: 28
Joined: Sun Oct 11, 2020 2:33 pm
Projects: R. I. P. Tour
itch: ghostclown
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#3 Post by ghostclown »

This looks awesome! I'm very excited to try it out.

User avatar
XT9K
Newbie
Posts: 14
Joined: Sun Nov 01, 2020 10:50 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#4 Post by XT9K »

So as mentioned in the update I have added a fix for allowing for TextButtons to properly supply our classes with style prefixes and base styles. Just go to where you have renpy installed (Currently should be a folder called renpy-7.3.5-sdk) and in there go to renpy > text and replace the text.py with the one on the GitHub. This changes set_style_prefix() to tell it to send the new prefix down into our child, so long as it has a function called set_style. This also updates Text's update function so that it supplies its style to our class after it has been added. And adds the function set_style() so that the Text class our Wrapper holds onto can be supplied a new style.

Just be sure to implement the functions set_style_prefix() and set_style() within your class to allow for the style to go through. Just copy and paste the functions in the block comment above BounceText to every class you'd want this done too. This also means you won't have to define a style at the beginning of a tag every time! (Though you will still have to add any {b}, {u}, {i}, etc tags if those occurred earlier in the text).

All that said, here are some words of caution.
  • While I do not believe this should affect anything outside of our classes, I cannot say that I have tested this in every scenario you may come across. If you encounter any bugs, please let me know and I will do my best to fix it.
  • This is currently only for 7.3.5. While I believe it should work with older versions of text.py, I cannot be sure without checking. If you're using an older version, you may want to double check that it's basically the same or insert the changes yourself. And when 7.4 comes out I'll do my best to add the change as well.
  • If you would like to revert back to normal text.py, I have included the line numbers of the changes I made in the GitHub README for you. thankfully I did not change any lines. Just have to remove what I added.
If you find a better way of doing this that does not involve changing Renpy's internals, please let me know and I'd be happy to add them to the GitHub as another option. I have also included my notes on how styles are applied to text on GitHub as well for anyone that'd like to look into the issue. Hope it can help you out.

Think that's all I had to say about it. Hopefully, this should make these tags useful just about anywhere you'd want them! If you find anywhere else they may be lacking, be sure to let me know and I'll see what I can do to help!! Gotten a pretty good understanding of how Renpy handles text and should be able to figure something out.

User avatar
SypherZent
Veteran
Posts: 361
Joined: Fri Sep 02, 2016 3:14 am
Completed: Multiverse Heroes, Space Hamster in Turmoil
Location: Puerto Rico
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#5 Post by SypherZent »

Slay the Spire and Monster Train and the recent roguelike CCG deckbuilders do things like this to the text of the dynamic events. So, it's pretty awesome to see this implemented in Ren'Py, but I wish it was done through an .rpy file and not by modifying the engine's files.

It's possible to create new classes in your own .rpy files without altering Ren'Py in any way. This way the changes persist across Ren'Py updates.

Developing this as a standalone system that doesn't depend on, nor interfere with Ren'Py's native functionality would be ideal.

(Correct me if I'm wrong, just that I saw text.py on the GitHub and assumed the file had been modified.)

User avatar
XT9K
Newbie
Posts: 14
Joined: Sun Nov 01, 2020 10:50 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#6 Post by XT9K »

SypherZent wrote: Fri Nov 20, 2020 1:03 pm Slay the Spire and Monster Train and the recent roguelike CCG deckbuilders do things like this to the text of the dynamic events. So, it's pretty awesome to see this implemented in Ren'Py, but I wish it was done through an .rpy file and not by modifying the engine's files.

It's possible to create new classes in your own .rpy files without altering Ren'Py in any way. This way the changes persist across Ren'Py updates.

Developing this as a standalone system that doesn't depend on, nor interfere with Ren'Py's native functionality would be ideal.

(Correct me if I'm wrong, just that I saw text.py on the GitHub and assumed the file had been modified.)
The text.py file is a largely optional inclusion. It's primarily there for people who want these text effects to work on textbuttons, since the button styling doesn't automatically propagate through the Text class. But all the effects can be implemented without the use or installation of it. If I find a workaround for this button styling thing I'll be sure to include an update though as I'd prefer not to use this method either. It's just the best I could think of for now for people who would want this in their menus.

If you do not care about applying these effects to textbuttons or the like, then you can happily ignore it and just use the kinetic_text_tags.rpy.

User avatar
SypherZent
Veteran
Posts: 361
Joined: Fri Sep 02, 2016 3:14 am
Completed: Multiverse Heroes, Space Hamster in Turmoil
Location: Puerto Rico
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#7 Post by SypherZent »

That's reassuring to hear, thanks for clarifying!

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#8 Post by _ticlock_ »

Hi XT9K,

Cool text effects. I like them. As I understand, it works with Text() as well. So, hypothetically it can be used for textbuttons without modifying the engine's files (without text.py) using Text():

Code: Select all

textbutton Text("{bt=10}Hi  I am wavy textbutton{/bt}"):
    action Return()
Or am I am missing something?

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#9 Post by _ticlock_ »

_ticlock_ wrote: Mon Nov 23, 2020 10:56 pm Or am I am missing something?
Nevermind. I just realized you were talking about button styles.

Anyway, I found it very useful to use with imagebuttons and imitate textbutton hover effect:

Code: Select all

imagebutton:
        idle Text("Plain text")
        hover Text("{bt=2}{color=#f00}" + "Red wavy text" + "{/color}{/bt}")

User avatar
XT9K
Newbie
Posts: 14
Joined: Sun Nov 01, 2020 10:50 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#10 Post by XT9K »

_ticlock_ wrote: Tue Nov 24, 2020 12:00 pm
_ticlock_ wrote: Mon Nov 23, 2020 10:56 pm Or am I am missing something?
Nevermind. I just realized you were talking about button styles.

Anyway, I found it very useful to use with imagebuttons and imitate textbutton hover effect:

Code: Select all

imagebutton:
        idle Text("Plain text")
        hover Text("{bt=2}{color=#f00}" + "Red wavy text" + "{/color}{/bt}")
Oh yeah. I remember I meant to look into trying that. But been distracted with a different project lately so forgot to get around to testing that. Good call! I'll try to remember to put some examples of that on the Github for people to reference. Sure some will still find use for the modification I included as well. But this is likely a suitable workaround in some instances. Though sure some will find it annoying to have to type in all the style stuff for each variation. But not like there's a non-hacky way to get around this limitation.

User avatar
Rukitto
Newbie
Posts: 2
Joined: Wed Apr 21, 2021 10:17 am
Completed: Koumon no Hana, KnHxAnother!!
Tumblr: handsofloukit
itch: studio-bakanal
Location: Lost in code
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#11 Post by Rukitto »

Hello XT9K!

I just ddl 2.0 vers and it's beautiful! Thank you so much! And it's easy even for a newbie to renpy like me! :D

I don't know if it's the appropriate place to ask since I'm new to this forum too... But I was thinking about people with reading difficulties, and wondered, do you know if there is a way to add an option in the preferences which will desactivate all text effects when people need it?

I though about doing it with a persistent, but clearly I don't know that much in python and it doesn't work... :/

PS : English isn't my native language so I apologies if there's mistakes ^^"

User avatar
XT9K
Newbie
Posts: 14
Joined: Sun Nov 01, 2020 10:50 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#12 Post by XT9K »

Rukitto wrote: Wed Apr 21, 2021 12:49 pm I don't know if it's the appropriate place to ask since I'm new to this forum too... But I was thinking about people with reading difficulties, and wondered, do you know if there is a way to add an option in the preferences which will desactivate all text effects when people need it?

I though about doing it with a persistent, but clearly I don't know that much in python and it doesn't work... :/
Thanks for downloading and glad you're enjoying it. To answer your question, here's how you go about it.

First I recommend using a preference variable since that's usually pretty good for things like options and accessibility. I recommend following the example I did for disabling the chaos text tag. So let's call it something like

Code: Select all

default preferences.disable_tags = False
and you can stick that wherever you like. For making it something you toggle in the preferences just follow the example I have in the screens.rpy of the game for disabling the chaos_tag. So in your preferences screen it'll be something like. (Multiple ways you could do this. This is just one example.)

Code: Select all

vbox:
    style_prefix "radio"
    label _("Disable Text Effects")
    textbutton _("On") action SetVariable("preferences.disable_tags", True)
    textbutton _("Off") action SetVariable("preferences.disable_tags", False)
Now in each of the text wrapper classes (ie BounceText, ScareText, ChaosText, etc.) you'll have to go in and modify their render function. The exact code might differ from class to class but generally, you'll put something like this at the top of each render function.

Code: Select all

if preferences.disable_tags: 
    return renpy.render(self.child, width, height, st, at)
Which just spits out the child render without anything fancy. You may want to add a renpy.redraw(self, 0) before that return if you want the tag to start animating again when they are no longer supposed to be disabled. But I imagine if someone is turning them off, they probably aren't going to want to turn it back on.

Now you can be more selective about this. Such as by using a set for disable_tags. So something like

Code: Select all

default preferences.disable_tags = set()
Then have something in the preferences screen like:

Code: Select all

vbox:
    label _("Disable Tags")
    textbutton _("Bounce") action If("bounce" not in preferences.disable_tags, AddToSet(preferences.disable_tags, "bounce"), RemoveFromSet(preferences.disable_tags, "bounce")) selected "bounce" in preferences.disable_tags
    textbutton _("Scare") action If("scare" not in preferences.disable_tags, AddToSet(preferences.disable_tags, "scare"), RemoveFromSet(preferences.disable_tags, "scare")) selected "scare" in preferences.disable_tags
    etc...
This will allow us to add and remove tags from the set.
And then in each render function you instead do:

Code: Select all

if "bounce" in preferences.disable_tags: # Or whatever relevant string will represent the tag in the set
    return renpy.render(self.child, width, height, st, at)
Hope all that answered your question. Feel free to ask if you have any more! :D

User avatar
Rukitto
Newbie
Posts: 2
Joined: Wed Apr 21, 2021 10:17 am
Completed: Koumon no Hana, KnHxAnother!!
Tumblr: handsofloukit
itch: studio-bakanal
Location: Lost in code
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#13 Post by Rukitto »

Hello!
I just tried it and it works perfectly, thank you so much for your help ! :D

henvu50
Veteran
Posts: 337
Joined: Wed Aug 22, 2018 1:22 am
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#14 Post by henvu50 »

I need to make the gradient text effect vertical, from the top of the text downwards, does anyone know how to do that?

User avatar
XT9K
Newbie
Posts: 14
Joined: Sun Nov 01, 2020 10:50 pm
Contact:

Re: Kinetic Text Tags (Moving, waving, bouncing, shaking, replacement, style changes and more!)

#15 Post by XT9K »

henvu50 wrote: Sat Jul 24, 2021 1:12 am I need to make the gradient text effect vertical, from the top of the text downwards, does anyone know how to do that?
I see two ways you could do this. First option is to write a shader that handles this. Probably wouldn't be the hardest thing if you know how they work.

Second option is to do something similar to what I did for wave rendering. Rendering the text multiple times with different colors. Then apply strips of each color to your final render. If I remember I'll maybe try writing this out in my free time. But been pretty busy lately so not sure when. If you wanna do it yourself though, I recommend taking a look at my wave rendering stuff, which has an example of rendering over a palette and rendering in strips. viewtopic.php?f=51&t=61941

Post Reply

Who is online

Users browsing this forum: No registered users