Author Topic: Displaying an image on pause  (Read 692 times)

Max

  • Full Member
  • ***
  • Posts: 118
    • View Profile
Re: Displaying an image on pause
« Reply #15 on: March 03, 2018, 01:11:00 am »
Awesome! So much learning. Thanks again.

llamazing

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: Displaying an image on pause
« Reply #16 on: March 03, 2018, 10:11:45 pm »
Since you're still learning, I'll post some examples of other things you can do too.

One trick you can do is change the color of a bitmap font. In your case it works great because your font is black and white.

To change all black pixels to another color while leaving white pixels unchanged is to use the add blend mode. Think of black as having a value of 0 and white having a value of 1. black + color2 = color2 (0 plus anything is the value you added); white + color2 = white (because 1 for white is already the max value).

And likewise to change all white pixels to another color while leaving black pixels unchanged is to use the multiply blend mode. white * color2 = color2 (1 times anything is the value you multiplied by); black * color2 = black (0 times anything is 0).

This code shows an example of changing black to light blue (see top left screenshot) at lines 48-51. Lines 54-57 change white to yellow (see top right). You don't want to do both at the same time because that will cause funny results (since then there'd be more colors than just black and white present).

Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
  3.  
  4. local text_surface = sol.text_surface.create({
  5.     vertical_alignment = "top",
  6.     horizontal_alignment = "center",
  7.     font = "oceansfont",
  8. })
  9. --note that a size is not specified for a text surface, it will be as big as the text it contains
  10.  
  11. --Returns iterator to split text at line breaks
  12. local function line_it(text)
  13.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  14.  
  15.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  16.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  17. end
  18.  
  19.  
  20. function pause_infra:update_game(game)
  21.     current_log:clear() --erase any previous content on surface
  22.  
  23.     local log_a_text = game:get_value("quest_log_a") --the string saved to the game value "quest_log_a" should match a key in strings.dat
  24.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  25.  
  26.     text_surface:set_text(next_line()) --line 1 of quest_log_a
  27.     text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  28.         --x value 69 because horizontal alignment is center (138/2 = 69)
  29.  
  30.     text_surface:set_text(next_line()) --line 2 of quest_log_a
  31.     text_surface:draw(current_log, 69, 16)
  32.  
  33.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  34.  
  35.     local log_b_text = game:get_value("quest_log_b")
  36.     next_line = line_it(sol.language.get_string(log_b_text))
  37.  
  38.     text_surface:set_text(next_line()) --line 1 of quest_log_b
  39.     text_surface:draw(current_log, 69, 39)
  40.  
  41.     text_surface:set_text(next_line()) --line 2 of quest_log_b
  42.     text_surface:draw(current_log, 69, 55)
  43.  
  44.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  45.     --content won't change while pause menu open
  46.    
  47.     --use this trick to change the color of black pixels to something else
  48.     local blend_add_surface = sol.surface.create(current_log:get_size()) --same size as current_log
  49.     blend_add_surface:fill_color{0,128,255} --light blue
  50.     blend_add_surface:set_blend_mode("add") --allows changing black pixels without changing white pixels
  51.     blend_add_surface:draw(current_log)
  52.    
  53.     --use this trick to change the color of white pixels to something else
  54.     --[[local blend_mult_surface = sol.surface.create(current_log:get_size()) --same size as current_log
  55.     blend_mult_surface:fill_color{255,255,0} --yellow
  56.     blend_mult_surface:set_blend_mode("multiply") --allows changing white pixels without changing black pixels
  57.     blend_mult_surface:draw(current_log)]]
  58. end
  59.  
  60.  
  61. local pause_img = sol.surface.create("hud/pause_infra.png")
  62.  
  63. function pause_infra:on_draw(dst_surface)
  64.     --draw menu architecture
  65.     pause_img:draw(dst_surface)
  66.  
  67.     --draw quest log
  68.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  69. end

Another application for this technique is you can use it to change the color of just some words. You would do this by specifying x, y, width and height values when you call the fill_color() function of the blend mode surface. That way only the text within that rectangle would change color. You'd have to figure out the position of the words that you want to have a different color.



If you wanted to use a non-bitmap font, here's how you could do it. The trick here is drawing the text twice. The first time draws a shadow in one color, then the second pass draws the text again in a different color with a slight offset. This makes the text easier to read against a wide variety of background colors when using a semi-transparent background.

The font I used is EncodeSansCompressed-SemiBold, which you can find here (Open SIL license).

