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).
pause_infra = {}
local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
local text_surface = sol.text_surface.create({
vertical_alignment = "top",
horizontal_alignment = "center",
font = "oceansfont",
})
--note that a size is not specified for a text surface, it will be as big as the text it contains
--Returns iterator to split text at line breaks
local function line_it(text)
assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
end
function pause_infra:update_game(game)
current_log:clear() --erase any previous content on surface
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
local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
text_surface:set_text(next_line()) --line 1 of quest_log_a
text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
--x value 69 because horizontal alignment is center (138/2 = 69)
text_surface:set_text(next_line()) --line 2 of quest_log_a
text_surface:draw(current_log, 69, 16)
--NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
local log_b_text = game:get_value("quest_log_b")
next_line = line_it(sol.language.get_string(log_b_text))
text_surface:set_text(next_line()) --line 1 of quest_log_b
text_surface:draw(current_log, 69, 39)
text_surface:set_text(next_line()) --line 2 of quest_log_b
text_surface:draw(current_log, 69, 55)
--Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
--content won't change while pause menu open
--use this trick to change the color of black pixels to something else
local blend_add_surface = sol.surface.create(current_log:get_size()) --same size as current_log
blend_add_surface:fill_color{0,128,255} --light blue
blend_add_surface:set_blend_mode("add") --allows changing black pixels without changing white pixels
blend_add_surface:draw(current_log)
--use this trick to change the color of white pixels to something else
--[[local blend_mult_surface = sol.surface.create(current_log:get_size()) --same size as current_log
blend_mult_surface:fill_color{255,255,0} --yellow
blend_mult_surface:set_blend_mode("multiply") --allows changing white pixels without changing black pixels
blend_mult_surface:draw(current_log)]]
end
local pause_img = sol.surface.create("hud/pause_infra.png")
function pause_infra:on_draw(dst_surface)
--draw menu architecture
pause_img:draw(dst_surface)
--draw quest log
current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
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)
pause_infra = {}
local current_log = sol.surface.create(138,72)
local FONT_COLOR = {255, 255 ,255} --white
local FONT_SHAODW_COLOR = {255,0,0} --red
local text_surface = sol.text_surface.create({
vertical_alignment = "top",
horizontal_alignment = "center",
font = "EncodeSansCompressed-SemiBold",
font_size = 14,
rendering_mode = "antialiasing",
})
--note that a size is not specified for a text surface, it will be as big as the text it contains
--Returns iterator to split text at line breaks
local function line_it(text)
assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
end
--This function consolidates repeated lines of code to reduce redundancy in pause_infra:update_game()
local function draw_text(text, dst_surface, x, y)
--ensure inputs valid
assert(type(text)=="string", "Bad argument #1 to 'draw_text' (string expected, got "..type(text)..")")
assert(sol.main.get_type(dst_surface)=="surface", "Bad argument #2 to 'draw_text' (surface expected, got "..sol.main.get_type(dst_surface)..")")
x = math.floor(tonumber(x) or 0) --try to force x to integer or use 0 instead
y = math.floor(tonumber(y) or 0)
text_surface:set_text(text)
text_surface:set_color(FONT_SHAODW_COLOR)
text_surface:draw(current_log, x+1, y+1) --draw shadow text first, offset 1 pixel to the right and down
text_surface:set_color(FONT_COLOR)
text_surface:draw(current_log, x, y) --draw non-shadow text on top of shadow text
end
function pause_infra:update_game(game)
current_log:clear() --erase any previous content on surface
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
local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
draw_text(next_line(), current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
--x value 69 because horizontal alignment is center (138/2 = 69)
draw_text(next_line(), current_log, 69, 16) --line 2 of quest_log_a
--NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
local log_b_text = game:get_value("quest_log_b")
next_line = line_it(sol.language.get_string(log_b_text))
draw_text(next_line(), current_log, 69, 39) --line 1 of quest_log_b
draw_text(next_line(), current_log, 69, 55) --line 2 of quest_log_b
--Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
--content won't change while pause menu open
end
local pause_img = sol.surface.create("hud/pause_infra.png")
function pause_infra:on_draw(dst_surface)
--draw menu architecture
pause_img:draw(dst_surface)
--draw quest log
current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
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.
pause_infra = {}
local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
local text_surface = sol.text_surface.create({
vertical_alignment = "top",
horizontal_alignment = "center",
font = "oceansfont",
})
--note that a size is not specified for a text surface, it will be as big as the text it contains
--Returns iterator to split text at line breaks
local function line_it(text)
assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
end
function pause_infra:update_game(game)
current_log:clear() --erase any previous content on surface
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
local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
text_surface:set_text(next_line()) --line 1 of quest_log_a
text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
--x value 69 because horizontal alignment is center (138/2 = 69)
text_surface:set_text(next_line()) --line 2 of quest_log_a
text_surface:draw(current_log, 69, 16)
--NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
local log_b_text = game:get_value("quest_log_b")
next_line = line_it(sol.language.get_string(log_b_text))
text_surface:set_text(next_line()) --line 1 of quest_log_b
text_surface:draw(current_log, 69, 39)
text_surface:set_text(next_line()) --line 2 of quest_log_b
text_surface:draw(current_log, 69, 55)
--Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
--content won't change while pause menu open
end
--create image for quest log
local ui_draw = require("scripts/ui_draw.lua")
local pause_borders = sol.surface.create("hud/pause_infra_small.png") --15x15 image
local pause_img = sol.surface.create(152,103) --size to make the quest log box
pause_img:fill_color( --first draw semi-transparent background
{0,0,0,204}, --black with 80% opacity
1,1,152-2,103-2 --fill surface with color except for 1 pixel border around edge
)
ui_draw.draw_frame(
{ --table describing the source image to use
surface = pause_borders,
border = 7, --x & y offset to use for determining repeating section of border image
},
pause_img --border will be drawn on top of existing pause_img surface
)
text_surface:set_text("QUEST_LOG") --this string should come from a new entry in string.dat to facilitate localization to other languages
--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
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
pause_img:fill_color({0,0,0},8,19,136,3) --draw line under title, starting with black outline
pause_img:fill_color({255,255,255},8+1,19+1,136-2,3-2) --draw white line under title
--create image below quest log
local pause_img2 = sol.surface.create(152,46) --size to make bottom box
pause_img2:fill_color(
{0,64,128,204}, --let's make this one blue because why not?
1,1,152-2,46-2
)
ui_draw.draw_frame({surface=pause_borders, border=7}, pause_img2)
function pause_infra:on_draw(dst_surface)
--draw menu architecture
pause_img:draw(dst_surface, 160, 1)
--draw quest log
current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
--draw bottom box
pause_img2:draw(dst_surface, 160, 105)
end