Final Fantasy Tactics Advance: Text UI in Godot

Final Fantasy Tactics Advance
Zavier’s Begining Fantasy Tactice Not So Advance

I I should preface this by saying this is not meant to be a tutorial, but just a place for me to jot things down, and maybe someone might find my crazy useful.

Github repo: https://github.com/zipzav/Final-Fantasy-Tactics-Advance-UI

Most people probably will cite Final Fantasy Tactics on the Playstation as the ultimate FF Tactics game, but my first introduction to the Tactics genre was the latter Final Fantasy Tactic Advance on the Gameboy Advance. So I decided to replicate some UI elements from it as a form of Design Spec/Tech Spec practice.

Requirements

UI: Character Textbox

Overview

This textbox will be the main way the game characters will speak in the game. The entire UI element will consist of a text portion and the speaking character’s portrait. The textbox will often be interspersed between animations, so there should be a way to activate the next set of animations/actions or just the next part of the conversation.

Conversation Structure

A conversation can be between any amount of characters ( >= 1 ). There are no multiple paths to the dialog, nor choices to be made.

Element Placement

A designer should be able to specify where the UI element will sit on the game screen from 4 options: Top Left, Top Right, Bottom Left, Bottom Right. Maybe they should also be able to set the group for more than 1 character at a time:

Text

Because of the limited amount of space on the screen, each instance of the textbox can only contain a limited amount of characters. It can only contain at most 3 lines with <= 20 characters each, whitespace included. It does not follow that 60 character strings will fit, as the text will be will auto wrapped in the box, thus potentially wasting the remaining space on a line. This means for longer speeches, the player will have to continue to the next textbox dialog. When writing out the lines of text, a designer will provide the element the string for that single box and the widget should autowrap the text itself. It should warn designers that the assigned text does not fit.

Each time someone starts speaking and each the speaker changes, the text box will appear from an edge. The whole text will not appear at first, the text will printout one letter at a time (in a given speed) until the all the text is revealed. There should be a sound when letters are being printed out, to provide more feedback. The player can press “A” to force all the text to revealed. Once the whole text is typed out a downward triangle will appear, signifying that the player can continue on to the next next block. If the next text is still being said by the same character, the outline and the portrait will stay the same, while the text is cleared and the text printing will start again. If the next text is by a different character, then the outline and the portrait will slide out of the frame, and then the a new text box will appear from an edge and the process will start again.

There should be a slight shadow beneath the text.

Portrait

The portrait will always sit on the edge the textbox originates from. There should be an invisible boundary separating the text and the portrait to prevent the text from ever being written over the portrait.

Auto-sizing

The width of the textbox will be dependent on the longest line of the 3 currently displayed.

Implementation

Godot Node Structure:

ConversationComponent
	OriginSelector ( NodeSelector, custom Node )
		TopRight ( UITextBoxWidget, custom Node )
			BGSelector ( NodeSelector, custom Node )
				onesize ( UIConversationTextWidget, custom Node )
					background ( CanvasItem )
					Text ( RichTextLabel )
					ContinueTriangle ( Sprite )
				twosize ( UIConversationTextWidget, custom Node )
					background ( CanvasItem )
					Text ( RichTextLabel )
					ContinueTriangle ( Sprite )
				threesize ( UIConversationTextWidget, custom Node )
					background ( CanvasItem )
					Text ( RichTextLabel )
					ContinueTriangle ( Sprite )
				foursize ( UIConversationTextWidget, custom Node )
					background ( CanvasItem )
					Text ( RichTextLabel )
					ContinueTriangle ( Sprite )
			CharacterPortrait ( Sprite )

UI: Character Textbox

Data Structure

Because only one TextWidget is visible at a time, we only need to maintain a pointer to the one currently selected under the BG Selector, Sprite texture will also be allocated at the start of a conversation (and destroyed after). Conversation data (that is parsed from our conversation JSON file can also be an array).

class ConversationComponent
{
		UIConversationTextWidget* m_UITextWidget;
		Sprite* m_characterSprite;
		std::map<String, Ref<Texture>> m_spriteMap;
		Array m_conversationArray;
}

Currently, there seems to be a bug inGgodot where when we obtain a pointer to the Label text of our UIConversationTextWidget, by the time we used it, the pointer is invalid. So, we are re-accessing the Label each time we modify it. If it did work properly, the widget would just maintain a pointer to the Lbael;

class UIConversationTextWidget
{
//Label* m_text;
}

Because a character’s speech can go over a single Textbox, we will use a vector of strings where each element in the vector is the text for a single textbox.

Textbox Animation

Currently, there is an issue in Godot’s GDNavtive feature (which allows us to write C++ code for our game), where Tweening doesn’t work in the negative range. If you tween a value from 150 to -150, the value will stop changing at 0.

This posed a challenge when trying to animation the Textboxes in, as we can have it so that it starts off screen, and then tween the position to the final position. The left side textboxes have a starting position in the negative range, and it would not animate.

So instead of going for position, we just went with scale. if you animate the x-scale of a sprite, it can mimic the look of the textbox moving in from offscreen. This doesn’t always look the best, as it squishes the UI at the start, but the animation is short enough that it looks fine.

Text Scrolling

The implement the text scroll we will make use of Godot’s Tween Node as well as the percent_visible member variable of a Label Node. We can provide it a letters per second rate to help tune the animation:

m_textAnimationtween->interpolate_property( m_UITextWidget->get_text_node(), "percent_visible", 0.0f, 1.0f, static_cast<float>( length ) / m_letterPerSecond );
IDDescriptionAcceptance CriteriaNotes
UITB-01Create Node Selector Widget Script– Can chose between child of selector widget.
– Choosing a child will make it visible and make all its siblings hidden.
UITB-02Create Godot Screen with Code Hookups
UITB-03Create base UIConversationTextWidget– Can instantiate Widget on screen, providing it with an origin, text and Sprite.
UITB-04Implement Text Wrapping Logic-Should select the correct widget size and properly wrap the next.
UITB-05Implement text scroll animation– When a Widget first appears, the text should scroll in.
– Should be able to skip it my pressing a button.
– When done scrolling, downward triangle should appear and animate.
UITB-06 Implement text scroll sound– When text scrolls sound plays.
-When text finishes scrolling or animation is ended early, sound stops
UITB-07Create ConvesationComponent– Can read in in scene data from an external file
– Can instantiate the appropriate Sprite Textures
UITB-08Implement Widget in and out animation-Correctly plays appropriate animation
UITB-09Create ConversationComponent: stringing together TextWidgets– User can continue from one text widget to the next
– When the character speaking changes:
– Sprite changes
– Origin changes
– TextWidget is removed after the conversation

Leave a Comment