Water is a third person puzzle solving adventure type of game with an unique look and gameplay. It's a story about a mermaid, and fish men who aren't fish men. A story that brings you into tears and fears. It's about fish people, harp instruments, swimming, and crazy priests.

Report article RSS Feed Speech Bubbles

Today we are releasing the source code for the speech bubbles. We are not planning to release the whole source code for Water, atleast not yet, but if you have wishes to know how some certain parts of Water were made, let us know. The speech bubbles in Water are completely generated by the HUD. The size of the bubble is calculated by how long the text is. They also support localization, to allow them to be translated to any language.

Posted by Au-heppa on Dec 11th, 2011


Mod of the Year Awards Today we are releasing the source code for the speech bubbles. We are not planning to release the whole source code for Water, atleast not yet, but if you have wishes to know how some certain parts of Water were made, let us know.

The speech bubbles in Water are completely generated by the HUD. The size of the bubble is calculated by how long the text is. They also support localization, to allow them to be translated to any language.

Dont forget to read Making Of Water:

You can also now download the remastered Water soundtrack by Dennis "Entru" Yaremov.

But I digress, the speech bubbles:


Speech Bubbles

Setting it up

First you can download the four C++ files:

mm_hud_speech_bubbles.h and mm_hud_speech_bubbles.cpp are for the client side while util_speech_bubble.cpp is for the server side, and hlss_speech_bubbles_shared.h is shared.

You need to add add "StartBubble" and "NarrationBox" to your hl2_usermessages.cpp.

usermessages->Register( "SpeechBubble", -1 );
usermessages->Register( "NarrationBox", -1 );

You also need to download the materials:

They need to go to materials\vgui\ subfolder of your mod.

You need to add the speech bubble hud element to HudLayout.res in the script subfolder of your mod. Add this somewhere in the file.

code:
  HudSpeechBubble
  {
    "fieldName"   "HudSpeechBubble"
    "wide"  "640"
    "tall"  "480"
    "visible" "1"
    "enabled" "1"

  }

 

For localization, you can either add your lines to your existing mod localization file which is [modname]_english.txt for english language users. Or you can create a seperate localization file for all the speech bubbles. If you have your own localization file you need to add it to the localization system either in the speech bubbles hud element or somewhere like in VGui_Startup function in vgui_int.cpp. g_pVGuiLocalize->AddFile("resource/mermaid/default_´┐Żla;nguage%.txt"); Finally you need to define what fonts you want to use for the bubble text. You can either edit mm_hud_speech_bubbles.h so that it uses some entries from ClientScheme.Res in the resource subfolder of your mod.

code:
CPanelAnimationVar( vgui::HFont, m_hTextFont, "TextFont", "HLSS_SpeechBubble" );
CPanelAnimationVar( vgui::HFont, m_hKeyFont, "KeyFont", "HLSS_SpeechBubbleKey" );  
 

Or you can edit the ClientScheme.Res and add the fonts we used into the Fonts section:

code:
HLSS_SpeechBubble
{
  "1"
  {
    "name"    "Georgia"
    "tall"    "9"
    "weight"  "700"
    "antialias" "1"
  }
}

HLSS_SpeechBubbleKey
{
  "1"
  {
    "name"    "Georgia"
    "tall"    "9"
    "weight"  "700"
    "dropshadow"  "1"
  }
}

How to use the bubbles

On the server side you have the four functions. You have two for the speech bubbles, and two for the narration boxes. Include hlss_speech_bubbles_shared.h to any file where you want to use the speech bubbles in.

code:
  void UTIL_SpeechBubble( CBasePlayer *pPlayer,
        CBaseAnimating *pSpeaker,
        const char *strBubble,
        float flDelay = 5.0f,
        int iPriority = BUBBLE_PRIORITY_VERY_LOW,
        int iAttachment = -1 );
  void UTIL_StopBubble( CBasePlayer *pPlayer, CBaseAnimating *pSpeaker );
  void UTIL_NarrationBox( CBasePlayer *pPlayer,
        const char *strBubble,
        float flDelay,
        bool bAbsolute = false,
        float xratio = 0.5,
        float yratio = 0.5 );
  void UTIL_ClearNarrationBoxes( CBasePlayer *pPlayer );
 

