Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to transition text in #9137

Closed
IntangibleMatter opened this issue Feb 20, 2024 · 8 comments · May be fixed by godotengine/godot#91749
Closed

Add a way to transition text in #9137

IntangibleMatter opened this issue Feb 20, 2024 · 8 comments · May be fixed by godotengine/godot#91749

Comments

@IntangibleMatter
Copy link

IntangibleMatter commented Feb 20, 2024

Describe the project you are working on

Several games with a large amount of dialogue.

Describe the problem or limitation you are having in your project

A key piece of polish in games with dialogue these days is text transitioning in, so that instead of a character simply appearing on a frame, it might, say, fade in and drop into position over 4 frames, making the text a lot cleaner.

As it stands, Godot has no way to do this without sacrificing large amounts of premade features, quality of life, or both.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

In RichTextLabels, there should be a way to transition characters in more smoothly. This has previously been discussed in #7270 and #5880, however both were too narrow in their discussion to implement an effective solution.

In essence, the feature that is needed is a way to define a RichTextEffect that only activates once, when a character becomes visible. This would allow dialogue (and any other text that people want to cleanly appear) in a RichTextLabel to be much more polished, and on par with what's generally standard with polished games these days this millenium.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

The implementation under the hood is up for discussion as I am unsure of how this would best be implemented, but on the user side it would work approximately like this:

$RichTextLabel.text = "[dropin]This text drops from the top of the line, and looks cleaner than just making a character appear[/dropin]"

Just another BBcode effect.

If this enhancement will not be used often, can it be worked around with a few lines of script?

This enhancement would, I believe, be used very often once implemented.

While it can hypothetically be worked around with scripting, it would require a large amount of script, and you would always be losing access to the automatic word shaping, an easy-to-use label, or a number of other features that make the RichTextLabel excellent for dialogue.

Is there a reason why this should be core and not an add-on in the asset library?

This is core functionality of the RichTextLabel.

@Mickeon
Copy link

Mickeon commented Feb 20, 2024

While it can hypothetically be worked around with scripting, it would require a large amount of script, and you would always be losing access to the automatic word shaping, an easy-to-use label, or a number of other features that make the RichTextLabel excellent for dialogue.

You did mention RichTextEffect. CharFXTransform even contains elapsed_time which allows you to have full control on the way the character is displayed, specifically at the beginning. This transition is not something identical for every game, so the extra customization is important.
A bit of fading isn't exactly a "large" amount of scripting, it can be probably clumped up to a formula.

So, a system for this already exists. What's the drawback of the current system?

@IntangibleMatter
Copy link
Author

IntangibleMatter commented Feb 20, 2024

While it can hypothetically be worked around with scripting, it would require a large amount of script, and you would always be losing access to the automatic word shaping, an easy-to-use label, or a number of other features that make the RichTextLabel excellent for dialogue.

You did mention RichTextEffect. CharFXTransform even contains elapsed_time which allows you to have full control on the way the character is displayed, specifically at the beginning. This transition is not something identical for every game, so the extra customization is important. A bit of fading isn't exactly a "large" amount of scripting, it can be probably clumped up to a formula.

So, a system for this already exists. What's the drawback of the current system?

Elapsed time doesn't act like it says it does, though.

It's not elapsed time per character, it's time elapsed for the entire label since the text was set. This is... a bit of a roadblock to having it done cleanly.

@KoBeWi
Copy link
Member

KoBeWi commented Feb 20, 2024

Well, if you are controlling how fast the text appears, calculating time needed to reach a character shouldn't be a problem.

@IntangibleMatter
Copy link
Author

If the text is displayed at a variable rate, however, that becomes a lot more difficult. With the current system you can only have certain properties at any given time.

Precalculated timing doesn't work when, say, you have dialogue that can be advanced earlier than it would normally finish by the player hitting "continue", which is generally standard in games with dialogue these days.

@mrcdk
Copy link

mrcdk commented Mar 2, 2024

If it is something like this:

GkAujnU.mp4

Then it's possible to do it with scripts. It's not straightforward but doable.

RichTextEffect script:

@tool
class_name FlyRichTextEffect
extends RichTextEffect


var bbcode = "fly"
var data = {}
var delta:float = 0.0
var visible_characters:int = -1:
	set(new_value):
		visible_characters = new_value
		# Clean up the data if we are showing less characters than before
		if visible_characters < 0:
			data.clear()
		else:
			for data_range in data.keys():
				if data_range.x > visible_characters:
					data.erase(data_range)


func _process_custom_fx(char_fx: CharFXTransform) -> bool:
	if visible_characters < 0:
		return true

	var starting_x = float(char_fx.env.get('start', -10))
	var speed = float(char_fx.env.get('speed', 50))

	var range = char_fx.range

	# get the char data, set the default data if not available
	var char_data = data.get(range, {
		"offset_x": starting_x,
		"end": char_fx.transform,
		"finished": false
	})

	var finished = char_data.get("finished", false)

	# if not finished then update the offset_x until it reaches 0
	# update the char_fx values with that
	if not finished:
		var offset_x = char_data.get('offset_x', starting_x)
		offset_x = move_toward(offset_x, 0, delta * speed)
		if offset_x >= 0:
			offset_x = 0
			char_data['finished'] = true

		char_fx.offset.x = floor(offset_x)
		char_fx.color.a = remap(offset_x, starting_x, 0, 0.0, 1.0)

		char_data['offset_x'] = offset_x

	# update the data dictionary
	data[range] = char_data

	return true

RichTextLabelScript script:

@tool
extends RichTextLabel

var fly_effect:FlyRichTextEffect

func _ready() -> void:
	# Disable clip contents or the first glyph of each line will be clipped from the left
	clip_contents = false

	fly_effect = FlyRichTextEffect.new()
	custom_effects.clear()
	install_effect(fly_effect)

func _process(delta: float) -> void:
	fly_effect.delta = delta
	fly_effect.visible_characters = visible_characters

You can get an idea on how to get whatever effect you are after. Basically, maintain a dictionary with glyph ranges as keys and the data you want to update as values. When changing the RichTextLabel.visible_characters value, it will remove old glyph values that have a range larger than the visible characters' value. And for each glyph within the visible characters' range that is not already in the dictionary with starting values, create a new entry in the dictionary and update its values.

If you need more control with how the visible characters animate in and out don't use RichTextLabel.visible_characters and use a custom variable. Animate whatever you need and set the alpha of the characters that should be hidden to 0.

@IntangibleMatter
Copy link
Author

Ah, so it is actually possible with not too much code, it's just... awkward and very unintuitive. I think that adding a tutorial for this effect (or something like this) to the docs, as I'm sure many developers would love to know that this is something you can do, as well as how to do it. As @mrcdk seems to know how it works, I think that he could be a good candidate for writing this tutorial. However, if I could get some direction from some of the more experienced Docs users, I could attempt to write this myself.

@Mickeon
Copy link

Mickeon commented Mar 2, 2024

I edited the above comment to use GDScript syntax highlighting, in case users are to stumble over this proposal.

@jordigcs
Copy link

jordigcs commented May 9, 2024

I was looking to add the same feature for my game in a less hacky way, so I opened a pull request for a simple, built-in method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants