Fixed Position Textbox Workaround

Started by dad d, Wed 19/02/2025 22:16:56

Previous topic - Next topic

dad d

I am making a first person detective adventure game using the VerbCoin template for interactions and so far I've used primarily hotspots and Display() for the text you get as you inspect objects in the rooms. 

My issue I can't solve comes into my trying to emulate the style of the series I've based my game on, where the text box is in a fixed position at the bottom of the screen like a visual novel every time your character thinks to himself.  From scrubbing the forums I've gathered that a true fixed position & size textbox just isn't doable, but I'm trying to find the workaround that keeps the workflow of designing the rooms as simple as possible.

My first thought is wondering if there's a way to execute a function every time Display() is used which just displays a different piece of UI and takes the text from the Display command to put in its text field instead.

The values I'm trying to use are textbox size 640x109 and position 0,339.

Crimson Wizard

#1
Quote from: dad d on Wed 19/02/2025 22:16:56From scrubbing the forums I've gathered that a true fixed position & size textbox just isn't doable

There have been a number of discussions on how to make a visual-novel style in AGS. The fixed position textbox is doable with GUI + Label, but you have to write your own custom speech function that sets/resets a text, and not use Display function at all.

Searching for "visual novel" on tech forums, these are couple of threads that come up first:
https://www.adventuregamestudio.co.uk/forums/beginners-technical-questions/visual-novel-style-dialogues/
https://www.adventuregamestudio.co.uk/forums/beginners-technical-questions/implementing-character-busts-during-dialogue/

the first linked thread have links to 3 more related threads. The second contains some actual script examples.

dad d

I had been through a couple of these threads, but my wording probably wasn't specific enough on my goal: Even if it is via a custom command, I am looking to just display a single message textbox that disappears on click - not initiate a dialog sequence or have a character portrait displayed.  So maybe the question then becomes how to adapt one of the examples in those threads to that simpler format?

Crimson Wizard

#3
Quote from: dad d on Thu 20/02/2025 03:02:45I had been through a couple of these threads, but my wording probably wasn't specific enough on my goal: Even if it is via a custom command, I am looking to just display a single message textbox that disappears on click - not initiate a dialog sequence or have a character portrait displayed.  So maybe the question then becomes how to adapt one of the examples in those threads to that simpler format?

Well, there's Overlay.CreateTextual that creates an overlay with text at an exact position with an exact width (height is still dynamically calculated from the length of the text, but text will be clamped to the set width):
https://adventuregamestudio.github.io/ags-manual/Overlay.html#overlaycreatetextual

If you have a speech style set to the Sierra-style, then Overlay will automatically use the TextWindow GUI, the one that draws tiled borders around the text. If you normally have a different speech style, then you can still force Overlay to use that by temporarily switching this setting before creating overlay and then switching back, like:

Code: ags
Speech.Style = eSpeechSierraWithBackground;
Overlay *over = Overlay.CreateTextual( ...your params here... )
Speech.Style = whatever it was before;

This is bit ugly, but might work. You can wrap the above snippet into a custom function, and call it, passing text as an argument.

What remains is Overlay's removal. For that you need to:
* store created Overlay's pointer in a global variable;
* check if that pointer is not null in on_mouse_click, and call Remove() function from it, then set variable to null.

So in the end the code may be
Code: ags
Overlay *TextBoxOverlay;
void DisplayTextBox(String text)
{
     eSpeechStyle old_style = Speech.Style;
     Speech.Style = eSpeechSierraWithBackground;
     TextBoxOverlay = Overlay.CreateTextual(X, Y, WIDTH, FONT, COLOR, text); // replace X,Y etc with whatever you need
     Speech.Style = old_style;
}
Code: ags
function on_mouse_click(MouseButton button, int x, int y)
{
     if (TextBoxOverlay != null)
     {
          TextBoxOverlay.Remove();
          TextBoxOverlay = null;
          return;
     }

     // the rest of mouse click
}

And then you can display these messages like:
Code: ags
DisplayTextBox("bla bla bla");

dad d

That seems just about perfect other than no height set!  Is there anything hackey to get around that, like say building the command to display the text on an invisible textbox and also toggling visibility on a fixed size UI piece beneath it?

