Fancy version 0.7.5 Get Latest Release
fancy.scm (https://github.com/ericoporto/fancy/releases/download/v0.7.5/fancy.scm) | GitHub Repo (https://github.com/ericoporto/fancy) | Project with Demo! (https://github.com/ericoporto/fancy/releases/download/v0.7.5/fancy_demo_windows.zip)
(https://github.com/ericoporto/fancy/actions/workflows/main.yml/badge.svg) (https://github.com/ericoporto/fancy/actions)
Fancy is a Script module for "fancy" text in Adventure Game Studio, you can have text with multiple colors, fonts, with sprites and other. It brings it's own Typed-Text mechanism and additional fancyness.
The cheatsheet of tags are below. Some tags are "solo", they don't require a closing tag.
- Color tag is "[c:123]" and "[/c]", where "123" is an ags color for the text.
- Outline color tag is "[o:123]" and "[/o]", where "123" is an ags color for the outline.
- Font tag is "[f:123]" and "[/f]", where "123" is an ags font index.
- Sprite tag is solo "[s:123]", where "123" is an ags sprite.
Note: use
"\n" for linefeed, old lone
"[" ags linefeed is not supported.
Notice that if you need to pass a number that is dynamic you can use String.Format to create the string with the proper number, like if the sprite icon you want to put in your text is from a dynamic sprite or the color of a word comes from a character speech color.
(https://i.imgur.com/w5olTxG.png)
UsageI will improve this soon, for now some small examples
Using a regular drawing surface:function room_AfterFadeIn()
{
Fancy.AddAlias("red", 64493); // this should be at game_start
DrawingSurface* surf = Room.GetDrawingSurfaceForBackground();
surf.DrawingColor = 10565;
surf.DrawRectangle(48, 48, 248, 108);
surf.DrawFancyString(48, 48, "Hello!\n[o:8560]Can you find me the [c:27647]blue cup [s:2041][/c][/o]?\nI lost it in the [c:red]dangerous [f:0]planet[/f][/c], somewhere.", FancyConfig.Create(eFontSpeech, 22422), 200);
}
Simple not-useful typed text example:FancyTypedText fttb; // has to be global
function room_AfterFadeIn()
{
Fancy.AddAlias("red", 64493); // this should be at game_start
Fancy.AddAlias("ico_bcup", 2041); // this should be at game_start
fttb.FancyConfig.Font = eFontSpeech;
fttb.FancyConfig.TextColor = 22422;
fttb.SetDrawingArea(48, 40, 200);
fttb.Start("Hello!\n[o:8560]Can you find me the [c:27647]blue cup [s:ico_bcup][/c][/o]?\nI lost it in the [c:red]dangerous [f:0]planet[/f][/c], somewhere.");
}
void repeatedly_execute_always()
{
if(fttb.IsTextBeingTyped)
{
DrawingSurface* surf = Room.GetDrawingSurfaceForBackground();
surf.DrawingColor = 10565;
surf.DrawRectangle(48, 40, 248, 90);
fttb.DrawTyped(surf);
}
}
Script APISpoiler
Script Extensions
DrawingSurface.DrawFancyString
void DrawingSurface.DrawFancyString(int x, int y, const string text, optional FancyConfig* config, optional int width);
Draw the text from a fancy string on the drawing surface.
DynamicSprite.CreateFromFancyString
DynamicSprite* DynamicSprite.CreateFromFancyString(const string text, optional FancyConfig* config, optional width);
Create a sprite with the text of a fancy string
DynamicSprite.CreateFromFancyTextBox
DynamicSprite* DynamicSprite.CreateFromFancyTextBox(const string text, optional FancyConfig* config, optional width, optional Fancy9Piece* f9p);
Create a sprite of a textbox with a fancy string using a 9-piece.
Character.FancySay
void Character.FancySay( const string text, optional FancyConfig* config, optional int width, optional Fancy9Piece* f9p );
A Say alternative that support fancy strings
Character.FancySayTyped
void Character.FancySayTyped( const string text, optional FancyConfig* config, optional int width, optional Fancy9Piece* f9p );
A Say alternative that support fancy strings, it types it instead of readily drawing
Button.Fancify
void Button.Fancify(optional Fancy9Piece* normal, optional Fancy9Piece* mouse_over, optional Fancy9Piece* pushed);
Sets a button NormalGraphic and additional sprites from it's text, assumed as fancy string, and 9-piece.
Button.UnFancify
void Button.UnFancify();
Removes fancyness from button (clear any altered sprites)
Fancy
This is a global struct you can't instantiate, it contains static methods for global configuration meant to be used at game start.
Fancy.AddAlias
static void Fancy.AddAlias(String key, int value);
Allows adding a global alias to a tag-value. Ex: AddAlias("red", 63488) allows using [c:red] instead of [c:63488].
This may be useful if you want to be able to change your mind later on what is the specific of a color, or you want to have an easier type remembering sprite icons you are reusing in your texts.
Alias added here are global to all of Fancy. It's recommended that you only add an alias once to everything you need at the game_start of your project, make it easier to manage aliases.
Fancy.FancyConfig
static attribute FancyConfig* Fancy.FancyConfig;
This is the default global FancyConfig, if you don't specify or if you pass null to a method that requires a FancyConfig as parameter it will use this config instead.
Fancy9Piece
This is a managed struct that holds a 9-piece that can be used for drawing text boxes.
Fancy9Piece.CreateFromTextWindowGui
static Fancy9Piece* Fancy9Piece.CreateFromTextWindowGui(GUI* text_window_gui);
Create a 9 piece fancy compatible from a Text Window GUI.
Fancy9Piece.CreateFrom9Sprites
static Fancy9Piece* Fancy9Piece.CreateFrom9Sprites(int top , int bottom, int left, int right, int top_left, int top_right, int bottom_left, int bottom_right, int center_piece = 0, int bg_color = 0);
Create a 9 piece fancy from 9 sprite slots.
You can optionally pass a color instead of a sprite for the center piece, by passing 0 to center_piece and a valid ags color in bg_color.
FancyConfig
This is a managed struct meant to configure an instance from FancyTextBase and extensions, prefer using it's Create method.
FancyConfig.Create
static FancyConfig* FancyConfig.Create(FontType font, int color, int outline_color, int outline_width, Alignment align, int line_spacing);
Configuration structure for fancy text drawing, allowing customization of font, text color, line spacing, and alignment.
By default, when using create, if you don't set, outline color is initially set for COLOR_TRANSPARENT and outline width is initially set to 1, align is set to eAlignBottomLeft and line_spacing is 0.
FancyTextBase
FancyTextBase.SetDrawingArea
void FancyTextBase.SetDrawingArea(int x, int y, int width = FANCY_INFINITE_WIDTH);
Sets the area for drawing fancy text, specifying the position and width.
FancyTextBase.Text
attribute String FancyTextBase.Text;
Sets the text content for the fancy text, this is where the parsing of the text happens.
FancyTextBase.PlainText
attribute readonly String FancyTextBase.PlainText;
Get the set text without tags.
FancyTextBase.Draw
void FancyTextBase.Draw(DrawingSurface* surf);
Draws the fancy text on the specified drawing surface.
FancyTextBase.FancyConfig
attribute FancyConfig* FancyTextBase.FancyConfig;
Property to set the Fancy Text rendering configuration.
FancyTextBox
FancyTextBox.CreateTextBoxSprite
DynamicSprite* FancyTextBox.CreateTextBoxSprite();
Create a sprite of a textbox with a fancy string using the configured 9-piece
FancyTextBox.Fancy9Piece
atrribute Fancy9Piece* FancyTextBox.Fancy9Piece;
Setup the 9-piece for the Text Box creation.
FancyTypedText
FancyTypedText.Clear
void FancyTypedText.Clear();
Clears all text and resets everything for typed text.
FancyTypedText.Start
void FancyTypedText.Start(String text);
Sets a new string and resets everything to start typing. You can then use Tick repeatedly to advance the text.
FancyTypedText.Skip
void FancyTypedText.Skip();
Skips all remaining typing of the text.
FancyTypedText.Tick
void FancyTypedText.Tick();
Updates the typed text state, advancing it by a single tick.
FancyTypedText.DrawTyped
void FancyTypedText.DrawTyped(DrawingSurface* surf);
Draws the typed text in it's current state.
FancyTypedText.CreateTypedSprite
DynamicSprite* FancyTypedText.CreateTypedSprite();
Create a sprite of the text being typed.
FancyTypedText.IsTextBeingTyped
attribute readonly bool FancyTypedText.IsTextBeingTyped;
True if a text is being typed in the FancyTypedText, and not finished.
LicenseThis module is created by eri0o is provided with MIT License, see LICENSE (https://github.com/ericoporto/fancy/blob/main/LICENSE) for more details.
Spoiler
Added a new 0.2.0 release, where I am adding outline config! It's color can be set using the "o" tag like "[o:33153]key[/o]"!
There is no way to set the outline width in the string itself, but you can set it in the config when using the FancyTextBase and anything that extends it.
Of course if you font already has an Outline set in AGS, you won't be able to either remove or set the color or do any changes at all. But this may be alright if you don't intend to change it and the drawing of text that has to have an outline will be slightly faster when using the ags one - but it probably won't be noticeable.
Edit:
Quickly adding a 0.3.0 release where I am adding a global dictionary inside Fancy that can hold aliases. This should be useful if you want to alias something, like giving a name to a sprite or color when referencing inside the fancy strings. Alias can't start with numbers.
Added a new 0.3.1 release adding some maybe useful extensions:
Spoiler
DrawingSurface.DrawFancyText
void DrawingSurface.DrawFancyText(int x, int y, int color, FontType font, const string text);
Draw the text from a fancy string on the drawing surface.
DynamicSprite.CreateFromFancyTextWrapped
DynamicSprite* DynamicSprite.CreateFromFancyTextWrapped(int width, int color, FontType font, const string text);
Create a sprite with the text of a fancy string wwith word-wrap at set width
DynamicSprite.CreateFromFancyText
DynamicSprite* DynamicSprite.CreateFromFancyText(int color, FontType font, const string text);
Create a sprite with the text of a fancy string
Overlay.CreateFancyTextual
Overlay* Overlay.CreateFancyTextual(int x, int y, int width, FontType font, int color, const string text);
Creates a screen overlay from fancy text
Added the above in the documentation in the first post too!
I think I want to try to tackle 9-piece text box next.
Spoiler
Added new 0.4.0 release with the possibility to create a textbox sprite from a 9-piece. You can create a 9-piece manually by passing the 9 sprites or from a Text Window GUI you set in the Editor.
A 9-piece has the form like the table below:
Top Left Sprite | Top Sprite | Top Right Sprite |
Left Sprite | Center Background Sprite | Right Sprite |
Bottom Left Sprite | Bottom Sprite | Bottom Right Sprite |
It will keep the top-left and such corners, and stretch the top and bottom horizontally and the left and right vertically, the center will also be stretched. This is how the current AGS Text Window GUI works right now.
The added API is listed below, but the main topic has also been updated!
Spoiler
Fancy9Piece
This is a managed struct that holds a 9-piece that can be used for drawing text boxes.
Fancy9Piece.CreateFromTextWindowGui
static Fancy9Piece* Fancy9Piece.CreateFromTextWindowGui(GUI* text_window_gui);
Create a 9 piece fancy compatible from a Text Window GUI.
Fancy9Piece.CreateFrom9Sprites
static Fancy9Piece* Fancy9Piece.CreateFrom9Sprites(int top , int bottom, int left, int right, int top_left, int top_right, int bottom_left, int bottom_right, int center_piece = 0, int bg_color = 0);
Create a 9 piece fancy from 9 sprite slots.
You can optionally pass a color instead of a sprite for the center piece, by passing 0 to center_piece and a valid ags color in bg_color.
Script Extensions
DynamicSprite.CreateFromFancyTextBox
DynamicSprite* DynamicSprite.CreateFromFancyTextBox(int color, FontType font, Fancy9Piece* f9p, const string text, int width = FANCY_INFINITE_WIDTH);
Create a sprite of a textbox with a fancy string using a 9-piece.
Overlay.CreateFancyTextBox
Overlay* Overlay.CreateFancyTextBox(int x, int y, int width, FontType font, int color, const string text, Fancy9Piece* f9p);
Creates a screen overlay from a textbox with a fancy string using a 9-piece
To avoid confusion I renamed the previous Draw api to use the String word instead. The main post is also updated on this.
Edit:
Added new 0.4.1 Release that adds an extension to buttons so you can fancify or unfancify them, which will use it's text and you can optionally pass a 9-piece to it.
The added API is below and is also updated in the first post!
Button.Fancifyvoid Button.Fancify(Fancy9Piece* normal, Fancy9Piece* mouse_over = 0, Fancy9Piece* pushed = 0);
Sets a button NormalGraphic and additional sprites from it's text, assumed as fancy string, and 9-piece.
Button.UnFancifyvoid Button.UnFancify();
Removes fancyness from button (clear any altered sprites)
Edit: quick 0.4.2 release to fix to parameter ordering in Overlay.CreateFancyTextual and Overlay.CreateFancyTextBox. Edit: oops broke the demo, fixed in 0.4.3.
Spoiler
Created a new 0.5.0 release, redid the entire API, you now should use the
FancyConfig managed struct instead of individually passing color or font parameter. This is because I noticed I myself was constantly switching one by the other and getting unexpected results and using this it becomes easier to figure issues.
The FancyConfig struct has the following properties
- Font, it's the base font, if a text is not in font tags it will have this font
- TextColor, base color, the same, when no tag indicates text color it will use this one
- OutlineColor, base outline color, same as above
- OutlineWidth, there is no way to set individual outline width, but you can set here for all the texts, when an outline color that is different from COLOR_TRANSPARENT, it will be draw with this.
- LineSpacing, for additional space between lines
- TextAlign, you can change this property to align the text inside the line, it applies to all lines in the text
- Padding, can add internal space between the text and textbox border, ignored in non-textbox functions.
The methods accept a pointer to it so you can create it first and then pass it along.
Edit: quick type fix for the FancyConfig in 0.5.1 and added a padding to the textbox.
Edit2: having a few second thoughts on the API, I will probably change things again at the 0.6.0 release. Until a 1.0.0 is done I will keep adjusting as long as I feel I can improve it's ergonomics. :P
If someone could try it out anyway and give some review on it would be helpful too.
Another change I want to make is remove the "Wrapped" functions from the API and instead just let it be known that any fancy text that have some non-infinite width set is wrappable.
Edit: ok, new
0.6.0 release: major API changes to make parameter order more consistent, removed redundant stuff. I think I am more happy now regarding API, will look into adding more interesting stuff soon.
Just updated the markdown readme with the documentation in GitHub too in case someone prefers reading that instead of the forums. The forums docs are my first thing to update always though.
Edit: new
0.7.0 release!
A few changes all around, biggest one is the addition of
FancySay and
FancySayTypedCharacter.FancySayvoid Character.FancySay( const string text, optional FancyConfig* config, optional int width, optional Fancy9Piece* f9p );
A Say alternative that support fancy strings
Character.FancySayTypedvoid Character.FancySayTyped( const string text, optional FancyConfig* config, optional int width, optional Fancy9Piece* f9p );
A Say alternative that support fancy strings, it types it instead of readily drawing
Edit: new
0.7.2 release, lots of little fixes!
Edit: new
0.7.3 release, small fixes to FancySayTyped when changing f9p between different values and null.
This is incredibly cool! Fiddling with it now. :)
You will be the first user so please ask about any issues or questions! Also I haven't tested with anti aliasing or high resolution fonts so if something appears just shoot me a message here or somewhere and I will look into it!
This time I took a lot of care in the module code to make things both modular and easy to understand (for me) so I hope I can tackle problems that appear much faster. 8-)
Really cool work,
@eri0o. One idea: It would be nice if users could provide colors in hex-RGB format as well as AGS color number:
"[c:#ff88aa]Light pink color text[/c]"
I would like to criticize the current API of this module, because, in my opinion, it is giving too many high-level features (like text typing and overriding Say), but at the same time is hiding most basic things that should have been exposed instead, in order to give maximal customization capabilities. Perhaps, the intent for the existing API was to quickly deliver most common actions for the users. But in the long term this may lead to this module doing too much job itself and the "text fancifying" system being less flexible.
The way I look at this fancy text problem, is that it has 3 distinct steps:
1. Parsing the input string, and building a list of tokens, or "text items", which describe how the text piece should look like. If I understand correctly, this is currently done using FancyTextToken struct.
2. (OPTIONAL) Applying a chain of filters / transforms to the list of "text items".
3. Drawing the post-filter list of "text items" using chosen method (drawing surface, constructing overlays, or whatnot).
In my opinion, ideally, these tasks should be separated, and exposed as a low-level API of this module.
When using this low-level API the user would do something like this (in pseudo code):
FancyTextTokenList *fancy_list = Fancy.Tokenize(input_str);
// do some custom stuff with this fancy_list of tokens
// then draw it using provided drawing methods:
DrawFancyList(drawsurf, fancy_list, x, y, <...>);
// etc
The high-level API may be reorganized to use same methods internally (maybe they already do, I did not delve too much into the implementation). I might also suggest to move them out to a separate "helper" module, but that's also may be a matter of personal preference.
My main point is that if you give the token list to the user, then users may write custom processing over these tokens in a way they prefer. They may change their properties dynamically, specify how much of them should be drawn anytime (custom typing effect), and so forth. They may even write their own custom tokenizers which produce your FancyTextTokens as an output. Or implement their own "text drawers".
On the other hand, I don't think it will be possible for you to predict all kinds of effects people may want, so trying to have them inside the module itself may be counter-productive.
UPDATE:
I should throw in few examples of that those "custom processing" / filters could be:
1. Text wrapping - is a filter. It takes a list of tokens on input and inserts "linebreaks" into them. I think that your module is already doing this as a separate operation internally, which is very good.
2. Text typing - is either a filter, or drawing controller. I suppose this may be implemented in various ways. Either tell the text drawer the range of text positions, or insert a "BREAK" token into the list.
Random examples of user-made filters:
1. Blinking part of text: toggles particular token on and off (may be also made by switching color between foreground and background).
2. Change text's color using a custom Tween.
3. Wiggle particular token around (change x,y position) using a custom Tween.
Hey
@Snarky thanks, I will think about it but for now you can alias an AGS color and simply write it's alias. I think when (when?) ags4 comes around supposedly we will be using 32-bit colors instead and I will be able to add this more easily - and cleanly.
About the criticism
@Crimson Wizard , it's fair, I think I won't do it now though. The module was written in a week and had zero usage so far and exposing these internals would mean I have to change the API there less often and I simply don't know enough about it yet.
In terms of ergonomics, exposing the tokens as mentioned really requires ags4 (managed structs in managed structs and arrays), otherwise it would require brittle hacks (see the initial version that used a global array and token count in first element (https://github.com/ericoporto/fancy/blob/14d6f853d9a3d42aaa93f40e3af6357fc2722e69/fancy_demo/fancy.asc#L98)) - ideally, besides the tokens it needs an array to put them, the number of tokens and some sort of header where I can anotate things.
There's also something that I am using tokens that are really closer to the representation of what thing will be draw at the end, this means they have some information that comes from additional information at the start - as example, if no font is specified, which font should I use? - because they affect the token - what is the size on screen of the token? I feel the proposal you have would work better if the tokens were closer to the input information - my thinking is that the draw step is the most expensive one, so I want to reduce amount of work there.
Anyway, for now I will work in fixing module issues and adding features until a 1.0.0 version can be made and then later in a future 2.0.0, where hopefully ags4 is closer to release, then do a complete refactor of the module with the new scripting capabilities and exposing of the internals with a nice API.
new
0.7.5 release.
- fix invisible text issue when FancySay/Typed is called in a Room that scrolls
- fix FancySay/Typed crashes with null issue if not global Fancy config is set before
- fix speech lines are printed on screen when using FancySay/Typed
Just wanted to show my appreciation for this project, as I failed twice to create something similar. :-D Also the last module offering something similar was Hypertext by SSH and that one is from the Stone Age of AGS.
Awesome work,
@eri0o !
I've been checking the implementation, for the reasons why do you have to set position of a blocking speech overlay, with the coordinate conversion hack.
I wonder, would it be more convenient if, instead of changing the blocking speech overlay, you would make it transparent and create a new overlay with the custom graphic and in the wanted place instead? Then no coordinate hacks would be necessary.
The original overlay must only persist to keep character "speaking", but it does not have to be visible.
Played with this module a bit today and am mostly very happy with the results, but I found a possible bug: If I'm using Sierra-style speech with portrait, than using FancySayTyped means I have to specify a width for the text box, otherwise it can overlap with the portrait (especially if the portrait image is large and the portrait alignment is set to "right" in general settings. Setting a width does allow me to keep it from overlapping though.
I'm trying to figure out how to get Fancy to work with my custom speech GUI (so I can implement italics). The way it currently works is that two GUIs appear at the bottom of the screen, one with the speaker's name, one with their speech. The latter is a copy of the character's actual speech, which uses an invisible font.
Here's a stripped-down version of the function:
void Speak(this Character*, String message) {
Label3.Text=this.Name; //char's name appears on label Label3, on gSpeechLabel
gSpeechLabel.Visible=true;
labeltext.Height = GetTextHeight(message, labeltext.Font, labeltext.Width); //labeltext is the label where character's speech appears, on gLabel
gLabel.Width = labeltext.Width + (2*trans_textmargin);
gLabel.Height = labeltext.Height + (4*trans_textmargin);
labeltext.Text = message;
gLabel.X = (System.ViewportWidth-gLabel.Width)/2;
gLabel.Y = (System.ViewportHeight-gLabel.Height)-trans_guibottommargin;
gLabel.Visible = true;
gSpeechLabel.Y=gLabel.Y;
this.Say(message); //character speaks in an invisible font (so their mouth moves, using lip sync)
gLabel.Visible = false;
gSpeechLabel.Visible=false;
}
EDIT: I've added this right before the this.Say line (thanks, Crimsonwizard!), and although the text displays, the formatting won't work. It remains visible as [f:5]...[/f].
DynamicSprite *dspr = DynamicSprite.Create(labeltext.Width, labeltext.Height);
DrawingSurface *ds = dspr.GetDrawingSurface();
ds.DrawFancyString(labeltext.X, labeltext.Y, message);
ds.Release();
Overlay *text_over = Overlay.CreateGraphical(labeltext.X, labeltext.Y, dspr.Graphic);
text_over.ZOrder = gLabel.ZOrder + 1; // position overlay right above speech gui
EDIT 2: Thanks to Crimsonwizard, my function is almost working. My only problem at the moment is centering the text, As you can see, there are some lines declaring the text to be centered, but this doesn't seem to be working. It appears crushed against the left side of the GUI, and there is also a large gap between the bottom of the text and the bottom of the GUI which I can't seem to get rid of.
void Speak(this Character*, String message) {
Label3.Text=this.Name; //char's name appears on label Label3, on gSpeechLabel
gSpeechLabel.Visible=true;
labeltext.Height = GetTextHeight(message, labeltext.Font, labeltext.Width); //labeltext is the label where character's speech appears, on gLabel
gLabel.Width = labeltext.Width + (2*trans_textmargin);
gLabel.Height = labeltext.Height + (4*trans_textmargin);
labeltext.Text = message;
gLabel.X = (System.ViewportWidth-gLabel.Width)/2;
gLabel.Y = (System.ViewportHeight-gLabel.Height)-trans_guibottommargin;
gLabel.Visible = true;
gSpeechLabel.Y=gLabel.Y;
DynamicSprite *dspr = DynamicSprite.Create(labeltext.Width, labeltext.Height, true);
DrawingSurface *ds = dspr.GetDrawingSurface();
FancyConfig* config = FancyConfig.Create();
config.TextAlign = eAlignCenter;
ds.DrawFancyString(0, 0, message, config);
ds.Release();
Overlay *text_over = Overlay.CreateGraphical(gLabel.X + labeltext.X, gLabel.Y + labeltext.Y, dspr.Graphic);
text_over.ZOrder = gLabel.ZOrder + 1; // position overlay right above speech gui
labeltext.Visible = false; // hide the label
this.Say(message); //character speaks in an invisible font (so their mouth moves, using lip sync)
gLabel.Visible = false;
gSpeechLabel.Visible=false;
For text alignment changes you need two things
- specify the alignment in FancyConfig
- have non-infinite width
When you use DrawFancyString, you will need to pass the width parameter. I think you also need to pass the fancyconfig there since the width parameter for that function is at right.
How exactly do I do that? Right now I've got this in game_start:
Fancy.FancyConfig.Font = eFontNormal;
Fancy.FancyConfig.TextAlign=eAlignCentre;
Fancy.Fancy9Piece = Fancy9Piece.CreateFromTextWindowGui(gTxtwindow);
And I changed FancyConfig* config = FancyConfig.Create();
in the function to
FancyConfig* config = FancyConfig.Create(eFontNormal, 15, 0, 0, eAlignMiddleCenter);
but so far, nothing's changed. Also, how do I implement the non-infinite width? I wasn't sure how to do that from the single mention of it in the manual.
Thanks for the reply!
If you read the DrawFancyString method, it looks like:
void DrawingSurface.DrawFancyString(int x, int y, const string text, optional FancyConfig* config, optional int width);
There's "optional int width" as the last parameter.
Okay, I didn't realize I had to modify an existing line. The relevant part of my function now looks like this, but the text is still aligned to the left. Making the max width smaller pushes it left even more.
DynamicSprite *dspr = DynamicSprite.Create(labeltext.Width, labeltext.Height, true);
DrawingSurface *ds = dspr.GetDrawingSurface();
FancyConfig* config = FancyConfig.Create(eFontNormal, 15, 0, 0, eAlignMiddleCenter);
config.TextAlign = eAlignCenter;
ds.DrawFancyString(0, 0, message, config, 700);
ds.Release();
Overlay *text_over = Overlay.CreateGraphical(gLabel.X + labeltext.X, gLabel.Y + labeltext.Y, dspr.Graphic);
text_over.ZOrder = gLabel.ZOrder + 1; // position overlay right above speech gui
labeltext.Visible = false; // hide the label
I think there may be a mistake in the module.
If you look in fancy.asc, function called "_draw_tokens", there's a line:
int text_width = fs.TextWidth;
It probably should be:
int text_width = fs.MaxTextWidth;
Hmm...that definitely changed things. Unfortunately, the text still isn't quite centered and gets cut off by the right edge of the GUI. There's still a big gap between the bottom of the text and the bottom of the GUI as well.
EDIT: Crimson Wizard was able to walk me through the various minutiae that resulted in the effect I was looking for. I've included the end result below, In case anyone finds it useful:
void SpeakFancy(this Character*, String message) {
Label3.Text=this.Name;
gSpeechLabel.Visible=true;
labeltext.Height = GetTextHeight(message, labeltext.Font, labeltext.Width);
gLabel.Width = labeltext.Width + (3*trans_textmargin);
gLabel.Height = labeltext.Height + (4*trans_textmargin);
labeltext.Text = message;
speech_label.Y=gLabel.Y; //speech_label is an invisible label on gLabel, but it helps with vertical alignment
gLabel.X = (System.ViewportWidth-gLabel.Width)/2;
gLabel.Y = (System.ViewportHeight-gLabel.Height)-trans_guibottommargin;
gLabel.Visible = true;
gSpeechLabel.Y=gLabel.Y;
this.Say(message);
DynamicSprite *dspr = DynamicSprite.Create(gLabel.Width, gLabel.Height);
DrawingSurface *ds = dspr.GetDrawingSurface();
surf.DrawFancyString(0, 0, message);
ds.Release();
Overlay *text_over = Overlay.CreateGraphical(gLabel.X, gLabel.Y, dspr.Graphic);
text_over.ZOrder = gLabel.ZOrder + 1; // position overlay right above speech gui
gLabel.Visible = false;
gSpeechLabel.Visible=false;
}
I think that's fine since the width is adjusted before when it calculates the word wrapping.
@Akril15 , from your code I don't understand what you are trying to do, afaict if you don't maintain a global dynamic sprite and overlay I can't understand how they would show on screen.
We figured this out on Discord, using DynamicSprite.CreateFromFancyString instead, which allows to know the width and height occupied by the text, which in turn lets to align the resulting text on the background as the user wants.
(somehow this reminds me SDL_ttf which creates a bitmap with the text)
Quote from: eri0o on Mon 19/05/2025 02:55:29I think that's fine since the width is adjusted before when it calculates the word wrapping.
If you are referring to the horizontal alignment, then it does not center it in the provided "max width", but (apparently) longest line's width? This makes the whole text aligned to some seemingly random horizontal point, which is not what a user would expect.
Quote from: eri0o on Mon 19/05/2025 02:55:29@Akril15 , from your code I don't understand what you are trying to do, afaict if you don't maintain a global dynamic sprite and overlay I can't understand how they would show on screen.
There's a character.Say call right after that, which blocks the game and keeps created overlay displayed.
The code overall does following:
- displays a background GUI with some extra elements, such as character's name.
- creates a dynamic sprite with a Fancy text on it, makes a overlay and aligns over the background GUI.
- calls character.Say, so that the default lip-sync (and maybe voice) could work, and game blocks until speech is done.
Uhm, I think then it could be if MaxTextWidth is infinite then it uses TextWidth, otherwise it uses MaxTextWidth.
Quote from: Crimson Wizard on Mon 19/05/2025 03:48:11If you are referring to the horizontal alignment, then it does not center it in the provided "max width", but (apparently) longest line's width? This makes the whole text aligned to some seemingly random horizontal point, which is not what a user would expect.
That sounds correct to me. Consider the case of a textbox that is resized to fit the text content: you don't want to center the text around the halfway point of the "max width," but around the halfway point of the actual longest line width.
Quote from: Snarky on Mon 19/05/2025 16:59:59That sounds correct to me. Consider the case of a textbox that is resized to fit the text content: you don't want to center the text around the halfway point of the "max width," but around the halfway point of the actual longest line width.
There are 2 different cases:
Case 1. When the text is being drawn on a prepared surface of the fixed-sized text box. This is the case of DrawingSurface.DrawFancyString which we tried first, and that's what I reported above. In such case I expect it to center relative to the "max width" that I pass; because otherwise it does not make any sense. I do not have a way to know the width of the longest line
beforehand, so that I could adjust the x position.
Compare it with DrawingSurface.DrawStringWrapped, which wraps in the "max width":
https://adventuregamestudio.github.io/ags-manual/DrawingSurface.html#drawingsurfacedrawstringwrapped
Quotewidth is the width of the virtual textbox enclosing the text, and is the point that the text will wrap at. You can use the alignment parameter to determine how the text is horizontally aligned.
Case 2. When the text is placed on a exclusive sprite, resized to fit just the text, which one may align with the text box as sees fit. This is the case of DynamicSprite.CreateFromFancyString, which we used in the end.
I will need to find some time to play with this but I think my blunt of clause should work.
There are definitely 2 opposite use cases of drawing a text:
1. When you draw a text on a prepared space, where the text must fit in that space.
2. When you draw a text (maybe limiting by some width), but then adjust the "space" to the text.
DrawingSurface API suits for the first purpose, and I could not figure out if it's possible to use for the second, unless it's an internal call where you already precalculated text size. But when I think more about this, there's another issue with DrawFancyString, as well as AGS own DrawStringWrapped: there's no way to limit by height.
And DrawingSurface does not expose "Clipping" feature either, which seems like an oversight.
Which means that if one needs to limit an output text by height, then they either have to get a precalculated text size first (Fancy module seem to have a FancyState for this, but it does not expose this atm, unless I missed anything), or draw on a intermediate sprite first, and then manipulate that sprite (crop or whatnot).
Hi, I'm new to AGS. I tried using your module, but whenever I try to save/run the game I get this error:"fancy.ash(33): Error (line 33): '2' is already defined". Apologies for the inconvenience, but do you know what might be causing this?
You have a macro for one of the following letters T, B, L, R, or for the words TL, TR, BL, BR, CBG.