MODULE: SpeechBubble v0.8.0

Started by Snarky, Sun 03/12/2017 11:28:04

Previous topic - Next topic

bx83

Will this slow it down, have the old compiler etc. and basically be like using 3.4.1?

Crimson Wizard

Quote from: bx83 on Sun 28/07/2019 09:33:49
Will this slow it down, have the old compiler etc. and basically be like using 3.4.1?

It simply enables old script commands. They are hidden but still there.

bx83

Cool, got it, changed and works.

I have a question for Snarky (or anyone if they know the answer).
SpeechBubble has a border of custom thickness and colour to text in the speech bubble. How do I do this elsewhere in the game?
I have an item description as a GUI with a label containig '@OVERHOTSPOT@', positioned at the top-centre of screen. How can I make this text with a 2px thickness black border, just as text in SpeechBubble?

Snarky

SpeechBubble uses custom code to draw the outline (the function drawStringWrappedOutline()). To get this effect elsewhere in the game you would have to copy that code and draw the text yourself onto a dynamic sprite (shown as an overlay, GUI, etc.).

The AGS devs are currently working on incorporating something similar into the engine, but I'm not sure whether that covers all text or only speech.

fernewelten

#64
Quote from: bx83 on Sun 18/08/2019 11:31:35
… colour to text in the speech bubble. How do I do this elsewhere in the game?

As concerns the colour of the outline in standard AGS without modules, see game.text_shadow_color (look for "game. variables" in the index of the online doc). You'll need to set the variable immediately before the  Say() or  Display() or whatever command and reset it immediately afterwards because this variable pertains to all outlines -- keep it at a certain value and the outlines will be coloured that way everywhere.

For my recent game Mamma mia Winter Ice Cream Mayhem, I've patched the Engine at the same place that the new code for variable outline thickness is going to go. I got thick outlines in the status line, too. So I think that the thick outlines will come everywhere, not only in Say().

bx83

To be clear, I mean the black colour around the font (the Stroke, for any photoshop aficionados), that fits the front perfectly like tight fitting clothing, not the rounded square frame around the speech bubble. Does anyone know which functions constitute this in speechbubble?

Snarky

Like I said, drawStringWrappedOutline().

bx83

Okay, I've attempted to copy this function (now called oulinedMessage) from speechbubble, and transplant my own outlineDrawStringWrappedAA into the function:

I've reduced the number of arguments, and supplanted default values hard-coded - these items will never change:

GlobalScript.asc
Code: ags

// Draw a string with outline (make sure the canvas has at least outlineWidth pixels on each side of the string)
function outlinedMessage(this DrawingSurface*, int x, int y, int width, String message)
{
  
  // This is what we draw on (because we might need to copy with transparency)
  DynamicSprite* outlineSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* outlineSurface = outlineSprite.GetDrawingSurface();
  
  // This holds multiple horizontal copies of the text
  // We copy it multiple times (shifted vertically) onto the outlineSprite to create the outline
  DynamicSprite* outlineStripSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* outlineStripSurface = outlineStripSprite.GetDrawingSurface();
  
  // This is our "text stamp" that we use to draw the outline, we copy it onto outlineStripSprite
  DynamicSprite* textSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* textSurface = textSprite.GetDrawingSurface();
  
  // Draw our text stamp
  textSurface.DrawingColor = 682;
  textSurface.DrawStringWrapped(x, y, width, 1, 2, message);
  textSurface.Release();
  
  // Draw Circular outline
  int maxSquare = 2*2+1; // Add 1 for rounding purposes, to avoid "pointy corners" 
  int maxWidth = 0;
  outlineStripSurface.DrawImage(0, 0, textSprite.Graphic);
  
  // We loop from top and bottom to the middle, making the outline wider and wider, to form circular outline
  for(int i = 2; i > 0; i--)
  {
    // Here's the circular calculation...
    while(i*i + maxWidth*maxWidth <= maxSquare)
    {
      // Increase width of the outline if necessary
      maxWidth++;
      outlineStripSurface.DrawImage(-maxWidth, 0, textSprite.Graphic);
      outlineStripSurface.DrawImage(maxWidth, 0, textSprite.Graphic);
      outlineStripSurface.Release();
      outlineStripSurface = outlineStripSprite.GetDrawingSurface();
    }
    // Draw outline strip above and below
    outlineSurface.DrawImage(0, -i, outlineStripSprite.Graphic);
    outlineSurface.DrawImage(0, i, outlineStripSprite.Graphic);
  }
  // Finally the middle strip
  outlineSurface.DrawImage(0, 0, outlineStripSprite.Graphic);
  
  textSprite.Delete();
  outlineStripSurface.Release();
  outlineStripSprite.Delete();
  
  /// Now draw the text itself on top of the outline
  outlineSurface.DrawingColor = this.DrawingColor;
  
  //BEGIN OUTLINE DRAW STRING WRAPPED AA
  
  DynamicSprite* atextSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* atextSurface = atextSprite.GetDrawingSurface();
  atextSurface.DrawingColor = this.DrawingColor;
  atextSurface.DrawStringWrapped(x, y, width, 1, 2, message);
  atextSurface.Release();
  this.DrawImage(0, 0, atextSprite.Graphic, 0);
  atextSprite.Delete();
  
  //END OUTLINE DRAW STRING WRAPPED AA
  
  outlineSurface.Release();
  // ... And copy it onto our canvas
  this.DrawImage(100, 100, outlineSprite.Graphic, 0);
  outlineSprite.Delete();
}