So, to show a simple speech bubble on an NPC's head you call UTIL_SpeechBubble. The first variable is the player you want to send the bubble. If the pointer is NULL it will use the PAS filter. If your mod is a multiplayer, this might be useful since it would send it to all players who are in the proximity of the speaking entity. However, since Water was a single player mod, I didn't add an option to send the bubble to all players no matter where they are in the map. If the pointer is defined, it will send the bubble only to that player.

Next is the pointer to the speaker entity. This can be something like an NPC or the player themselves. The bubble will appear on that NPC. The attachment option defines where from the character's body the bubble's tail is coming from. Usually you want to use the "mouth" attachment, which would mean calling pSpeaker->LookupAttachment( "mouth" ). Each entity can only show one speech bubble at once, and if the entity is already showing a speech bubble, the new will either replace that one, or not show up at all.

The strBubble option should usually be a localization token refering to an entry in a localization file. You can use unlocalized lines here as well, but it is recommended you use localization. The delay determines how long the bubble should appear on the screen. In Water using the delay of 0, would make the game wait until the player has pressed a key to skip it. Since I don't cover how to make a dialogue selection menu in this tutorial, I have disabled that option for now.

The priority option tells the system how important the bubble is. If the entity the bubble is supposed to be shown on is already showing a speech bubble, and the old speech bubble has higher priority than the new one, the new one wont replace the old one. Bubbles with higher or equal priority of Medium, are also always shown, even if the entity is off screen.

How it works

You don't necessarily need to read this if you want to add the bubbles to your mod, but it helps if you want to change something in their behavior. I only cover bubbles here, but the narration boxes work pretty much the same. Since this tutorial doesn't cover how to make a dialogue selection menu either, I have commented out some of the references to it.

In short, first the server side sends a localization token to the client side with info on which entity it wants to show the bubble on and so on. The client side CHudSpeechBubbles HUD element then localizes the file, goes through it cutting it into smaller lines to fit the speech bubble shape, and adds it to the list of all speech bubbles.

The Paint function then paints all the speech bubbles and removes them if necessary.

UTIL_SpeechBubble
This is the function you call on the server side to show a speech bubble on the speaker entity. First it creates a filter, which determines which players can see the bubble. If the player pointer is NULL, it will simply use the speaker's center to determine which player's could see it using the PAS filter.

It then starts sending the usermessage SpeechBubble (as declared in hl2_usermessage.cpp) to the client. The HLSS_MESSAGE_SPEECH_BUBBLE macro defines the message. It starts by writing the entity index of the speaker entity, which will used to find the entity on the client side. After that it will send the bubble delay, priority, the token for the bubble, and the attachment index.

MsgFunc_SpeechBubble
This is the first thing that gets called on the client side. It goes through the data the server side sent, and follows the same message structure. It uses the entity index to find the pointer to the speaker entity. If the delay is negative it assumes it's supposed to remove any speech bubble from the entity rather than add a new one, and after doing so, it will stop reading the message. Other wise it will continue reading the message, and priority, token and the attachment.

It then tries to localize the token into a wchar_t buffer. If the token is not found in any localization file, it will simply show the token as it is.

If the bubble token has the $number$ character in it, the code tries to replace the number with a random number from 1 to the number before localization. For example, if the localization token sent by the server was #MM_Helper_Ask$3$, it would randomize a number between 1 and 3, so that any of the three versions of the line could show up.

"MM_Helper_Ask1" "Hey, toss me that beer, will you?"
"MM_Helper_Ask2" "Can I have that beer?"
"MM_Helper_Ask3" "Can I please get that beer?"

AddBubble
This function checks if the speaker entity already has a speech bubble, and depending on that, adds a new one to the list of speechbubbles m_vSpeechBubbles, or edits the existing on. After this it calls ProcessLines.

ProcessLines
Here the speech bubbles are cut into several lines that are later painted seperately. It also goes through all the text and tries to replace any references to key bindings, and tries to find the name of the key the player has bound to that action. For example, if the bubble has the text %+attack% in it, the code will replace it with the name of the key the player has bound it to, which is by default MOUSE1.

First the function tries to approximate the size of the speech bubble by using it length. It then starts going through the text character by character, trying to figure out how it would fit inside the bubble shape, and cuts the lines as necessary. Finally it adds the fully processed line to the list of all speech bubbles.

