Difference between revisions of "Argo Bubbles"
m |
|||
(30 intermediate revisions by 2 users not shown) | |||
Line 3: | Line 3: | ||
! style="text-align:left" | Name !! style="text-align:left" | Type !! style="text-align:left" | By | ! style="text-align:left" | Name !! style="text-align:left" | Type !! style="text-align:left" | By | ||
|- | |- | ||
− | | Argo | + | | Argo Bubbles - Dynamic stylized speech bubbles || Definition || The Argonauts |
|} | |} | ||
− | This script allows you to turn texts spoken by characters into speech bubbles | + | This script allows you to turn texts spoken by characters into speech bubbles. |
<hr> | <hr> | ||
{| class="toccolours mw-collapsible mw-collapsed ts qntoggle" | {| class="toccolours mw-collapsible mw-collapsed ts qntoggle" | ||
− | |||
|- | |- | ||
− | | colspan="2" | '' | + | | colspan="2" | ''The "Argo Bubbles" script is based on the "Advanced Speechbubble Script" by Sebastian aka [https://www.youtube.com/c/turbomodus turbomodus].'' |
|- | |- | ||
| style="text-align:center;vertical-align:middle;" | <youtube width="400">wV6Fh_baPkk</youtube> || style="text-align:center;" | <youtube width="400">dn_X6OsozZI</youtube> | | style="text-align:center;vertical-align:middle;" | <youtube width="400">wV6Fh_baPkk</youtube> || style="text-align:center;" | <youtube width="400">dn_X6OsozZI</youtube> | ||
Line 20: | Line 19: | ||
== Instructions == | == Instructions == | ||
− | + | [[File:Argo_Bubbles_-_Example.png|thumb]] | |
− | + | 1. Add the [[#Main_Script|main script]] to the Visionaire Studio Script Editor and set the script as a definition script. | |
− | + | 2. Create the bubble graphics and add them to your project folder or any subfolder. Basic graphics are included in the demo project which can be downloaded from the [[#Resources|resources section]]. | |
− | + | 3. Adjust the [[#Settings|settings]] at the top of the script (between "DEFINE YOUR SETTINGS HERE" and "USUALLY NO NEED TO CHANGE ANYTHING BELOW THIS LINE"). | |
− | |||
− | + | == Description == | |
− | |||
− | |||
− | |||
− | + | The script is extensively commented, giving you all the information you need to make use of its features. A [[#Settings|detailed description of the settings]] can be found on this page. | |
− | |||
− | + | === How the Argo Bubbles are created === | |
− | + | * Display of the regular character text is prevented. The text is stored internally to get integrated in the speech bubble. The font used in the bubble will stay the same though - it is the regular font defined in the editor for a specific character. Choose a font for the character that fits your speech bubble design. Any [[Text#Text_formatting|text formatting]] or [[Text#Text_effects|text effects]] are preserved. | |
+ | * The main bubble graphic is cut into nine pieces which are individually stretched and then reassembled to match the calculated size of the bubble (see the graphic below). This "ninerect" technique ensures that the corners don't get distorted, but leads to limitations to what the speech bubbles can look like. It is not possible with this script to have elliptical or fancy-shaped bubbles. They need to be more or less rectangular. | ||
+ | * You usually want to have a pointer attached to the bubble, pointing at the character who is currently speaking. Depending on your alignment settings and on the direction the character is facing, the script selects one of up to four pointer graphics you can provide and places it accordingly. Pointers are optional though. | ||
+ | * You have the option to add an additional image to the bubble. It is intended for a portrait picture of the character, but you can choose any image you like, of course. | ||
− | |||
− | + | [[File:Argo_Bubbles_-_How_the_bubbles_are_created.png|thumb|center|800px]] | |
− | |||
+ | === Settings === | ||
+ | |||
+ | ==== General settings ==== | ||
+ | |||
+ | * <span class="inlinecode">enable_shader_viewport_adjustments:</span> If set to true, the script will take viewport manipulations by Visionaire's Shader Toolkit into account. If you don't make use of the Shader Toolkit, you can set this option to false. | ||
+ | |||
+ | |||
+ | ==== Default bubble style ==== | ||
+ | |||
+ | * <span class="inlinecode">align_h:</span> The horizontal alignment of the bubble in relation to the character. Possible values are (center|left|right|char_facing). The "char_facing" value results in a left/right aligned bubble, respectively, depending on the character's facing direction. | ||
+ | * <span class="inlinecode">align_v:</span> The vertical alignment of the bubble in relation to the character. Possible values are (top|bottom). This setting also defines the vertical positioning of the bubble pointer: it is attached to the bottom of the bubble, if the bubble is top-aligned, and vice versa. | ||
+ | * <span class="inlinecode">flip_v_align_on_edge:</span> If set to true, a top-aligned bubble will automatically turn into a bottom-aligned bubble (and vice versa) when the character gets too close to the top (bottom) edge of the screen. | ||
+ | * <span class="inlinecode">bubble_offset_x:</span> The horizontal offset of the bubble from the initial position centered above the character's head. Positive values move the bubble in the character's facing direction. | ||
+ | * <span class="inlinecode">bubble_top_offset_y:</span> The vertical offset of the bubble for top-aligned bubbles. Positive values move the bubble up. | ||
+ | * <span class="inlinecode">bubble_bottom_offset_y:</span> The vertical offset of the bubble for bottom-aligned bubbles. Positive values move the bubble down. | ||
+ | * <span class="inlinecode">adjust_v_offset_to_char_scale:</span> If set to true, the current character scaling is taken into account when applying the vertical bubble offset. The offset (as defined in "bubble_top_offset_y" or "bubble_bottom_offset_y") will be reduced/increased according to the character scale. | ||
+ | * <span class="inlinecode">color:</span> Color tint for the bubble (and pointer). Please note that this option takes the color in hexadecimal BGR notation (instead of RGB), prepended with "0x". The default value of 0xffffff (white) results in no tint. | ||
+ | * <span class="inlinecode">text_align_h:</span> The horizontal text alignment inside the bubble. Possible values are (center|left|right). This option has a visible effect only if it's multiline text and/or if the bubble has a fixed width. | ||
+ | * <span class="inlinecode">text_align_v:</span> The vertical text alignment inside the bubble. Possible values are (center|top|bottom). This option has a visible effect only if the bubble has a fixed height. | ||
+ | * <span class="inlinecode">padding:</span> The distance between the text block and the outer edge of the bubble, defined in a table {"top", "right", "bottom", "left"}. | ||
+ | * <span class="inlinecode">fixed_width:</span> The fixed width of the bubble. Set the value to 0 to not set a fixed width. Be aware that the width of the bubble does currently not affect line breaks. | ||
+ | * <span class="inlinecode">fixed_height:</span> The fixed height of the bubble. Set the value to 0 to not set a fixed height. | ||
+ | * <span class="inlinecode">fixed_pos:</span> A fixed position on the screen to show the bubble at, defined in a table {"x", "y"}. That position will serve as the root point of the position calculation (it substitutes the character's position), so it depends on your alignment settings where the bubble will be placed. Don't set "align_h" to "char_facing" when using the fixed position, because the bubble position would change depending on the character's facing direction. | ||
+ | * <span class="inlinecode">expand_fixed_dims:</span> If set to true, a fixed bubble width/height will expand if the text doesn't fit in the fix-sized bubble. The fixed width/height will act like a min-width/min-height setting. | ||
+ | * <span class="inlinecode">file_bubble:</span> The path to the main bubble graphic. The graphic needs to be suited for the "ninerect" technique. The filepath must be relative to the project root folder and be prepended with "vispath:". | ||
+ | * <span class="inlinecode">ninerect:</span> The measurements used to cut the "ninerect" bubble graphic, defined in a table {"x", "y", "width, "height"}. The x/y values define the width/height of the top left tile of the "ninerect", the width/height values define the width/height of the center tile (see the graphic below). | ||
+ | * <span class="inlinecode">file_portrait:</span> The path to a portrait image. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a portrait image. | ||
+ | * <span class="inlinecode">portrait_align_h:</span> The horizontal alignment of the portrait image in the bubble. Possible values are (left|right). There will be no padding between the portrait and the edge of the bubble; the padding setting only applies to the text. Add transparent areas to your portrait image to adjust the position (see the graphic below). | ||
+ | * <span class="inlinecode">portrait_align_v:</span> The vertical alignment of the portrait image in the bubble. Possible values are (top|center|bottom). There will be no padding between the portrait and the edge of the bubble; the padding setting only applies to the text. Add transparent areas to your portrait image to adjust the position (see the graphic below). | ||
+ | * <span class="inlinecode">file_pointer_bottom_right:</span> The path to the down-right-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer. | ||
+ | * <span class="inlinecode">file_pointer_bottom_left:</span> The path to the down-left-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer. | ||
+ | * <span class="inlinecode">pointer_bottom_right_offset_x:</span> The horizontal offset of the down-right-pointing pointer. Positive values move the pointer in the character's facing direction. | ||
+ | * <span class="inlinecode">pointer_bottom_left_offset_x:</span> The horizontal offset of the down-left-pointing pointer. Positive values move the pointer in the character's facing direction. | ||
+ | * <span class="inlinecode">pointer_bottom_offset_y:</span> The vertical offset of the down-pointing pointer. Positive values move the pointer inwards. | ||
+ | * <span class="inlinecode">file_pointer_top_right:</span> The path to the up-right-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer. | ||
+ | * <span class="inlinecode">file_pointer_top_left:</span> The path to the up-left-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer. | ||
+ | * <span class="inlinecode">pointer_top_right_offset_x:</span> The horizontal offset of the up-right-pointing pointer. Positive values move the pointer in the character's facing direction. | ||
+ | * <span class="inlinecode">pointer_top_left_offset_x:</span> The horizontal offset of the up-left-pointing pointer. Positive values move the pointer in the character's facing direction. | ||
+ | * <span class="inlinecode">pointer_top_offset_y:</span> The vertical offset of the up-pointing pointer. Positive values move the pointer inwards. | ||
+ | * <span class="inlinecode">pointer_min_distance:</span> The minimum distance a pointer has to keep from the edge of the bubble (see the graphic below). | ||
+ | * <span class="inlinecode">min_distance:</span> The minimum distance the bubble has to keep from the edge of the screen. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | [[File:Argo_Bubbles_-_Style_settings.png|thumb|center|800px]] | |
− | - | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ==== Custom bubble styles ==== | |
− | |||
+ | A main feature of the "Argo Bubbles" script is the ability to define multiple bubble styles. You could have a different bubble color/design for each character, for example. Or depending on where an NPC stands, that character might need a different positioning of the bubble than the default one. You can even define multiple styles for the same character and switch between them during the game. | ||
− | + | The default bubble style is defined in the "default_style" table. If you would like to add more styles: | |
− | |||
+ | * Add custom bubble styles to the <span class="inlinecode">custom_styles</span> table. You can override all properties of the default style. Undefined properties will fallback to the default values. | ||
+ | * Each custom bubble style is defined for a specific character. Add the name of the character as an additional <span class="inlinecode">char</span> property. | ||
− | + | <syntaxhighlight lang="lua"> | |
+ | -- Example | ||
+ | local custom_styles = { | ||
+ | { | ||
+ | char = "Hero", | ||
+ | align_h = "left", | ||
+ | align_v = "bottom", | ||
+ | bubble_offset_x = 40, | ||
+ | bubble_top_offset_y = 35, | ||
+ | file_pointer_top_right = "vispath:gui/bubble_pointer_tr.png", | ||
+ | pointer_top_offset_y = 10 | ||
+ | }, | ||
+ | { | ||
+ | char = "Villain", | ||
+ | file_bubble = "vispath:gui/bubble_black.png" | ||
+ | }, | ||
+ | { | ||
+ | -- next style definition here ... | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | + | * If a character is meant to use only a single style throughout the game (either the default style or a custom one), you don't have to do anything else. | |
+ | * If you want the character to switch styles during the game, add a Visionaire value called "argo_bubble" to the character. By changing that value you can select the style to be active. A value of 0 selects the default style. A value of 1 selects the first custom style defined for that character, a value of 2 the second custom style for that character etc. You can define as many custom styles per character as you like - just add them to the "custom_styles" table. | ||
− | |||
− | + | ==== Advanced settings ==== | |
− | + | * <span class="inlinecode">ab_bind_to_handler:</span> You can define the "textStopped" event handler only once in your game project. If you want to do that in another script, set this variable to false. You'll then have to call "destroy_argo_bubble(text)" in that other script. | |
+ | * <span class="inlinecode">ab_on_text_stopped():</span> You can define the "textStopped" event handler only once in your game project. If you want to do that here in the Argo Bubbles script, but have other functions you want to call from this handler, add these calls to this function. Only applicable if "ab_bind_to_handler" is set to true. | ||
− | |||
+ | === Kill the bubbles === | ||
− | + | Call the <code>kill_argo_bubbles()</code> function to immediately remove all current bubbles from the screen. You'll need this, for example, if the user can open the menu by pressing a key, because it would be possible to do that while a character is talking and thus a speech bubble is visible. By calling the kill function you prevent the bubble from appearing on the menu as well. A scene change does not automatically hide a bubble. | |
− | |||
− | + | == Tutorial == | |
− | + | For a deeper understanding of how the speech bubbles are created and how they replace the text displayed by Visionaire, please have a look at the tutorial; it is included [[#Resources|in the .zip file]]. You <u>don't</u> have to read it, if you just want to use the script in your project. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <span class="red">Be aware that the tutorial was created for script version 2.1.2. The basic idea is still the same, but the code has changed a lot since then.</span> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | == Main Script == | |
+ | <html><button id="button1" class="copybtn" onclick="CopyToClipboard('txt')"></button></html> | ||
+ | <syntaxhighlight lang="lua" id="txt"> | ||
--[[ | --[[ | ||
− | |||
− | + | Argo Bubbles | |
+ | ------------ | ||
+ | This script turns character texts into speech bubbles. It allows defining differently styled bubbles for each character, or even different speech bubble styles for the same character to switch between during the game. | ||
− | + | Authors: The Argonauts | |
+ | (with contributions by Mulewa and some lines of code | ||
+ | taken from the Visionaire Shader Toolkit by Simon Scheckel) | ||
+ | Version: 2.3.0 | ||
+ | Date: 2024-01-05 | ||
+ | Requirements: Visionaire Studio 5.3 or higher | ||
+ | License: MIT License (details at the bottom of the script) | ||
+ | Based on: Advanced Speechbubble Script [v1.1] (10/13/2018), written by Sebastian/TURBOMODUS (with permission) | ||
+ | Instructions: https://wiki.visionaire-tracker.net/wiki/Argo_Bubbles | ||
− | + | Argonauts games: https://the-argonauts.itch.io/ | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
]] | ]] | ||
− | |||
− | -- | + | ------------------------------- |
− | -- | + | -- DEFINE YOUR SETTINGS HERE -- |
− | + | ------------------------------- | |
− | local | + | |
+ | -- Detailed description of the settings: | ||
+ | -- https://wiki.visionaire-tracker.net/wiki/Argo_Bubbles#Settings | ||
+ | -- or in the "readme.txt" file coming with this script | ||
+ | |||
+ | |||
+ | -- GENERAL SETTINGS | ||
+ | local enable_shader_viewport_adjustments = true | ||
+ | |||
+ | |||
+ | -- DEFAULT BUBBLE STYLE | ||
+ | local default_style = { | ||
+ | align_h = "center", | ||
+ | align_v = "top", | ||
+ | flip_v_align_on_edge = true, | ||
+ | bubble_offset_x = 0, | ||
+ | bubble_top_offset_y = 25, | ||
+ | bubble_bottom_offset_y = 70, | ||
+ | adjust_v_offset_to_char_scale = true, | ||
+ | color = 0xffffff, | ||
+ | text_align_h = "center", | ||
+ | text_align_v = "center", | ||
+ | padding = { top = 15, right = 20, bottom = 12, left = 20 }, | ||
+ | fixed_width = 0, | ||
+ | fixed_height = 0, | ||
+ | fixed_pos = false, | ||
+ | expand_fixed_dims = true, | ||
+ | file_bubble = "vispath:gui/bubble.png", | ||
+ | ninerect = { x = 20, y = 15, width = 30, height = 20 }, | ||
+ | file_portrait = "", | ||
+ | portrait_align_h = "left", | ||
+ | portrait_align_v = "top", | ||
+ | file_pointer_bottom_right = "vispath:gui/bubble_pointer_br.png", | ||
+ | file_pointer_bottom_left = "vispath:gui/bubble_pointer_bl.png", | ||
+ | pointer_bottom_right_offset_x = 20, | ||
+ | pointer_bottom_left_offset_x = 0, | ||
+ | pointer_bottom_offset_y = 3, | ||
+ | file_pointer_top_right = "", | ||
+ | file_pointer_top_left = "", | ||
+ | pointer_top_right_offset_x = 0, | ||
+ | pointer_top_left_offset_x = 0, | ||
+ | pointer_top_offset_y = 0, | ||
+ | pointer_min_distance = 15, | ||
+ | min_distance = 15 | ||
+ | } | ||
+ | |||
+ | |||
+ | -- CUSTOM BUBBLE STYLES | ||
+ | local custom_styles = {} | ||
− | |||
− | |||
− | |||
− | + | -- ADVANCED SETTINGS | |
+ | local ab_bind_to_handler = true | ||
− | |||
function ab_on_text_stopped(text) | function ab_on_text_stopped(text) | ||
-- add your functions here | -- add your functions here | ||
Line 211: | Line 265: | ||
− | -- | + | -- CALL FUNCTIONS |
+ | |||
local bubbles = {} | local bubbles = {} | ||
+ | local killed = {} | ||
+ | -- New mode: create bubbles in "textRender" function | ||
function show_argo_bubble(text) | function show_argo_bubble(text) | ||
− | -- Add current text to the bubbles table and | + | if text.Owner.tableId == eCharacters then |
− | + | -- Add current character text to the bubbles table, if not already there (and bubbles not killed early) | |
− | + | if bubbles[text.id] == nil and killed[text.id] == nil then | |
+ | bubbles[text.id] = { | ||
+ | Text = text.CurrentText, | ||
+ | Owner = text.Owner.name, | ||
+ | Background = text.Background, | ||
+ | Object = text | ||
+ | } | ||
+ | end | ||
− | text | + | -- override the default text rendering |
+ | return true | ||
end | end | ||
+ | |||
+ | return false | ||
+ | end | ||
+ | |||
− | |||
− | |||
− | |||
− | |||
− | |||
function destroy_argo_bubble(text) | function destroy_argo_bubble(text) | ||
-- Remove current text from bubbles table | -- Remove current text from bubbles table | ||
− | bubbles[text | + | bubbles[text.id] = nil |
-- Call external functions that use the "textStopped" event handler | -- Call external functions that use the "textStopped" event handler | ||
Line 238: | Line 302: | ||
end | end | ||
− | + | ||
− | + | ||
+ | -- Call this function to immediately destroy all bubbles | ||
+ | function kill_argo_bubbles() | ||
+ | -- Since we use the render hook, the bubbles table gets quickly filled again; | ||
+ | -- so we store the ids of texts to be killed in a separate table | ||
+ | for key, val in pairs(bubbles) do | ||
+ | killed[key] = key | ||
+ | end | ||
+ | |||
+ | bubbles = {} | ||
+ | end | ||
+ | |||
+ | |||
+ | |||
+ | -- Register hook function and bind to event handler | ||
+ | registerHookFunction("textRender", "show_argo_bubble") | ||
+ | |||
+ | if ab_bind_to_handler then | ||
registerEventHandler("textStopped","destroy_argo_bubble") | registerEventHandler("textStopped","destroy_argo_bubble") | ||
end | end | ||
+ | |||
+ | |||
+ | |||
Line 254: | Line 338: | ||
end | end | ||
end | end | ||
+ | |||
+ | |||
-- Draw non-background texts from bubbles table above interfaces | -- Draw non-background texts from bubbles table above interfaces | ||
Line 263: | Line 349: | ||
end | end | ||
end | end | ||
+ | |||
+ | |||
-- Main bubble function | -- Main bubble function | ||
Line 269: | Line 357: | ||
local char = Characters[val.Owner] | local char = Characters[val.Owner] | ||
local pos = graphics.getCharacterTextPosition(char) | local pos = graphics.getCharacterTextPosition(char) | ||
+ | |||
+ | -- Get bubble style | ||
+ | local bubble_style = get_style(key, val, char) | ||
+ | |||
+ | -- Get shader toolkit viewport adjustments | ||
+ | local shader_adjusted_viewport = get_viewport_adjustments(bubble_style) | ||
+ | |||
+ | -- Facing direction (if bubble alignment is set to "left" or "right", the actual facing direction is irrelevant) | ||
local char_facing = "right" | local char_facing = "right" | ||
− | if | + | |
+ | if bubble_style.align_h == "left" then | ||
char_facing = "left" | char_facing = "left" | ||
+ | elseif bubble_style.align_h ~= "right" then | ||
+ | if char.Direction > 90 and char.Direction < 270 then | ||
+ | char_facing = "left" | ||
+ | end | ||
end | end | ||
− | |||
− | -- | + | -- Get text and bubble dimensions |
− | local bubble_style = | + | local text_and_bubble_dims = get_text_and_dims(key, val, char, bubble_style) |
+ | |||
+ | local text_dim = text_and_bubble_dims.text_dim | ||
+ | local bubble_dim = text_and_bubble_dims.bubble_dim | ||
− | + | -- Get the bubble position | |
− | + | pos = get_bubble_position(pos, bubble_style, bubble_dim, char, char_facing, shader_adjusted_viewport) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | -- | + | -- Get bubble graphic and define ninerect geometry |
− | + | local bubble_sprite | |
− | + | local dest_rect = { x = pos.x, y = pos.y, width = bubble_dim.x, height = bubble_dim.y } | |
− | + | ||
+ | -- Take sprite from bubbles table, if cached | ||
+ | if val.bubble_sprite ~= nil then | ||
+ | bubble_sprite = val.bubble_sprite | ||
+ | else | ||
+ | bubble_sprite = graphics.loadFromFile(bubble_style.file_bubble) | ||
− | + | -- Cache bubble sprite | |
− | + | bubbles[key].bubble_sprite = bubble_sprite | |
− | |||
end | end | ||
− | + | -- Draw the bubble | |
+ | graphics.drawSpriteWithNineRect(bubble_sprite, dest_rect, bubble_style.ninerect, bubble_style.color, 1.0) | ||
− | -- | + | -- Get portrait graphic |
− | + | local portrait_sprite = nil | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | if | + | -- Take sprite from bubbles table, if cached |
− | + | if val.portrait_sprite ~= nil then | |
− | elseif | + | portrait_sprite = val.portrait_sprite |
− | + | elseif bubble_style.file_portrait ~= nil and bubble_style.file_portrait ~= "" then | |
+ | portrait_sprite = graphics.loadFromFile(bubble_style.file_portrait) | ||
+ | |||
+ | -- Cache bubble sprite | ||
+ | bubbles[key].portrait_sprite = portrait_sprite | ||
end | end | ||
+ | |||
+ | -- Continue with portrait, if graphic has been defined | ||
+ | if portrait_sprite ~= nil then | ||
+ | -- Get position of portrait image | ||
+ | portrait_sprite.position = get_portrait_position(pos, bubble_style, bubble_dim, portrait_sprite) | ||
− | + | -- Draw the portrait | |
− | + | graphics.drawSprite(portrait_sprite, 1.0) | |
− | |||
− | |||
end | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
-- Get pointer graphic | -- Get pointer graphic | ||
− | local | + | local pointer_sprite = nil |
− | local | + | local pointer_offset = 0 |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
if char_facing == "left" then | if char_facing == "left" then | ||
-- right pointer when char facing left | -- right pointer when char facing left | ||
− | if bubble_style.align_v == "bottom" then | + | if (bubble_style.align_v_temp ~= nil and bubble_style.align_v_temp == "bottom") or (bubble_style.align_v_temp == nil and bubble_style.align_v == "bottom") then |
-- top pointer when bubble aligned to bottom | -- top pointer when bubble aligned to bottom | ||
− | if bubble_style.file_pointer_top_right ~= nil then | + | if bubble_style.file_pointer_top_right ~= nil and bubble_style.file_pointer_top_right ~= "" then |
− | + | pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_top_right) | |
− | + | pointer_offset = -bubble_style.pointer_top_right_offset_x | |
end | end | ||
else | else | ||
-- bottom pointer when bubble aligned to top | -- bottom pointer when bubble aligned to top | ||
− | if bubble_style.file_pointer_bottom_right ~= nil then | + | if bubble_style.file_pointer_bottom_right ~= nil and bubble_style.file_pointer_bottom_right ~= "" then |
− | + | pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_bottom_right) | |
− | + | pointer_offset = -bubble_style.pointer_bottom_right_offset_x | |
end | end | ||
end | end | ||
else | else | ||
-- left pointer when char facing right | -- left pointer when char facing right | ||
− | if bubble_style.align_v == "bottom" then | + | if (bubble_style.align_v_temp ~= nil and bubble_style.align_v_temp == "bottom") or (bubble_style.align_v_temp == nil and bubble_style.align_v == "bottom") then |
-- top pointer when bubble aligned to bottom | -- top pointer when bubble aligned to bottom | ||
− | if bubble_style.file_pointer_top_left ~= nil then | + | if bubble_style.file_pointer_top_left ~= nil and bubble_style.file_pointer_top_left ~= "" then |
− | + | pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_top_left) | |
− | + | pointer_offset = bubble_style.pointer_top_left_offset_x | |
end | end | ||
else | else | ||
-- bottom pointer when bubble aligned to top | -- bottom pointer when bubble aligned to top | ||
− | if bubble_style.file_pointer_bottom_left ~= nil then | + | if bubble_style.file_pointer_bottom_left ~= nil and bubble_style.file_pointer_bottom_left ~= "" then |
− | + | pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_bottom_left) | |
− | + | pointer_offset = bubble_style.pointer_bottom_left_offset_x | |
end | end | ||
end | end | ||
Line 391: | Line 460: | ||
-- Continue with pointer, if graphic has been defined | -- Continue with pointer, if graphic has been defined | ||
− | if | + | if pointer_sprite ~= nil then |
− | -- | + | -- Get position of pointer image |
− | local | + | pointer_sprite.position = get_pointer_position(pos, bubble_style, bubble_dim, pointer_offset, char, char_facing, shader_adjusted_viewport, pointer_sprite) |
− | + | ||
+ | -- Draw the pointer | ||
+ | graphics.drawSprite(pointer_sprite, 1.0, bubble_style.color) | ||
+ | end | ||
+ | |||
+ | -- Draw the text | ||
+ | -- Calculate vertical alignment of text inside bubble (only if fixed height and not at the top) | ||
+ | local text_box_pos_y = pos.y + bubble_style.padding.top | ||
+ | |||
+ | if bubble_style.fixed_height ~= nil then | ||
+ | if bubble_style.text_align_v == "center" then | ||
+ | text_box_pos_y = pos.y + bubble_style.padding.top + (bubble_dim.y - bubble_style.padding.top - bubble_style.padding.bottom - text_dim.y) / 2 | ||
+ | elseif bubble_style.text_align_v == "bottom" then | ||
+ | text_box_pos_y = pos.y + bubble_dim.y - bubble_style.padding.bottom - text_dim.y | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- Draw the text object | ||
+ | local text_pos = {x = 0, y = text_box_pos_y} | ||
+ | local alignment = 2 | ||
+ | |||
+ | if bubble_style.text_align_h == "left" then | ||
+ | text_pos.x = pos.x + bubble_style.padding.left | ||
+ | alignment = 0 | ||
+ | elseif bubble_style.text_align_h == "right" then | ||
+ | text_pos.x = pos.x + bubble_dim.x - bubble_style.padding.right | ||
+ | alignment = 1 | ||
+ | else -- center | ||
+ | text_pos.x = pos.x + bubble_style.padding.left + (bubble_dim.x - bubble_style.padding.left - bubble_style.padding.right) / 2 | ||
+ | end | ||
+ | |||
+ | graphics.drawTextObject(val.Object, text_pos.x, text_pos.y, alignment) | ||
+ | end | ||
− | |||
− | |||
− | |||
− | + | ||
− | if | + | -- Get style for the current bubble |
− | + | function get_style(key, val, char) | |
+ | local bubble_style = default_style | ||
+ | |||
+ | -- Take style from bubbles table, if cached | ||
+ | if val.style ~= nil then | ||
+ | bubble_style = val.style | ||
+ | else | ||
+ | if c_styles ~= nil and c_styles[char.name] ~= nil then | ||
+ | if Characters[char.name].Values["argo_bubble"] ~= nil then | ||
+ | if c_styles[char.name][ Characters[char.name].Values["argo_bubble"].Int ] ~= nil then | ||
+ | bubble_style = c_styles[char.name][ Characters[char.name].Values["argo_bubble"].Int ] | ||
+ | end | ||
+ | else | ||
+ | bubble_style = c_styles[char.name][1] | ||
end | end | ||
− | + | end | |
− | + | ||
− | + | -- Cache style | |
+ | bubbles[key].style = bubble_style | ||
+ | end | ||
+ | |||
+ | return bubble_style | ||
+ | end | ||
+ | |||
+ | |||
+ | |||
+ | -- Get shader toolkit viewport adjustments (probably not fully supported yet) | ||
+ | function get_viewport_adjustments(bubble_style) | ||
+ | local shader_adjusted_viewport = {x = 0, y = 0, scale = 1} | ||
+ | |||
+ | if enable_shader_viewport_adjustments and shader_newViewport ~= nil and bubble_style.fixed_pos == false then | ||
+ | if shader_viewportTransition > 0.0 and shader_viewportTransition < 1.0 then | ||
+ | shader_adjusted_viewport.scale = (1.0 - shader_viewportTransition) * shader_oldViewport.scale + shader_viewportTransition * shader_newViewport.scale | ||
+ | |||
+ | local startCenter = { | ||
+ | x = shader_oldViewport.x + c_res.x / shader_oldViewport.scale * shader_interpolationPoint.x, | ||
+ | y = shader_oldViewport.y + c_res.y / shader_oldViewport.scale * shader_interpolationPoint.y | ||
+ | } | ||
+ | |||
+ | local endCenter = { | ||
+ | x = shader_newViewport.x + c_res.x / shader_newViewport.scale * shader_interpolationPoint.x, | ||
+ | y = shader_newViewport.y + c_res.y / shader_newViewport.scale * shader_interpolationPoint.y | ||
+ | } | ||
+ | |||
+ | local interpolatedCenter = { | ||
+ | x = startCenter.x * (1.0 - shader_viewportTransition) + endCenter.x * shader_viewportTransition, | ||
+ | y = startCenter.y * (1.0 - shader_viewportTransition) + endCenter.y * shader_viewportTransition | ||
+ | } | ||
+ | |||
+ | shader_adjusted_viewport.x = interpolatedCenter.x - c_res.x / shader_scale * shader_interpolationPoint.x | ||
+ | shader_adjusted_viewport.y = interpolatedCenter.y - c_res.y / shader_scale * shader_interpolationPoint.y | ||
+ | else | ||
+ | shader_adjusted_viewport = shader_newViewport | ||
+ | end | ||
+ | end | ||
+ | |||
+ | return shader_adjusted_viewport | ||
+ | end | ||
+ | |||
+ | |||
+ | |||
+ | -- Calculate text and bubble dimensions | ||
+ | function get_text_and_dims(key, val, char, bubble_style) | ||
+ | local txt = "" | ||
+ | local lines = {} | ||
+ | local text_dim = {x = 0, y = 0} | ||
+ | local bubble_dim = {x = 0, y = 0} | ||
+ | |||
+ | graphics.font = char.Font | ||
+ | |||
+ | -- Take from bubbles table, if cached | ||
+ | if val.text_dim ~= nil then | ||
+ | txt = val.txt | ||
+ | lines = val.lines | ||
+ | text_dim = val.text_dim | ||
+ | bubble_dim = val.bubble_dim | ||
+ | else | ||
+ | txt = val.Text:gsub("<br/"..">", "\n") | ||
+ | lines = graphics.performLinebreaks(txt) | ||
− | + | for k, line in ipairs(lines) do | |
− | if | + | local tempdim = graphics.fontDimension(line) |
− | + | ||
+ | if text_dim.x < tempdim.x then | ||
+ | text_dim.x = tempdim.x | ||
+ | end | ||
+ | end | ||
+ | |||
+ | text_dim.y = #lines * (char.Font.Size + char.Font.VerticalLetterSpacing) - char.Font.VerticalLetterSpacing | ||
+ | |||
+ | bubble_dim.x = text_dim.x + bubble_style.padding.right + bubble_style.padding.left | ||
+ | bubble_dim.y = text_dim.y + bubble_style.padding.top + bubble_style.padding.bottom | ||
+ | |||
+ | if bubble_style.fixed_width > 0 then | ||
+ | if not bubble_style.expand_fixed_dims or bubble_style.fixed_width > bubble_dim.x then | ||
+ | bubble_dim.x = bubble_style.fixed_width | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if bubble_style.fixed_height > 0 then | ||
+ | if not bubble_style.expand_fixed_dims or bubble_style.fixed_height > bubble_dim.y then | ||
+ | bubble_dim.y = bubble_style.fixed_height | ||
end | end | ||
− | |||
− | |||
− | |||
end | end | ||
− | if bubble_style.align_v == "bottom" then | + | -- Cache dimensions |
− | + | bubbles[key].txt = txt | |
− | + | bubbles[key].lines = lines | |
+ | bubbles[key].text_dim = text_dim | ||
+ | bubbles[key].bubble_dim = bubble_dim | ||
+ | end | ||
+ | |||
+ | return { lines = lines, text_dim = text_dim, bubble_dim = bubble_dim } | ||
+ | end | ||
+ | |||
+ | |||
+ | |||
+ | -- Calculate the bubble position | ||
+ | function get_bubble_position(pos, bubble_style, bubble_dim, char, char_facing, shader_adjusted_viewport) | ||
+ | -- Change reference point for bubble with fixed position | ||
+ | if bubble_style.fixed_pos ~= false then | ||
+ | pos.x = bubble_style.fixed_pos.x + game.ScrollPosition.x | ||
+ | pos.y = bubble_style.fixed_pos.y + game.ScrollPosition.y | ||
+ | end | ||
+ | |||
+ | -- X position | ||
+ | if bubble_style.align_h == "right" or (bubble_style.align_h == "char_facing" and char_facing == "right") then | ||
+ | pos.x = (pos.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale | ||
+ | elseif bubble_style.align_h == "left" or (bubble_style.align_h == "char_facing" and char_facing == "left") then | ||
+ | pos.x = (pos.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale - bubble_dim.x | ||
+ | else -- center | ||
+ | pos.x = (pos.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale - bubble_dim.x / 2 | ||
+ | end | ||
+ | |||
+ | if char_facing == "left" then | ||
+ | pos.x = pos.x - bubble_style.bubble_offset_x | ||
+ | else -- right | ||
+ | pos.x = pos.x + bubble_style.bubble_offset_x | ||
+ | end | ||
+ | |||
+ | if pos.x < bubble_style.min_distance then | ||
+ | pos.x = bubble_style.min_distance | ||
+ | elseif pos.x > game.WindowResolution.x - bubble_dim.x - bubble_style.min_distance then | ||
+ | pos.x = game.WindowResolution.x - bubble_dim.x - bubble_style.min_distance | ||
+ | end | ||
+ | |||
+ | -- Y position | ||
+ | local temp_y = 0 | ||
+ | local char_scale = 1 | ||
+ | |||
+ | if bubble_style.adjust_v_offset_to_char_scale then | ||
+ | char_scale = (char.ScaleFactor / 100) * shader_adjusted_viewport.scale | ||
+ | |||
+ | if char.Scale then | ||
+ | char_scale = char_scale * (char.Size / 100) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if bubble_style.align_v == "bottom" then | ||
+ | temp_y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale + math.floor(bubble_style.bubble_bottom_offset_y * char_scale) | ||
+ | else -- top | ||
+ | temp_y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale - bubble_dim.y - math.floor(bubble_style.bubble_top_offset_y * char_scale) | ||
+ | end | ||
+ | |||
+ | if temp_y < bubble_style.min_distance then | ||
+ | if bubble_style.flip_v_align_on_edge and bubble_style.fixed_pos == false then | ||
+ | bubble_style.align_v_temp = "bottom" | ||
+ | pos.y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale + math.floor(bubble_style.bubble_bottom_offset_y * char_scale) | ||
else | else | ||
− | + | pos.y = bubble_style.min_distance | |
− | |||
end | end | ||
+ | elseif temp_y > game.WindowResolution.y - bubble_dim.y - bubble_style.min_distance then | ||
+ | if bubble_style.flip_v_align_on_edge and bubble_style.fixed_pos == false then | ||
+ | bubble_style.align_v_temp = "top" | ||
+ | pos.y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale - bubble_dim.y - math.floor(bubble_style.bubble_top_offset_y * char_scale) | ||
+ | else | ||
+ | pos.y = game.WindowResolution.y - bubble_dim.y - bubble_style.min_distance | ||
+ | end | ||
+ | else | ||
+ | pos.y = temp_y | ||
+ | bubble_style.align_v_temp = nil | ||
+ | end | ||
+ | |||
+ | return pos | ||
+ | end | ||
+ | |||
− | |||
− | + | -- Calculate position of portrait image | |
− | + | function get_portrait_position(pos, bubble_style, bubble_dim, portrait_sprite) | |
+ | local portrait_pos = { x = pos.x, y = pos.y } | ||
+ | |||
+ | if bubble_style.portrait_align_h == "right" then | ||
+ | portrait_pos.x = pos.x + bubble_dim.x - portrait_sprite.width | ||
+ | end | ||
+ | |||
+ | if bubble_style.portrait_align_v == "center" then | ||
+ | portrait_pos.y = pos.y + (bubble_dim.y - portrait_sprite.height) / 2 | ||
+ | elseif bubble_style.portrait_align_v == "bottom" then | ||
+ | portrait_pos.y = pos.y + bubble_dim.y - portrait_sprite.height | ||
end | end | ||
+ | |||
+ | return portrait_pos | ||
+ | end | ||
+ | |||
+ | |||
+ | |||
+ | -- Calculate position of pointer image | ||
+ | function get_pointer_position(pos, bubble_style, bubble_dim, pointer_offset, char, char_facing, shader_adjusted_viewport, pointer_sprite) | ||
+ | -- Calculate pointer position | ||
+ | local pointer_pos = {x = 0, y = 0} | ||
+ | |||
+ | if bubble_style.align_h == "right" or (bubble_style.align_h == "char_facing" and char_facing == "right") then | ||
+ | -- if bubble is aligned right, pointer is positioned leftmost | ||
+ | pointer_pos.x = pos.x + pointer_offset | ||
+ | |||
+ | -- Adjust position, if bubble is too close to the edge | ||
+ | if bubble_style.fixed_pos == false then | ||
+ | if pointer_pos.x < (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset then | ||
+ | pointer_pos.x = (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset | ||
+ | end | ||
+ | end | ||
+ | elseif bubble_style.align_h == "left" or (bubble_style.align_h == "char_facing" and char_facing == "left") then | ||
+ | -- if bubble is aligned left, pointer is positioned rightmost | ||
+ | pointer_pos.x = pos.x + bubble_dim.x - pointer_sprite.width + pointer_offset | ||
− | + | -- Adjust position, if bubble is too close to the edge | |
− | + | if bubble_style.fixed_pos == false then | |
− | + | if pointer_pos.x > (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset then | |
− | + | pointer_pos.x = (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset | |
+ | end | ||
+ | end | ||
+ | else -- center | ||
+ | -- if bubble is aligned center, pointer is positioned at character | ||
+ | if bubble_style.fixed_pos == false then | ||
+ | pointer_pos.x = (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset | ||
+ | else | ||
+ | pointer_pos.x = pos.x + bubble_dim.x / 2 | ||
+ | end | ||
+ | end | ||
− | + | -- Adjust position, if character is too close to the edge | |
− | + | if bubble_style.fixed_pos == false then | |
− | + | if pointer_pos.x < pos.x - pointer_offset then | |
− | + | pointer_pos.x = pos.x - pointer_offset | |
− | + | elseif pointer_pos.x > pos.x + bubble_dim.x - pointer_sprite.width - pointer_offset then | |
− | + | pointer_pos.x = pos.x + bubble_dim.x - pointer_sprite.width - pointer_offset | |
+ | end | ||
+ | |||
+ | if pointer_pos.x < pos.x + bubble_style.pointer_min_distance then | ||
+ | pointer_pos.x = pos.x + bubble_style.pointer_min_distance | ||
+ | elseif pointer_pos.x > pos.x + bubble_dim.x - pointer_sprite.width - bubble_style.pointer_min_distance then | ||
+ | pointer_pos.x = pos.x + bubble_dim.x - pointer_sprite.width - bubble_style.pointer_min_distance | ||
end | end | ||
+ | end | ||
− | + | if (bubble_style.align_v_temp ~= nil and bubble_style.align_v_temp == "bottom") or (bubble_style.align_v_temp == nil and bubble_style.align_v == "bottom") then | |
− | + | -- pointer on top when bubble aligned to bottom | |
− | + | pointer_pos.y = pos.y - pointer_sprite.height + bubble_style.pointer_top_offset_y | |
− | + | else | |
− | + | -- pointer on bottom when bubble aligned to top | |
+ | pointer_pos.y = pos.y + bubble_dim.y - bubble_style.pointer_bottom_offset_y | ||
end | end | ||
− | end | + | |
+ | return pointer_pos | ||
+ | end | ||
Line 457: | Line 772: | ||
graphics.addDrawFunc("bubble_below_interface()", 0) | graphics.addDrawFunc("bubble_below_interface()", 0) | ||
graphics.addDrawFunc("bubble_above_interface()", 1) | graphics.addDrawFunc("bubble_above_interface()", 1) | ||
+ | |||
+ | |||
+ | |||
+ | --[[ | ||
+ | |||
+ | MIT License | ||
+ | |||
+ | Copyright 2024 The Argonauts | ||
+ | |||
+ | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
+ | |||
+ | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
+ | |||
+ | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
+ | |||
+ | ]] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 465: | Line 796: | ||
! style="text-align:left" | Name !! style="text-align:left" | Description | ! style="text-align:left" | Name !! style="text-align:left" | Description | ||
|- | |- | ||
− | | [[media:argo_bubbles_2. | + | | [[media:argo_bubbles_2.3.0.zip|argo_bubbles_2.3.0.zip]] || A working example of the script in action. ''Visionaire Studio 5.3+'' required to run the included .ved file(s). |
|}{{toc}} | |}{{toc}} |
Latest revision as of 12:43, 13 February 2024
Name | Type | By |
---|---|---|
Argo Bubbles - Dynamic stylized speech bubbles | Definition | The Argonauts |
This script allows you to turn texts spoken by characters into speech bubbles.
The "Argo Bubbles" script is based on the "Advanced Speechbubble Script" by Sebastian aka turbomodus. | |
Instructions
1. Add the main script to the Visionaire Studio Script Editor and set the script as a definition script.
2. Create the bubble graphics and add them to your project folder or any subfolder. Basic graphics are included in the demo project which can be downloaded from the resources section.
3. Adjust the settings at the top of the script (between "DEFINE YOUR SETTINGS HERE" and "USUALLY NO NEED TO CHANGE ANYTHING BELOW THIS LINE").
Description
The script is extensively commented, giving you all the information you need to make use of its features. A detailed description of the settings can be found on this page.
How the Argo Bubbles are created
- Display of the regular character text is prevented. The text is stored internally to get integrated in the speech bubble. The font used in the bubble will stay the same though - it is the regular font defined in the editor for a specific character. Choose a font for the character that fits your speech bubble design. Any text formatting or text effects are preserved.
- The main bubble graphic is cut into nine pieces which are individually stretched and then reassembled to match the calculated size of the bubble (see the graphic below). This "ninerect" technique ensures that the corners don't get distorted, but leads to limitations to what the speech bubbles can look like. It is not possible with this script to have elliptical or fancy-shaped bubbles. They need to be more or less rectangular.
- You usually want to have a pointer attached to the bubble, pointing at the character who is currently speaking. Depending on your alignment settings and on the direction the character is facing, the script selects one of up to four pointer graphics you can provide and places it accordingly. Pointers are optional though.
- You have the option to add an additional image to the bubble. It is intended for a portrait picture of the character, but you can choose any image you like, of course.
Settings
General settings
- enable_shader_viewport_adjustments: If set to true, the script will take viewport manipulations by Visionaire's Shader Toolkit into account. If you don't make use of the Shader Toolkit, you can set this option to false.
Default bubble style
- align_h: The horizontal alignment of the bubble in relation to the character. Possible values are (center|left|right|char_facing). The "char_facing" value results in a left/right aligned bubble, respectively, depending on the character's facing direction.
- align_v: The vertical alignment of the bubble in relation to the character. Possible values are (top|bottom). This setting also defines the vertical positioning of the bubble pointer: it is attached to the bottom of the bubble, if the bubble is top-aligned, and vice versa.
- flip_v_align_on_edge: If set to true, a top-aligned bubble will automatically turn into a bottom-aligned bubble (and vice versa) when the character gets too close to the top (bottom) edge of the screen.
- bubble_offset_x: The horizontal offset of the bubble from the initial position centered above the character's head. Positive values move the bubble in the character's facing direction.
- bubble_top_offset_y: The vertical offset of the bubble for top-aligned bubbles. Positive values move the bubble up.
- bubble_bottom_offset_y: The vertical offset of the bubble for bottom-aligned bubbles. Positive values move the bubble down.
- adjust_v_offset_to_char_scale: If set to true, the current character scaling is taken into account when applying the vertical bubble offset. The offset (as defined in "bubble_top_offset_y" or "bubble_bottom_offset_y") will be reduced/increased according to the character scale.
- color: Color tint for the bubble (and pointer). Please note that this option takes the color in hexadecimal BGR notation (instead of RGB), prepended with "0x". The default value of 0xffffff (white) results in no tint.
- text_align_h: The horizontal text alignment inside the bubble. Possible values are (center|left|right). This option has a visible effect only if it's multiline text and/or if the bubble has a fixed width.
- text_align_v: The vertical text alignment inside the bubble. Possible values are (center|top|bottom). This option has a visible effect only if the bubble has a fixed height.
- padding: The distance between the text block and the outer edge of the bubble, defined in a table {"top", "right", "bottom", "left"}.
- fixed_width: The fixed width of the bubble. Set the value to 0 to not set a fixed width. Be aware that the width of the bubble does currently not affect line breaks.
- fixed_height: The fixed height of the bubble. Set the value to 0 to not set a fixed height.
- fixed_pos: A fixed position on the screen to show the bubble at, defined in a table {"x", "y"}. That position will serve as the root point of the position calculation (it substitutes the character's position), so it depends on your alignment settings where the bubble will be placed. Don't set "align_h" to "char_facing" when using the fixed position, because the bubble position would change depending on the character's facing direction.
- expand_fixed_dims: If set to true, a fixed bubble width/height will expand if the text doesn't fit in the fix-sized bubble. The fixed width/height will act like a min-width/min-height setting.
- file_bubble: The path to the main bubble graphic. The graphic needs to be suited for the "ninerect" technique. The filepath must be relative to the project root folder and be prepended with "vispath:".
- ninerect: The measurements used to cut the "ninerect" bubble graphic, defined in a table {"x", "y", "width, "height"}. The x/y values define the width/height of the top left tile of the "ninerect", the width/height values define the width/height of the center tile (see the graphic below).
- file_portrait: The path to a portrait image. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a portrait image.
- portrait_align_h: The horizontal alignment of the portrait image in the bubble. Possible values are (left|right). There will be no padding between the portrait and the edge of the bubble; the padding setting only applies to the text. Add transparent areas to your portrait image to adjust the position (see the graphic below).
- portrait_align_v: The vertical alignment of the portrait image in the bubble. Possible values are (top|center|bottom). There will be no padding between the portrait and the edge of the bubble; the padding setting only applies to the text. Add transparent areas to your portrait image to adjust the position (see the graphic below).
- file_pointer_bottom_right: The path to the down-right-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer.
- file_pointer_bottom_left: The path to the down-left-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer.
- pointer_bottom_right_offset_x: The horizontal offset of the down-right-pointing pointer. Positive values move the pointer in the character's facing direction.
- pointer_bottom_left_offset_x: The horizontal offset of the down-left-pointing pointer. Positive values move the pointer in the character's facing direction.
- pointer_bottom_offset_y: The vertical offset of the down-pointing pointer. Positive values move the pointer inwards.
- file_pointer_top_right: The path to the up-right-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer.
- file_pointer_top_left: The path to the up-left-pointing pointer graphic. The filepath must be relative to the project root folder and be prepended with "vispath:". Set the value to "" (empty string) for a bubble without a pointer.
- pointer_top_right_offset_x: The horizontal offset of the up-right-pointing pointer. Positive values move the pointer in the character's facing direction.
- pointer_top_left_offset_x: The horizontal offset of the up-left-pointing pointer. Positive values move the pointer in the character's facing direction.
- pointer_top_offset_y: The vertical offset of the up-pointing pointer. Positive values move the pointer inwards.
- pointer_min_distance: The minimum distance a pointer has to keep from the edge of the bubble (see the graphic below).
- min_distance: The minimum distance the bubble has to keep from the edge of the screen.
Custom bubble styles
A main feature of the "Argo Bubbles" script is the ability to define multiple bubble styles. You could have a different bubble color/design for each character, for example. Or depending on where an NPC stands, that character might need a different positioning of the bubble than the default one. You can even define multiple styles for the same character and switch between them during the game.
The default bubble style is defined in the "default_style" table. If you would like to add more styles:
- Add custom bubble styles to the custom_styles table. You can override all properties of the default style. Undefined properties will fallback to the default values.
- Each custom bubble style is defined for a specific character. Add the name of the character as an additional char property.
-- Example
local custom_styles = {
{
char = "Hero",
align_h = "left",
align_v = "bottom",
bubble_offset_x = 40,
bubble_top_offset_y = 35,
file_pointer_top_right = "vispath:gui/bubble_pointer_tr.png",
pointer_top_offset_y = 10
},
{
char = "Villain",
file_bubble = "vispath:gui/bubble_black.png"
},
{
-- next style definition here ...
}
}
- If a character is meant to use only a single style throughout the game (either the default style or a custom one), you don't have to do anything else.
- If you want the character to switch styles during the game, add a Visionaire value called "argo_bubble" to the character. By changing that value you can select the style to be active. A value of 0 selects the default style. A value of 1 selects the first custom style defined for that character, a value of 2 the second custom style for that character etc. You can define as many custom styles per character as you like - just add them to the "custom_styles" table.
Advanced settings
- ab_bind_to_handler: You can define the "textStopped" event handler only once in your game project. If you want to do that in another script, set this variable to false. You'll then have to call "destroy_argo_bubble(text)" in that other script.
- ab_on_text_stopped(): You can define the "textStopped" event handler only once in your game project. If you want to do that here in the Argo Bubbles script, but have other functions you want to call from this handler, add these calls to this function. Only applicable if "ab_bind_to_handler" is set to true.
Kill the bubbles
Call the kill_argo_bubbles()
function to immediately remove all current bubbles from the screen. You'll need this, for example, if the user can open the menu by pressing a key, because it would be possible to do that while a character is talking and thus a speech bubble is visible. By calling the kill function you prevent the bubble from appearing on the menu as well. A scene change does not automatically hide a bubble.
Tutorial
For a deeper understanding of how the speech bubbles are created and how they replace the text displayed by Visionaire, please have a look at the tutorial; it is included in the .zip file. You don't have to read it, if you just want to use the script in your project.
Be aware that the tutorial was created for script version 2.1.2. The basic idea is still the same, but the code has changed a lot since then.
Main Script
--[[
Argo Bubbles
------------
This script turns character texts into speech bubbles. It allows defining differently styled bubbles for each character, or even different speech bubble styles for the same character to switch between during the game.
Authors: The Argonauts
(with contributions by Mulewa and some lines of code
taken from the Visionaire Shader Toolkit by Simon Scheckel)
Version: 2.3.0
Date: 2024-01-05
Requirements: Visionaire Studio 5.3 or higher
License: MIT License (details at the bottom of the script)
Based on: Advanced Speechbubble Script [v1.1] (10/13/2018), written by Sebastian/TURBOMODUS (with permission)
Instructions: https://wiki.visionaire-tracker.net/wiki/Argo_Bubbles
Argonauts games: https://the-argonauts.itch.io/
]]
-------------------------------
-- DEFINE YOUR SETTINGS HERE --
-------------------------------
-- Detailed description of the settings:
-- https://wiki.visionaire-tracker.net/wiki/Argo_Bubbles#Settings
-- or in the "readme.txt" file coming with this script
-- GENERAL SETTINGS
local enable_shader_viewport_adjustments = true
-- DEFAULT BUBBLE STYLE
local default_style = {
align_h = "center",
align_v = "top",
flip_v_align_on_edge = true,
bubble_offset_x = 0,
bubble_top_offset_y = 25,
bubble_bottom_offset_y = 70,
adjust_v_offset_to_char_scale = true,
color = 0xffffff,
text_align_h = "center",
text_align_v = "center",
padding = { top = 15, right = 20, bottom = 12, left = 20 },
fixed_width = 0,
fixed_height = 0,
fixed_pos = false,
expand_fixed_dims = true,
file_bubble = "vispath:gui/bubble.png",
ninerect = { x = 20, y = 15, width = 30, height = 20 },
file_portrait = "",
portrait_align_h = "left",
portrait_align_v = "top",
file_pointer_bottom_right = "vispath:gui/bubble_pointer_br.png",
file_pointer_bottom_left = "vispath:gui/bubble_pointer_bl.png",
pointer_bottom_right_offset_x = 20,
pointer_bottom_left_offset_x = 0,
pointer_bottom_offset_y = 3,
file_pointer_top_right = "",
file_pointer_top_left = "",
pointer_top_right_offset_x = 0,
pointer_top_left_offset_x = 0,
pointer_top_offset_y = 0,
pointer_min_distance = 15,
min_distance = 15
}
-- CUSTOM BUBBLE STYLES
local custom_styles = {}
-- ADVANCED SETTINGS
local ab_bind_to_handler = true
function ab_on_text_stopped(text)
-- add your functions here
end
--------------------------------------------------------
-- USUALLY NO NEED TO CHANGE ANYTHING BELOW THIS LINE --
--------------------------------------------------------
-- Build new custom styles table "c_styles" and fill up with default values
local c_styles = {}
if custom_styles ~= nil then
for key, styles in pairs(custom_styles) do
local num_char_styles = 1
if c_styles[ styles["char"] ] ~= nil then
num_char_styles = #c_styles[ styles["char"] ] + 1
c_styles[ styles["char"] ][num_char_styles] = {}
else
c_styles[ styles["char"] ] = {{}}
end
for k, v in pairs(default_style) do
c_styles[ styles["char"] ][num_char_styles][k] = v
end
for k, v in pairs(styles) do
c_styles[ styles["char"] ][num_char_styles][k] = v
end
end
end
-- CALL FUNCTIONS
local bubbles = {}
local killed = {}
-- New mode: create bubbles in "textRender" function
function show_argo_bubble(text)
if text.Owner.tableId == eCharacters then
-- Add current character text to the bubbles table, if not already there (and bubbles not killed early)
if bubbles[text.id] == nil and killed[text.id] == nil then
bubbles[text.id] = {
Text = text.CurrentText,
Owner = text.Owner.name,
Background = text.Background,
Object = text
}
end
-- override the default text rendering
return true
end
return false
end
function destroy_argo_bubble(text)
-- Remove current text from bubbles table
bubbles[text.id] = nil
-- Call external functions that use the "textStopped" event handler
if ab_on_text_stopped ~= nil then
ab_on_text_stopped(text)
end
end
-- Call this function to immediately destroy all bubbles
function kill_argo_bubbles()
-- Since we use the render hook, the bubbles table gets quickly filled again;
-- so we store the ids of texts to be killed in a separate table
for key, val in pairs(bubbles) do
killed[key] = key
end
bubbles = {}
end
-- Register hook function and bind to event handler
registerHookFunction("textRender", "show_argo_bubble")
if ab_bind_to_handler then
registerEventHandler("textStopped","destroy_argo_bubble")
end
-- DRAW FUNCTIONS
-- Draw background texts from bubbles table below interfaces (and cursors)
function bubble_below_interface()
for key, val in pairs(bubbles) do
if val.Background then
create_bubble(key, val)
end
end
end
-- Draw non-background texts from bubbles table above interfaces
function bubble_above_interface()
for key, val in pairs(bubbles) do
if not val.Background then
create_bubble(key, val)
end
end
end
-- Main bubble function
function create_bubble(key, val)
-- Get talking character
local char = Characters[val.Owner]
local pos = graphics.getCharacterTextPosition(char)
-- Get bubble style
local bubble_style = get_style(key, val, char)
-- Get shader toolkit viewport adjustments
local shader_adjusted_viewport = get_viewport_adjustments(bubble_style)
-- Facing direction (if bubble alignment is set to "left" or "right", the actual facing direction is irrelevant)
local char_facing = "right"
if bubble_style.align_h == "left" then
char_facing = "left"
elseif bubble_style.align_h ~= "right" then
if char.Direction > 90 and char.Direction < 270 then
char_facing = "left"
end
end
-- Get text and bubble dimensions
local text_and_bubble_dims = get_text_and_dims(key, val, char, bubble_style)
local text_dim = text_and_bubble_dims.text_dim
local bubble_dim = text_and_bubble_dims.bubble_dim
-- Get the bubble position
pos = get_bubble_position(pos, bubble_style, bubble_dim, char, char_facing, shader_adjusted_viewport)
-- Get bubble graphic and define ninerect geometry
local bubble_sprite
local dest_rect = { x = pos.x, y = pos.y, width = bubble_dim.x, height = bubble_dim.y }
-- Take sprite from bubbles table, if cached
if val.bubble_sprite ~= nil then
bubble_sprite = val.bubble_sprite
else
bubble_sprite = graphics.loadFromFile(bubble_style.file_bubble)
-- Cache bubble sprite
bubbles[key].bubble_sprite = bubble_sprite
end
-- Draw the bubble
graphics.drawSpriteWithNineRect(bubble_sprite, dest_rect, bubble_style.ninerect, bubble_style.color, 1.0)
-- Get portrait graphic
local portrait_sprite = nil
-- Take sprite from bubbles table, if cached
if val.portrait_sprite ~= nil then
portrait_sprite = val.portrait_sprite
elseif bubble_style.file_portrait ~= nil and bubble_style.file_portrait ~= "" then
portrait_sprite = graphics.loadFromFile(bubble_style.file_portrait)
-- Cache bubble sprite
bubbles[key].portrait_sprite = portrait_sprite
end
-- Continue with portrait, if graphic has been defined
if portrait_sprite ~= nil then
-- Get position of portrait image
portrait_sprite.position = get_portrait_position(pos, bubble_style, bubble_dim, portrait_sprite)
-- Draw the portrait
graphics.drawSprite(portrait_sprite, 1.0)
end
-- Get pointer graphic
local pointer_sprite = nil
local pointer_offset = 0
if char_facing == "left" then
-- right pointer when char facing left
if (bubble_style.align_v_temp ~= nil and bubble_style.align_v_temp == "bottom") or (bubble_style.align_v_temp == nil and bubble_style.align_v == "bottom") then
-- top pointer when bubble aligned to bottom
if bubble_style.file_pointer_top_right ~= nil and bubble_style.file_pointer_top_right ~= "" then
pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_top_right)
pointer_offset = -bubble_style.pointer_top_right_offset_x
end
else
-- bottom pointer when bubble aligned to top
if bubble_style.file_pointer_bottom_right ~= nil and bubble_style.file_pointer_bottom_right ~= "" then
pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_bottom_right)
pointer_offset = -bubble_style.pointer_bottom_right_offset_x
end
end
else
-- left pointer when char facing right
if (bubble_style.align_v_temp ~= nil and bubble_style.align_v_temp == "bottom") or (bubble_style.align_v_temp == nil and bubble_style.align_v == "bottom") then
-- top pointer when bubble aligned to bottom
if bubble_style.file_pointer_top_left ~= nil and bubble_style.file_pointer_top_left ~= "" then
pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_top_left)
pointer_offset = bubble_style.pointer_top_left_offset_x
end
else
-- bottom pointer when bubble aligned to top
if bubble_style.file_pointer_bottom_left ~= nil and bubble_style.file_pointer_bottom_left ~= "" then
pointer_sprite = graphics.loadFromFile(bubble_style.file_pointer_bottom_left)
pointer_offset = bubble_style.pointer_bottom_left_offset_x
end
end
end
-- Continue with pointer, if graphic has been defined
if pointer_sprite ~= nil then
-- Get position of pointer image
pointer_sprite.position = get_pointer_position(pos, bubble_style, bubble_dim, pointer_offset, char, char_facing, shader_adjusted_viewport, pointer_sprite)
-- Draw the pointer
graphics.drawSprite(pointer_sprite, 1.0, bubble_style.color)
end
-- Draw the text
-- Calculate vertical alignment of text inside bubble (only if fixed height and not at the top)
local text_box_pos_y = pos.y + bubble_style.padding.top
if bubble_style.fixed_height ~= nil then
if bubble_style.text_align_v == "center" then
text_box_pos_y = pos.y + bubble_style.padding.top + (bubble_dim.y - bubble_style.padding.top - bubble_style.padding.bottom - text_dim.y) / 2
elseif bubble_style.text_align_v == "bottom" then
text_box_pos_y = pos.y + bubble_dim.y - bubble_style.padding.bottom - text_dim.y
end
end
-- Draw the text object
local text_pos = {x = 0, y = text_box_pos_y}
local alignment = 2
if bubble_style.text_align_h == "left" then
text_pos.x = pos.x + bubble_style.padding.left
alignment = 0
elseif bubble_style.text_align_h == "right" then
text_pos.x = pos.x + bubble_dim.x - bubble_style.padding.right
alignment = 1
else -- center
text_pos.x = pos.x + bubble_style.padding.left + (bubble_dim.x - bubble_style.padding.left - bubble_style.padding.right) / 2
end
graphics.drawTextObject(val.Object, text_pos.x, text_pos.y, alignment)
end
-- Get style for the current bubble
function get_style(key, val, char)
local bubble_style = default_style
-- Take style from bubbles table, if cached
if val.style ~= nil then
bubble_style = val.style
else
if c_styles ~= nil and c_styles[char.name] ~= nil then
if Characters[char.name].Values["argo_bubble"] ~= nil then
if c_styles[char.name][ Characters[char.name].Values["argo_bubble"].Int ] ~= nil then
bubble_style = c_styles[char.name][ Characters[char.name].Values["argo_bubble"].Int ]
end
else
bubble_style = c_styles[char.name][1]
end
end
-- Cache style
bubbles[key].style = bubble_style
end
return bubble_style
end
-- Get shader toolkit viewport adjustments (probably not fully supported yet)
function get_viewport_adjustments(bubble_style)
local shader_adjusted_viewport = {x = 0, y = 0, scale = 1}
if enable_shader_viewport_adjustments and shader_newViewport ~= nil and bubble_style.fixed_pos == false then
if shader_viewportTransition > 0.0 and shader_viewportTransition < 1.0 then
shader_adjusted_viewport.scale = (1.0 - shader_viewportTransition) * shader_oldViewport.scale + shader_viewportTransition * shader_newViewport.scale
local startCenter = {
x = shader_oldViewport.x + c_res.x / shader_oldViewport.scale * shader_interpolationPoint.x,
y = shader_oldViewport.y + c_res.y / shader_oldViewport.scale * shader_interpolationPoint.y
}
local endCenter = {
x = shader_newViewport.x + c_res.x / shader_newViewport.scale * shader_interpolationPoint.x,
y = shader_newViewport.y + c_res.y / shader_newViewport.scale * shader_interpolationPoint.y
}
local interpolatedCenter = {
x = startCenter.x * (1.0 - shader_viewportTransition) + endCenter.x * shader_viewportTransition,
y = startCenter.y * (1.0 - shader_viewportTransition) + endCenter.y * shader_viewportTransition
}
shader_adjusted_viewport.x = interpolatedCenter.x - c_res.x / shader_scale * shader_interpolationPoint.x
shader_adjusted_viewport.y = interpolatedCenter.y - c_res.y / shader_scale * shader_interpolationPoint.y
else
shader_adjusted_viewport = shader_newViewport
end
end
return shader_adjusted_viewport
end
-- Calculate text and bubble dimensions
function get_text_and_dims(key, val, char, bubble_style)
local txt = ""
local lines = {}
local text_dim = {x = 0, y = 0}
local bubble_dim = {x = 0, y = 0}
graphics.font = char.Font
-- Take from bubbles table, if cached
if val.text_dim ~= nil then
txt = val.txt
lines = val.lines
text_dim = val.text_dim
bubble_dim = val.bubble_dim
else
txt = val.Text:gsub("<br/"..">", "\n")
lines = graphics.performLinebreaks(txt)
for k, line in ipairs(lines) do
local tempdim = graphics.fontDimension(line)
if text_dim.x < tempdim.x then
text_dim.x = tempdim.x
end
end
text_dim.y = #lines * (char.Font.Size + char.Font.VerticalLetterSpacing) - char.Font.VerticalLetterSpacing
bubble_dim.x = text_dim.x + bubble_style.padding.right + bubble_style.padding.left
bubble_dim.y = text_dim.y + bubble_style.padding.top + bubble_style.padding.bottom
if bubble_style.fixed_width > 0 then
if not bubble_style.expand_fixed_dims or bubble_style.fixed_width > bubble_dim.x then
bubble_dim.x = bubble_style.fixed_width
end
end
if bubble_style.fixed_height > 0 then
if not bubble_style.expand_fixed_dims or bubble_style.fixed_height > bubble_dim.y then
bubble_dim.y = bubble_style.fixed_height
end
end
-- Cache dimensions
bubbles[key].txt = txt
bubbles[key].lines = lines
bubbles[key].text_dim = text_dim
bubbles[key].bubble_dim = bubble_dim
end
return { lines = lines, text_dim = text_dim, bubble_dim = bubble_dim }
end
-- Calculate the bubble position
function get_bubble_position(pos, bubble_style, bubble_dim, char, char_facing, shader_adjusted_viewport)
-- Change reference point for bubble with fixed position
if bubble_style.fixed_pos ~= false then
pos.x = bubble_style.fixed_pos.x + game.ScrollPosition.x
pos.y = bubble_style.fixed_pos.y + game.ScrollPosition.y
end
-- X position
if bubble_style.align_h == "right" or (bubble_style.align_h == "char_facing" and char_facing == "right") then
pos.x = (pos.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale
elseif bubble_style.align_h == "left" or (bubble_style.align_h == "char_facing" and char_facing == "left") then
pos.x = (pos.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale - bubble_dim.x
else -- center
pos.x = (pos.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale - bubble_dim.x / 2
end
if char_facing == "left" then
pos.x = pos.x - bubble_style.bubble_offset_x
else -- right
pos.x = pos.x + bubble_style.bubble_offset_x
end
if pos.x < bubble_style.min_distance then
pos.x = bubble_style.min_distance
elseif pos.x > game.WindowResolution.x - bubble_dim.x - bubble_style.min_distance then
pos.x = game.WindowResolution.x - bubble_dim.x - bubble_style.min_distance
end
-- Y position
local temp_y = 0
local char_scale = 1
if bubble_style.adjust_v_offset_to_char_scale then
char_scale = (char.ScaleFactor / 100) * shader_adjusted_viewport.scale
if char.Scale then
char_scale = char_scale * (char.Size / 100)
end
end
if bubble_style.align_v == "bottom" then
temp_y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale + math.floor(bubble_style.bubble_bottom_offset_y * char_scale)
else -- top
temp_y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale - bubble_dim.y - math.floor(bubble_style.bubble_top_offset_y * char_scale)
end
if temp_y < bubble_style.min_distance then
if bubble_style.flip_v_align_on_edge and bubble_style.fixed_pos == false then
bubble_style.align_v_temp = "bottom"
pos.y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale + math.floor(bubble_style.bubble_bottom_offset_y * char_scale)
else
pos.y = bubble_style.min_distance
end
elseif temp_y > game.WindowResolution.y - bubble_dim.y - bubble_style.min_distance then
if bubble_style.flip_v_align_on_edge and bubble_style.fixed_pos == false then
bubble_style.align_v_temp = "top"
pos.y = (pos.y - game.ScrollPosition.y - shader_adjusted_viewport.y) * shader_adjusted_viewport.scale - bubble_dim.y - math.floor(bubble_style.bubble_top_offset_y * char_scale)
else
pos.y = game.WindowResolution.y - bubble_dim.y - bubble_style.min_distance
end
else
pos.y = temp_y
bubble_style.align_v_temp = nil
end
return pos
end
-- Calculate position of portrait image
function get_portrait_position(pos, bubble_style, bubble_dim, portrait_sprite)
local portrait_pos = { x = pos.x, y = pos.y }
if bubble_style.portrait_align_h == "right" then
portrait_pos.x = pos.x + bubble_dim.x - portrait_sprite.width
end
if bubble_style.portrait_align_v == "center" then
portrait_pos.y = pos.y + (bubble_dim.y - portrait_sprite.height) / 2
elseif bubble_style.portrait_align_v == "bottom" then
portrait_pos.y = pos.y + bubble_dim.y - portrait_sprite.height
end
return portrait_pos
end
-- Calculate position of pointer image
function get_pointer_position(pos, bubble_style, bubble_dim, pointer_offset, char, char_facing, shader_adjusted_viewport, pointer_sprite)
-- Calculate pointer position
local pointer_pos = {x = 0, y = 0}
if bubble_style.align_h == "right" or (bubble_style.align_h == "char_facing" and char_facing == "right") then
-- if bubble is aligned right, pointer is positioned leftmost
pointer_pos.x = pos.x + pointer_offset
-- Adjust position, if bubble is too close to the edge
if bubble_style.fixed_pos == false then
if pointer_pos.x < (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset then
pointer_pos.x = (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset
end
end
elseif bubble_style.align_h == "left" or (bubble_style.align_h == "char_facing" and char_facing == "left") then
-- if bubble is aligned left, pointer is positioned rightmost
pointer_pos.x = pos.x + bubble_dim.x - pointer_sprite.width + pointer_offset
-- Adjust position, if bubble is too close to the edge
if bubble_style.fixed_pos == false then
if pointer_pos.x > (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset then
pointer_pos.x = (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset
end
end
else -- center
-- if bubble is aligned center, pointer is positioned at character
if bubble_style.fixed_pos == false then
pointer_pos.x = (char.Position.x - game.ScrollPosition.x - shader_adjusted_viewport.x) * shader_adjusted_viewport.scale + pointer_offset
else
pointer_pos.x = pos.x + bubble_dim.x / 2
end
end
-- Adjust position, if character is too close to the edge
if bubble_style.fixed_pos == false then
if pointer_pos.x < pos.x - pointer_offset then
pointer_pos.x = pos.x - pointer_offset
elseif pointer_pos.x > pos.x + bubble_dim.x - pointer_sprite.width - pointer_offset then
pointer_pos.x = pos.x + bubble_dim.x - pointer_sprite.width - pointer_offset
end
if pointer_pos.x < pos.x + bubble_style.pointer_min_distance then
pointer_pos.x = pos.x + bubble_style.pointer_min_distance
elseif pointer_pos.x > pos.x + bubble_dim.x - pointer_sprite.width - bubble_style.pointer_min_distance then
pointer_pos.x = pos.x + bubble_dim.x - pointer_sprite.width - bubble_style.pointer_min_distance
end
end
if (bubble_style.align_v_temp ~= nil and bubble_style.align_v_temp == "bottom") or (bubble_style.align_v_temp == nil and bubble_style.align_v == "bottom") then
-- pointer on top when bubble aligned to bottom
pointer_pos.y = pos.y - pointer_sprite.height + bubble_style.pointer_top_offset_y
else
-- pointer on bottom when bubble aligned to top
pointer_pos.y = pos.y + bubble_dim.y - bubble_style.pointer_bottom_offset_y
end
return pointer_pos
end
-- ADD DRAW FUNCTIONS TO THE RENDERING
graphics.addDrawFunc("bubble_below_interface()", 0)
graphics.addDrawFunc("bubble_above_interface()", 1)
--[[
MIT License
Copyright 2024 The Argonauts
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
Resources
Name | Description |
---|---|
argo_bubbles_2.3.0.zip | A working example of the script in action. Visionaire Studio 5.3+ required to run the included .ved file(s). |