GlobalScript.ash:
Code: ags

import function outlinedMessage(this DrawingSurface*, int x, int y, int width, String message);


in room2.asc:
Code: ags

function room_AfterFadeIn()
{
  ...

  outlinedMessage(100, 100, 100, "checking this out");
}


But I get the error:
Failed to save room room2.crm; details below
room2.asc(5): Error (line 5): Undefined token 'outlinedMessage'

oulinedMessage() is at the top of GlobalScript.asc
The definition is at the top of the function definitions in GlobalScript.ash (after some #define's and enums)

...wtf has happened? It must be something incredibly obvious, but I can't see how room2 can't see GlobalScript.

Snarky

Well, that's because it's a "this" extender function, which has to be called on a DrawingSurface.

I'll take a look at adapting it to your needs.

Snarky

OK, here you go:

Keep drawStringWrapped() as it is. Just add this line to SpeechBubble.ash so you can call it from outside the module:

Code: ags
import void drawStringWrappedOutline(this DrawingSurface*, int x, int y, int width, TextOutlineStyle outlineStyle, FontType font,  Alignment alignment, String message, int transparency, int outlineColor, int outlineWidth);


Create a GUI that will display the mouseover text. It should be empty, without anything on it. Set the size and position to fit any text it will display. Make the background and border transparent, and make sure it's not clickable. I've named mine gFloatingText, but you can use any name.

Add this code to the script of the module that handles your UI, or as its own module:

Code: ags
// Replace these values with whatever you want
#define FLOATING_TEXT_GUI gFloatingText
#define FLOATING_TEXT_FONT eFontfntSpeech
#define FLOATING_TEXT_OUTLINE_WIDTH 3
#define FLOATING_TEXT_COLOR 11
#define FLOATING_TEXT_OUTLINE_COLOR 0

String textContent;
DynamicSprite* sprtFloatingText;

void SetFloatingText(String floatingText)
{
  if(textContent == null)
    textContent = "";
  if(floatingText != textContent)
  {
    textContent = floatingText;
    if(sprtFloatingText == null)
      sprtFloatingText = DynamicSprite.Create(FLOATING_TEXT_GUI.Width, FLOATING_TEXT_GUI.Height, true);
    DrawingSurface* dsFloatingText = sprtFloatingText.GetDrawingSurface();
    dsFloatingText.Clear(COLOR_TRANSPARENT);
    if(!String.IsNullOrEmpty(textContent))
    {
      dsFloatingText.DrawingColor = FLOATING_TEXT_COLOR;
      dsFloatingText.drawStringWrappedOutline(FLOATING_TEXT_OUTLINE_WIDTH,
                                              FLOATING_TEXT_OUTLINE_WIDTH,
                                              FLOATING_TEXT_GUI.Width - 2*FLOATING_TEXT_OUTLINE_WIDTH,
                                              eTextOutlineRounded,
                                              FLOATING_TEXT_FONT,
                                              eAlignCentre,
                                              textContent,
                                              0,
                                              FLOATING_TEXT_OUTLINE_COLOR,
                                              FLOATING_TEXT_OUTLINE_WIDTH);
    }
    dsFloatingText.Release();
    FLOATING_TEXT_GUI.BackgroundGraphic = sprtFloatingText.Graphic;
  }
}


Now whenever you want to set the content of this "label" you just call SetFloatingText(). I'm not sure you can use the @OVERHOTSPOT@ symbol (actually, I'm pretty sure it won't work), but you can use Game.GetLocationName(mouse.x,mouse.y) in repeatedly_execute() â€" see the TwoClickHandler template for an example.

bx83

#70
Imported it all into global script/header. Set defines in header, put first 2 variables (line 3 and 4 in GlobalScript.asc) in function.
Works, compiles.

Does the GUI have an actual label control in it, or you're just referring to the whole thing as a label?

Also changed #define FLOATING_TEXT_FONT eFontfntSpeech from line 5 to eFontSpeech - this way it compiles. Correct usage? Otherwise it won't compile.

GetLocationName(mouse.x, mouse.y) gets a location's (Hotspot, Object, etc.) 'Description' (in the editor, in Properties window, under Appearance)?


Here's what I have:

GlobalScript.ash
Code: ags

//for gFloatingText 'description of what mouse is over'
...
//for gFloatingText 'description of what mouse is over'
#define FLOATING_TEXT_GUI gFloatingText
#define FLOATING_TEXT_FONT eFontSpeech
#define FLOATING_TEXT_OUTLINE_WIDTH 2
#define FLOATING_TEXT_COLOR 49664
#define FLOATING_TEXT_OUTLINE_COLOR 0
import function SetFloatingText(String floatingText);
import void drawStringWrappedOutline(this DrawingSurface*, int x, int y, int width, TextOutlineStyle outlineStyle, FontType font,  Alignment alignment, String message, int transparency, int outlineColor, int outlineWidth);
...


GlobalScript.asc
Code: ags

function SetFloatingText(String floatingText)
{
  String textContent;
  DynamicSprite* sprtFloatingText;
  
  if(textContent == null)
    textContent = "";
  if(floatingText != textContent)
  {
    textContent = floatingText;
    if(sprtFloatingText == null)
      sprtFloatingText = DynamicSprite.Create(FLOATING_TEXT_GUI.Width, FLOATING_TEXT_GUI.Height, true);
    DrawingSurface* dsFloatingText = sprtFloatingText.GetDrawingSurface();
    dsFloatingText.Clear(COLOR_TRANSPARENT);
    if(!String.IsNullOrEmpty(textContent))
    {
      dsFloatingText.DrawingColor = FLOATING_TEXT_COLOR;
      dsFloatingText.drawStringWrappedOutline(FLOATING_TEXT_OUTLINE_WIDTH,
                                              FLOATING_TEXT_OUTLINE_WIDTH,
                                              FLOATING_TEXT_GUI.Width - 2*FLOATING_TEXT_OUTLINE_WIDTH,
                                              eTextOutlineRounded,
                                              FLOATING_TEXT_FONT,
                                              eAlignCentre,
                                              textContent,
                                              0,
                                              FLOATING_TEXT_OUTLINE_COLOR,
                                              FLOATING_TEXT_OUTLINE_WIDTH);
    }
    dsFloatingText.Release();
    FLOATING_TEXT_GUI.BackgroundGraphic = sprtFloatingText.Graphic;
  }
}

...

function repeatedly_execute_always()
{
  if (Game.GetLocationName(mouse.x, mouse.y)!=null) {
    SetFloatingText(Game.GetLocationName(mouse.x, mouse.y));
  }
}



Surprise, due to something in my implementation of your function in my own code, the floating text doesn't appear. Putting cursor over trees, cave, quicksand etc. doesn't come up with anything - but this Hotspots are supposed to have descriptive Descriptions.
BUT it does come up for object->lookat, and also for speech (so far) - just a copy of the speechbubble.

Video:
https://bluekeystudios.com/img/floatingtext1.mp4

Note: When I change the text of the function to THIS (lines 5 and 6):

Code: ags

...
    if(!String.IsNullOrEmpty(textContent))
    {
      dsFloatingText.DrawingColor = FLOATING_TEXT_COLOR;
      dsFloatingText.drawStringWrappedOutline(683,    //1366/2 pixels left - dead centre
                                              15,     //15 pixels down
                                              FLOATING_TEXT_GUI.Width - 2*FLOATING_TEXT_OUTLINE_WIDTH,
                                              eTextOutlineRounded,
                                              FLOATING_TEXT_FONT,
                                              eAlignCentre,
                                              textContent,
                                              0,
                                              FLOATING_TEXT_OUTLINE_COLOR,
                                              FLOATING_TEXT_OUTLINE_WIDTH);
    }
    dsFloatingText.Release();
...


to try and position the floating text in the dead centre, the first error is gone, though now it's copying part of the DyanmicSprite for the custom dialogue functions:

https://bluekeystudios.com/img/floatingtext2.mp4


....SO it's a bug in the dynamic sprite used in SetFloatingText(), OR I've just gone and ruined the implementation of your code :D

Snarky

#71
Quote from: bx83 on Mon 26/08/2019 01:21:07
Does the GUI have an actual label control in it, or you're just referring to the whole thing as a label?

No, it should be empty. The text is drawn onto the GUI background image.

Quote from: bx83 on Mon 26/08/2019 01:21:07
Also changed #define FLOATING_TEXT_FONT eFontfntSpeech from line 5 to eFontSpeech - this way it compiles. Correct usage? Otherwise it won't compile.

Yes, you have to use whatever font name you have set up in the game, of course.

Quote from: bx83 on Mon 26/08/2019 01:21:07
GetLocationName(mouse.x, mouse.y) gets a location's (Hotspot, Object, etc.) 'Description' (in the editor, in Properties window, under Appearance)?

Correct. One of AGS's little naming inconsistencies.

The reason it's not showing up is that you've put these lines…

Code: ags
String textContent;
DynamicSprite* sprtFloatingText;


… inside of the SetFloatingText() function. They need to be outside of it, so that the variables persist between function calls. That's why I put them outside.

I think that's also why it shows a copy of the SpeechBubble text: right after the SetFloatingText() DynamicSprite gets destroyed, SpeechBubble creates another one, so that gets the same sprite index, which the GUI background has been set to.

Oh, and also, you shouldn't mess with the positioning code. It's set to be centered and top-aligned within the GUI (with margins just wide enough to fit the outline): You're then shifting it half a screen right, which means it'll show up centered around the right edge of the screen. If you want to move the text, you should either choose a different alignment, or simply reposition/resize the GUI it appears on (e.g. moving it 15 pixels down). If the text may sometimes wrap over multiple lines and you want them bottom-aligned, that would require additional logic.

bx83

Turned the two variables to globals.
Thank you, it works :)