Processed Bubble structere
The processed lines are listed in the UtlVector m_vSpeechBubbles, where every bubble is a single struct sSpeechBubble.

code:
 
  struct sSpeechBubble
  {
  //BUBBLE:
  EHANDLE   m_hEnt;
  int     m_iAttachment;
  int     m_iType;

  //BOTH:
  float   m_flRemoveTime;
  int     m_iAlignment;
  int     m_iPriority;
  int     m_iWide;
  int     m_iTall;
  int     m_iLettersReached;
  float   m_flNextGetLetter;

  CUtlVector<sProcessedBubbleLine>  m_vSpeechBubblesLines;
  };
 

The EHANDLE m_hEnt is the entity the bubble is shown on. If the handle returns a NULL pointer, it means something has deleted the entity, and the bubble will be deleted as well. m_iAttachment defines, like previously mentioned, where in the model the bubble should appear. m_iType defines what color the bubble should be. As it is, the choices there are kind of limited, so you can add more choices there if you want.

m_iAlingment defines what direction the speech bubble's tail was pointing the last time the speech bubble was painted. m_iLettersReached is how many characters of the bubble should be shown. m_flNextGetLetter is when the next letter should be shown. The UtlVector m_vSpeechBubblesLines is a list of the speech bubbles lines.

code:
  struct sProcessedBubbleLine
  {
    wchar_t unicode[MAX_SPEECH_BUBBLE_LINE];
  int width;
  int length;
  };
 

Paint
The Paint function goes through all the bubbles and sees which ones it should paint, which ones not, and which ones it should remove. After that it does the same for narration boxes. For both of them it first checks if it should remove any of the bubbles.

With the bubbles it does a couple of things to check if the bubble should be drawn. It checks if the entity is off screen or if the speaker entity is visible. It also checks the distance of the attachment from the main view origin. If the distance is bigger than 1536 the bubble wont get drawn. In Water we didn't want the crabs' idle speech bubbles to be shown if the player was above water, so we also added a check to see if the main view origin is on the other side of the water surface than the attachment position.

The alignment of the bubble depends, if the bubble is near the edges of the screen, and direction the mouth attachment is pointing. The code also takes into consideration the alignment the bubble had previous round, so that the bubble wont suddenly change direction when the attachment moves even slightly.



Remember to vote us on the Mod Of The Year awards!

Download Water now

If you haven't yet played Water, you can download the installer, or use Desura, or unpack the .zip file to SourceMods so that the mod folder is called "water". You will have to have Source SDK Base 2007 installed on your Steam account. You can install Source SDK Base 2007 by accessing the Tools section in your Games Library. You need to have a Source engine game like Half-Life 2, Counter-Strike Source, or Portal to have Source SDK Base 2007 available.

Additionally if you the version of Water you downloaded is 1.0.2 or lower, download 1.0.3 Patch. The current installer is 1.0.3. If you have any problems with installing the patch, or anything else, go to the Water Bugs & Problems page.

Post comment Comments
JonnyBoy0719
JonnyBoy0719 Dec 11 2011, 4:47am says:

awesome :)

+1 vote     reply to comment
eghaz
eghaz Dec 11 2011, 7:06am says:

useful information. this is what I am looking for.

+1 vote     reply to comment
Zeltrax
Zeltrax Dec 11 2011, 5:05pm says:

Trailer scared me :(

+1 vote     reply to comment
Au-heppa
Au-heppa Dec 12 2011, 4:33am replied:

Excellent

+1 vote     reply to comment
Post a Comment
click to sign in

You are not logged in, your comment will be anonymous unless you join the community today (totally free - or sign in with your social account on the right) which we encourage all contributors to do.

2000 characters limit; HTML formatting and smileys are not supported - text only

Icon
Half-Life 2 Icon
Platform
Windows
Contact
Send Message
Official Page
Hlssmod.net
Release Date
Released Nov 13, 2011
Mod Watch
Track this mod
News
Browse
News
Report Abuse
Report article
Related Mods
Water (Half-Life 2)
Water Half-Life 2 - Single Player Puzzle Compilation
Related Games
Half-Life 2
Half-Life 2 Single & Multiplayer First Person Shooter
Related Groups
Half-Life: Short Stories Team
Half-Life: Short Stories Team Developer & Publisher with 8 members