(see bottom left screenshot)
Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72)
  3.  
  4. local FONT_COLOR = {255, 255 ,255} --white
  5. local FONT_SHAODW_COLOR = {255,0,0} --red
  6.  
  7. local text_surface = sol.text_surface.create({
  8.     vertical_alignment = "top",
  9.     horizontal_alignment = "center",
  10.     font = "EncodeSansCompressed-SemiBold",
  11.     font_size = 14,
  12.     rendering_mode = "antialiasing",
  13. })
  14. --note that a size is not specified for a text surface, it will be as big as the text it contains
  15.  
  16. --Returns iterator to split text at line breaks
  17. local function line_it(text)
  18.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  19.  
  20.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  21.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  22. end
  23.  
  24.  
  25. --This function consolidates repeated lines of code to reduce redundancy in pause_infra:update_game()
  26. local function draw_text(text, dst_surface, x, y)
  27.     --ensure inputs valid
  28.         assert(type(text)=="string", "Bad argument #1 to 'draw_text' (string expected, got "..type(text)..")")
  29.         assert(sol.main.get_type(dst_surface)=="surface", "Bad argument #2 to 'draw_text' (surface expected, got "..sol.main.get_type(dst_surface)..")")
  30.         x = math.floor(tonumber(x) or 0) --try to force x to integer or use 0 instead
  31.         y = math.floor(tonumber(y) or 0)
  32.        
  33.         text_surface:set_text(text)
  34.        
  35.     text_surface:set_color(FONT_SHAODW_COLOR)
  36.     text_surface:draw(current_log, x+1, y+1) --draw shadow text first, offset 1 pixel to the right and down
  37.    
  38.     text_surface:set_color(FONT_COLOR)
  39.     text_surface:draw(current_log, x, y) --draw non-shadow text on top of shadow text
  40. end
  41.  
  42.  
  43. function pause_infra:update_game(game)
  44.     current_log:clear() --erase any previous content on surface
  45.  
  46.     local log_a_text = game:get_value("quest_log_a") --the string saved to the game value "quest_log_a" should match a key in strings.dat
  47.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  48.  
  49.     draw_text(next_line(), current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  50.          --x value 69 because horizontal alignment is center (138/2 = 69)
  51.     draw_text(next_line(), current_log, 69, 16) --line 2 of quest_log_a
  52.  
  53.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  54.  
  55.     local log_b_text = game:get_value("quest_log_b")
  56.     next_line = line_it(sol.language.get_string(log_b_text))
  57.  
  58.     draw_text(next_line(), current_log, 69, 39) --line 1 of quest_log_b
  59.     draw_text(next_line(), current_log, 69, 55) --line 2 of quest_log_b
  60.  
  61.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  62.     --content won't change while pause menu open
  63. end
  64.  
  65.  
  66. local pause_img = sol.surface.create("hud/pause_infra.png")
  67.  
  68. function pause_infra:on_draw(dst_surface)
  69.     --draw menu architecture
  70.     pause_img:draw(dst_surface)
  71.  
  72.     --draw quest log
  73.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  74. end

Note that the new draw_text function is just to consolidate repeated code and make things cleaner.

Be aware that there is nothing to prevent the text from clipping in this example, and since I am using a font that isn't monospaced, the number of characters that can fit on a line will vary.

If you had actually wanted to use a non-bitmap, non-monospaced font, then it would probably be best to determine where the line breaks should occur dynamically in the code rather than manually entering the \n line breaks. This is getting into more details than I want to get into, but the gist of it is to split the text at the spaces and add one word at a time, checking the current text width using text_surface:get_size() and moving the new words to a new line once the length is exceeded.



Finally, one more thing you can do is create the borders you have around your quest log text box dynamically so that you don't need to use a different image for each size box you use. You would create a new image that contains the four corners of your box (7x7 pixels each) plus the repeating horizontal and vertical segments (1x7 and 7x1 pixels), plus 1 pixel in the center, giving a total size of 15x15 pixels.

To simplify things, steal my ui_draw.lua file from one of my sample quests here and put it in your scripts folder. You'll also need the pause_infra_small.png image I've attached which is derived from your original image. Then the following code will work.

One thing to note is I deleted the transparent pixels in the middle of the pause_infra.png image and am generating the transparent background dynamically in the code. This allows you to change the background color however you see fit. In the code I changed the background of the bottom box to be blue so you can see this in effect (see bottom right screenshot).

The following code generates an image similar to your pause_infra.png from just my pause_infra_small.png. This includes having to draw the "QUEST LOG" text separately as well as the line below it.

Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
  3.  
  4. local text_surface = sol.text_surface.create({
  5.     vertical_alignment = "top",
  6.     horizontal_alignment = "center",
  7.     font = "oceansfont",
  8. })
  9. --note that a size is not specified for a text surface, it will be as big as the text it contains
  10.  
  11. --Returns iterator to split text at line breaks
  12. local function line_it(text)
  13.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  14.  
  15.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  16.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  17. end
  18.  
  19.  
  20. function pause_infra:update_game(game)
  21.     current_log:clear() --erase any previous content on surface
  22.  
  23.     local log_a_text = game:get_value("quest_log_a") --the string saved to the game value "quest_log_a" should match a key in strings.dat
  24.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  25.  
  26.     text_surface:set_text(next_line()) --line 1 of quest_log_a
  27.     text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  28.         --x value 69 because horizontal alignment is center (138/2 = 69)
  29.  
  30.     text_surface:set_text(next_line()) --line 2 of quest_log_a
  31.     text_surface:draw(current_log, 69, 16)
  32.  
  33.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  34.  
  35.     local log_b_text = game:get_value("quest_log_b")
  36.     next_line = line_it(sol.language.get_string(log_b_text))
  37.  
  38.     text_surface:set_text(next_line()) --line 1 of quest_log_b
  39.     text_surface:draw(current_log, 69, 39)
  40.  
  41.     text_surface:set_text(next_line()) --line 2 of quest_log_b
  42.     text_surface:draw(current_log, 69, 55)
  43.  
  44.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  45.     --content won't change while pause menu open
  46. end
  47.  
  48. --create image for quest log
  49. local ui_draw = require("scripts/ui_draw.lua")
  50. local pause_borders = sol.surface.create("hud/pause_infra_small.png") --15x15 image
  51. local pause_img = sol.surface.create(152,103) --size to make the quest log box
  52. pause_img:fill_color( --first draw semi-transparent background
  53.     {0,0,0,204}, --black with 80% opacity
  54.     1,1,152-2,103-2 --fill surface with color except for 1 pixel border around edge
  55. )
  56. ui_draw.draw_frame(
  57.         { --table describing the source image to use
  58.                 surface = pause_borders,
  59.                 border = 7, --x & y offset to use for determining repeating section of border image
  60.         },
  61.         pause_img --border will be drawn on top of existing pause_img surface
  62. )
  63. text_surface:set_text("QUEST_LOG") --this string should come from a new entry in string.dat to facilitate localization to other languages
  64. --text_surface:set_text(sol.language.get_string("interface.title.quest_log")) --do something like this instead of line above once you create the new string.dat entry
  65. text_surface:draw(pause_img, 69, 3) --draw QUEST_LOG title text, you may have to temporarily change the horizontal alignment to center first if using left for objectives text
  66. pause_img:fill_color({0,0,0},8,19,136,3) --draw line under title, starting with black outline
  67. pause_img:fill_color({255,255,255},8+1,19+1,136-2,3-2) --draw white line under title
  68.  
  69. --create image below quest log
  70. local pause_img2 = sol.surface.create(152,46) --size to make bottom box
  71. pause_img2:fill_color(
  72.         {0,64,128,204}, --let's make this one blue because why not?
  73.         1,1,152-2,46-2
  74. )
  75. ui_draw.draw_frame({surface=pause_borders, border=7}, pause_img2)
  76.  
  77. function pause_infra:on_draw(dst_surface)
  78.     --draw menu architecture
  79.     pause_img:draw(dst_surface, 160, 1)
  80.  
  81.     --draw quest log
  82.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  83.    
  84.     --draw bottom box
  85.     pause_img2:draw(dst_surface, 160, 105)
  86. end

Max

  • Full Member
  • ***
  • Posts: 118
    • View Profile
Re: Displaying an image on pause
« Reply #17 on: March 04, 2018, 09:19:51 pm »
Thanks, llamazing! Those are neat tricks, basically since multiply is darkening pixels and you can't darken black, you can use it to affect white, and vice-versa? Awesome. I might probably take that tip and try to work it into the ALTTP dialog box script, so I can put something like $b in my dialogs to turn the text blue to indicate the hero is talking, then $w to turn it back to white for NPCs.

Not that I should get started on that until I finish up my current to-do list of things that need wrapped up before a like, demo 1 kind of thing, but it's nice to know some of what the to-do list holds for the future!

Diarandor

  • Hero Member
  • *****
  • Posts: 966
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: Displaying an image on pause
« Reply #18 on: March 04, 2018, 09:26:13 pm »
There is also an old script I made (a modified version of Chris' dialog box script) that allows to put particular words in a different color (it only works with monospaced fonts), and also allows to put sprite images (with animations) in the dialog box (useful to talk with NPCs).
“If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you.”

Max

  • Full Member
  • ***
  • Posts: 118
    • View Profile
Re: Displaying an image on pause
« Reply #19 on: March 04, 2018, 09:27:46 pm »
Oh, that's super cool! Is that something you have on the site somewhere for others to use, or would you rather hold onto it yourself. I totally understand if it's the second, and I'll eventually figure it out myself.

Diarandor

  • Hero Member
  • *****
  • Posts: 966
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: Displaying an image on pause
« Reply #20 on: March 04, 2018, 10:37:30 pm »
Nope, it is meant to be used by anyone. There is an old thread about it:
http://forum.solarus-games.org/index.php/topic,312.msg1179.html#msg1179
“If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you.”