Snarky


bx83

#74
I'm using speechbubble 0.8.0

I'm trying to work out an equation that will display the speech bubbles above my characters head for SayAtBubble(), changed for scaling of the area (for example, 40pixels at 100%, 20pixels at 50%)

x axis is already sorted.

But the y axis keeps being out a bit depending on scaling. Looks good for 50%, too low at 5%, too high at 100%, etc. even though it's altered for scaling. The 'scaling point' moves slightly, when it should be fixed for all scaling.

Here's my equation. What am I doing wrong?

Code: ags

function cJulius_spk(String msg)
{
  cJulius.SayAtBubble(cJulius.x, cJulius.y - (FloatToInt( (460.0/100.0)*IntToFloat(cJulius.Scaling) )), msg);
}


Where msg is the message eg. "&123 I am a characters."
The 460.0 is my constant distance above Julius's head. His frames very high, so unless I set it manually, it's way above his head all the time.

bx83

Don't worry, I solved it with this dandy piece of code.... which is just part of speechbubble copied and hardcoded, because I can't figure out how to get access to it's function renderbubble32():

Code: ags


function cJulius_spk(String msg)
{
  // Calculate text dimensions
  int textWidth = -1;

  int w = System.ViewportWidth * 2/3;
  if(cJulius.x - GetViewportX() <= System.ViewportWidth/4 || cJulius.x - GetViewportX() >= System.ViewportWidth * 3/4) {
    w -= System.ViewportWidth/5;
  }
  textWidth=w;

  if(textWidth < (System.ViewportWidth - 40)) {
    textWidth=textWidth;
  } else {
    textWidth=System.ViewportWidth - 40;
  }
  
  int textHeight = GetTextHeight(msg, 1, textWidth);
  
  int cut = textWidth;
  int height = textHeight;
  while(cut>1) {
    cut = (cut+1) >> 1; // Subtract half as much as we tried last time, rounding up
    height = GetTextHeight(msg, 1, textWidth - cut);
    if(height == textHeight) {
      textWidth -= cut;
    }
  }
  height = GetTextHeight(msg, 1, textWidth-1);
  if(height == textHeight) {
    textWidth=textWidth-1;
  } else {
    textWidth=textWidth;
  }

  int totalWidth = textWidth + 20 + 20;
  int bubbleHeight = textHeight + 10 + 10;
  int totalHeight;
  totalHeight = bubbleHeight + 9;

  SpeechBubbleHeight_blr=totalHeight;//BLR

  int headHeight=355;     //constant
  int spaceAboveHead=15;  //constant

  float h=IntToFloat( SpeechBubbleHeight_blr ); //constant
  float f=( (IntToFloat(headHeight+spaceAboveHead)/100.0)*IntToFloat(cJulius.Scaling) ) + h;

  cJulius.SayAtBubble(cJulius.x, cJulius.y - FloatToInt(f), msg);
}

