MODULE: Fancy 0.7.5

Started by eri0o, Mon 15/04/2024 19:53:49

Previous topic - Next topic

eri0o

Fancy version 0.7.5

Get Latest Release fancy.scm | GitHub Repo | Project with Demo!



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.





Usage

I will improve this soon, for now some small examples

Using a regular drawing surface:

Code: ags
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:

Code: ags
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 API
Spoiler
Script Extensions

DrawingSurface.DrawFancyString
Code: ags
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
Code: ags
DynamicSprite* DynamicSprite.CreateFromFancyString(const string text, optional FancyConfig* config, optional width);
Create a sprite with the text of a fancy string

DynamicSprite.CreateFromFancyTextBox
Code: ags
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
Code: ags
void Character.FancySay( const string text, optional FancyConfig* config, optional int width, optional Fancy9Piece* f9p );
A Say alternative that support fancy strings

Character.FancySayTyped
Code: ags
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
Code: ags
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
Code: ags
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
Code: ags
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
Code: ags
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
Code: ags
static Fancy9Piece* Fancy9Piece.CreateFromTextWindowGui(GUI* text_window_gui);
Create a 9 piece fancy compatible from a Text Window GUI.

Fancy9Piece.CreateFrom9Sprites
Code: ags
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
Code: ags
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
Code: ags
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
Code: ags
attribute String FancyTextBase.Text;
Sets the text content for the fancy text, this is where the parsing of the text happens.

FancyTextBase.PlainText
Code: ags
attribute readonly String FancyTextBase.PlainText;
Get the set text without tags.

FancyTextBase.Draw
Code: ags
void FancyTextBase.Draw(DrawingSurface* surf);
Draws the fancy text on the specified drawing surface.

FancyTextBase.FancyConfig
Code: ags
attribute FancyConfig* FancyTextBase.FancyConfig;
Property to set the Fancy Text rendering configuration.


FancyTextBox

FancyTextBox.CreateTextBoxSprite
Code: ags
DynamicSprite* FancyTextBox.CreateTextBoxSprite();
Create a sprite of a textbox with a fancy string using the configured 9-piece

FancyTextBox.Fancy9Piece
Code: ags
atrribute Fancy9Piece* FancyTextBox.Fancy9Piece;
Setup the 9-piece for the Text Box creation.


FancyTypedText

FancyTypedText.Clear
Code: ags
void FancyTypedText.Clear();
Clears all text and resets everything for typed text.

FancyTypedText.Start
Code: ags
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
Code: ags
void FancyTypedText.Skip();
Skips all remaining typing of the text.

FancyTypedText.Tick
Code: ags
void FancyTypedText.Tick();
Updates the typed text state, advancing it by a single tick.

FancyTypedText.DrawTyped
Code: ags
void FancyTypedText.DrawTyped(DrawingSurface* surf);
Draws the typed text in it's current state.

FancyTypedText.CreateTypedSprite
Code: ags
DynamicSprite* FancyTypedText.CreateTypedSprite();
Create a sprite of the text being typed.

FancyTypedText.IsTextBeingTyped
Code: ags
attribute readonly bool FancyTypedText.IsTextBeingTyped;
True if a text is being typed in the FancyTypedText, and not finished.
[close]




License
This module is created by eri0o is provided with MIT License, see LICENSE for more details.

eri0o

#1
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.
[close]

eri0o

#2
Added a new 0.3.1 release adding some maybe useful extensions:

Spoiler
DrawingSurface.DrawFancyText
Code: ags
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
Code: ags
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
Code: ags
DynamicSprite* DynamicSprite.CreateFromFancyText(int color, FontType font, const string text);
Create a sprite with the text of a fancy string

Overlay.CreateFancyTextual
Code: ags
Overlay* Overlay.CreateFancyTextual(int x, int y, int width, FontType font, int color, const string text);
Creates a screen overlay from fancy text
[close]

Added the above in the documentation in the first post too!

I think I want to try to tackle 9-piece text box next.

eri0o

#3
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 SpriteTop SpriteTop Right Sprite
Left SpriteCenter Background SpriteRight Sprite
Bottom Left SpriteBottom SpriteBottom 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
Code: ags
static Fancy9Piece* Fancy9Piece.CreateFromTextWindowGui(GUI* text_window_gui);
Create a 9 piece fancy compatible from a Text Window GUI.

Fancy9Piece.CreateFrom9Sprites
Code: ags
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
Code: ags
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
Code: ags
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

[close]

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.Fancify
Code: ags
void 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.UnFancify
Code: ags
void 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.
[close]

eri0o

#4
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.
[close]

Edit: new 0.7.0 release!

A few changes all around, biggest one is the addition of FancySay and FancySayTyped

Character.FancySay
Code: ags
void Character.FancySay( const string text, optional FancyConfig* config, optional int width, optional Fancy9Piece* f9p );
A Say alternative that support fancy strings

Character.FancySayTyped
Code: ags
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

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.

Dave Gilbert

This is incredibly cool! Fiddling with it now. :)

eri0o

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-)

Snarky

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]"

Crimson Wizard

#8
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):
Code: ags
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.

eri0o

#9
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) - 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.

eri0o

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

abstauber

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 !

SMF spam blocked by CleanTalk