That, or, is there a way to get the desired result with more backend code, but have the end result still as simple as
Code: ags
DisplayTextBox("bla bla bla")
as you put it

Crimson Wizard

#5
Quote from: dad d on Thu 20/02/2025 05:45:17That seems just about perfect other than no height set!  Is there anything hackey to get around that, like say building the command to display the text on an invisible textbox and also toggling visibility on a fixed size UI piece beneath it?

There are two ways:
1. Create dynamic sprite, of any size you want, draw text and borders on it, and create graphical overlay instead with the dynamic sprite attached.
https://adventuregamestudio.github.io/ags-manual/DynamicSprite.html
https://adventuregamestudio.github.io/ags-manual/DrawingSurface.html

2. Use GUI + Label. Since you want a fixed size always, then you can draw the whole border on a single sprite, and assign as a GUI's BackgroundGraphic. Set the GUI's Visible property to false in properties to make it hidden on start.
Maybe also set "Clickable" to false.
Then the code becomes
Code: ags
void DisplayTextBox(String text)
{
    lblMessage.Text = text;
    gTextMessage.Visible = false;
}

function on_mouse_click(MouseButton button, int x, int y)
{
    if (gTextMessage.Visible)
    {
          gTextMessage.Visible = false;
          return;
    }

    // the rest of mouse click
}

Where "gTextMessage" is the name of GUI and "lblMessage" is the name of the label.



If you need whole game to block during this message, then the blocking variant of this function becomes
Code: ags
void DisplayTextBoxBlocking(String text)
{
    lblMessage.Text = text;
    gTextMessage.Visible = false;
    WaitInput(eInputAny);
    gTextMessage.Visible = false;
}

dad d

OK I think that will do the ticket exactly, thanks for all the help!

Out of curiosity just as I'm learning AGS, what's the difference between a non-textbox GUI that you add a texbox layer to compared to adding a label to it?  I had actually tried doing the former at one point while experimenting.

Crimson Wizard

#7
Quote from: dad d on Fri 21/02/2025 05:26:37Out of curiosity just as I'm learning AGS, what's the difference between a non-textbox GUI that you add a texbox layer to compared to adding a label to it?  I had actually tried doing the former at one point while experimenting.

I am not sure if I understand the question, what do you call a "textbox layer"?
There are 2 types of GUI in AGS:
1. normal gui that can display any combination of controls
2. textwindow gui that cannot have any controls, but lets configure 9-tile pieces that form borders and background. Some functions use this kind of gui to display texts: Display (and variants), Character.Say and Overlay.CreateTextual (only if Speech.Style is "Sierra-style with background").

dad d

Ah, as in when you use the tool "Add GUI Textbox" as opposed to "Add GUI Label" or "Add GUI ListBox" when editing a normal GUI

Crimson Wizard

Quote from: dad d on Sat 22/02/2025 01:07:08Ah, as in when you use the tool "Add GUI Textbox" as opposed to "Add GUI Label" or "Add GUI ListBox" when editing a normal GUI

The "TextBox" is a text input control, it lets player to type text into the field.
Label displays the fixed text set in its Text property.

dad d

Okay that makes sense, I hadn't implemented anything to bring it up so I wasn't catching that organically.

On implementing the code above,  I added the portion to the on_mouse_click section of the global script, but when I add the rest to the top of global, I still get an error calling for DisplayTextBox("Sentence here") [or to be precise I named it Message() ] in my room script.  Do I have to define the function in each room individually?

Crimson Wizard

Quote from: dad d on Sat 22/02/2025 01:46:49On implementing the code above,  I added the portion to the on_mouse_click section of the global script, but when I add the rest to the top of global, I still get an error calling for DisplayTextBox("Sentence here") [or to be precise I named it Message() ] in my room script.  Do I have to define the function in each room individually?

You need to declare the function's import in the global header, the process is explained in the manual:
https://adventuregamestudio.github.io/ags-manual/ImportingFunctionsAndVariables.html

Simply put following to the GlobalScript header:
Code: ags
import void Message(String text);
(or whatever the function declaration is)

dad d

Perfect, had to change the one visible to true, but with that I've got it working.  Apologies not having read that out of the manual, I haven't ventured into custom commands outside of this solution.  Now I feel set though! (nod)

SMF spam blocked by CleanTalk