daneeca

This module is fantastic! Is it possible to make it work with QueuedSpeech module? And is there an easy way to change all "Say" command to "SayBubble"?

Snarky

Thanks! Glad you find it useful.

I think it's more a matter of getting QueuedSpeech to work with this module (by calling SayBackgroundBubble() instead of SayBackground()).

To quickly replace all Say() calls with SayBubble() calls you can use Edit|Replace All… (Ctrl-Shift-E), but you have to be careful that it only changes those calls, not any string that includes "say", and only where you want it to (for example, not in the module itself). If you replace ".Say(" by ".SayBubble(" with Case Sensitive on, one script at a time, you should be relatively safe.

AGS 3.5 also has a new feature where you can designate a custom function to use instead of Say() in dialogs.

bx83

What I want to happen:
1. Buddhist speaks, speech bubble appears
2. The sound sample 1 is over; delete this speech bubble, and create the second one; start the second bit of speech
3. When that's over, delete speechbubble, speech sample 2 has obviously stopped because it's non-blocking
4. Fade out screen

What *actually* happens:
1. Buddhist speaks speech sample 1, speech bubble appears
2. Speech bubble stops halfway through this first speech sample, second bubble appears
3. Speech sample 1 eventually ends, speech samples 2 starts
4. Speech sample 2 stops early, both speech sample and speech bubble disappear
5. Screen fades out

I'm using SpeechBubble8.0.

What's going on here? The speech bubbles are obviously ending early because they estimate a shorter amount of time to read the bubble than the speech sample has to end; but what is blocking/non-blocking?
Should I just write 'Wait(the length of time for the 2 speech samples)', and included it at the beginning/end of the block of code?

Also: I have chosen eSkipAnyKeyOrMouseClick for speech skipping

Code:
Code: ags
cBuddhistCow.SayBubble("63 At the dawn of time, the island - all it's crea.....e by the god of cows; Mooaclah.");
cBuddhistCow.SayBubble("64 Mooaclah was a bored creature, with many ......many cow-shaped vessels to store his power...");
FadeOut(10);


(Ampersands deleted because they're screwing up the text display of this post)

Khris

Looks like it's an issue with the module which might not be compatible with Voice Speech? Anyway, I'd post in the module's thread instead.

SMF spam blocked by CleanTalk