How to load and set a font at runtime?

I’m currently working on a localisation system and I’d like to have all the necessary files available outside of the actual program and create a possibility to add new translations even after the game has been build.
For that reason I would like the ability to load a font at runtime and set labels and gui text nodes to use this new font.

Is it in any way possible to achieve this?

Defold really likes to know about everything up front to be able to optimize resource use. This obviously doesn’t work well with a strategy of loading and using additional files at runtime. It is not impossible though.

Images can be loaded using either image.load() or through one of the extensions that are available in the asset portal.

Sounds can be loaded using the FMOD extension or through the new sound feature we released where you can load sound data into a buffer and set that on a sound component.

Fonts on the other hand doesn’t support this… A couple of solutions I can think of:

  • Use the resource.font() functionality to define additional fonts and then set those on your labels based on the user language.
  • Add multiple fonts to your gui scenes and use gui.set_font() based on user language.

In both of these cases you need to know beforehand which fonts you are going to need. You can probably figure out quite early on in which markets you wish to launch your games and then make sure to find the required fonts matching the languages in those markets.

You can also use live update to update an installed game with additional content, including fonts.

I already did some digging and feared this would be the answer.
The need to already set up the fonts beforehand is my plan B, which I’m not entierely happy with.

I would have wanted a fully flexible system which would allow to add translations without the need of the creators input or rebuilding the whole game.
This, for example, would allow something like fans being able to do their own translations, even if the game isn’t marketed towards their region.

I haven’t looked into live updating yet, but would it be possible to have a flexible system on which you just load a “language pack” the game didn’t know about before but just does have a loose construct of it in it’s framework?

Is it possible to set resource property for GUI?

It’s possible to add font loading to my text rendering extension GitHub - Lerg/extension-nativetext: Native Text Rendering extension for Defold. and render text to bitmaps with it. It’s much more work but would allow you to do what you wanted.
Currently it uses the default system font, which supports all languages.

1 Like

Is it possible to replace existed font with new?

I have game_font.
Then I load new font from live update. This is same as game_font, but with Japan, Korea and China.

I want to replace game_font with live update font.

I find this. So I can add new fonts in gui.

What about replacing existed?

Can’t you use go.set("#gui", "fonts", self.my_font, {key = "my_font"}) ? Or maybe I don’t understand the usecase.

1 Like

I can, but it will be much easier to replace existed font resource.

For now it worked like that

--replace font
--game font is same name i used for my current font
go.set(msg.url(self.target_gui), "fonts", COMMON.LOCALIZATION.font_all, { key = "game_font" })
--nodes still use old font. So i need send message to gui
msg.post(self.target_gui, "font_changed")
--replace font for all nodes that used localization strings
--it is easy to forgot some nodes(
--also it will be better replace this old font with new for every text node to reduce draw calls, but i replace only part of nodes.
function Script:on_message(message_id, message, sender)
	if message_id == COMMON.HASHES.hash("font_changed") then
		self.game_font_all = hash("game_font")

		GUI.replace_font(self.game_font_all, {
			self.views.title_lbl.node,
			self.views.music_lbl.node,
			self.views.sound_lbl.node,
			self.views.shadows_lbl.node,
		})
                --changing font is not enought. I need to change text. If i don't change text it not worked(use old font)
		self:on_language_changed()
	end
end

Change nodes text. Font will use new font only after changing text.

function Script:on_language_changed()
	local locale = COMMON.LOCALIZATION:locale_get()
	gui.set_enabled(self.views.flags.en.vh.selected, true) --fallback to en
	for k, flag in pairs(self.views.flags) do
		gui.set_enabled(flag.vh.selected, locale == flag.language)
	end

	self.views.title_lbl:set_text(COMMON.LOCALIZATION:translate("settings_title"))
	self.views.music_lbl:set_text(COMMON.LOCALIZATION:translate("settings_music"))
	self.views.sound_lbl:set_text(COMMON.LOCALIZATION:translate("settings_sound"))
	self.views.shadows_lbl:set_text(COMMON.LOCALIZATION:translate("settings_shadow"))
end

I need add this not funny code for every gui that i used.

Also i need to be carefull if i forgot some node in one of steps, node will be use old font.

It worked, no problems. But looks like this can be much easier))

Sorry i was wrong)
This is enought)

--replace font
--game font is same name i used for my current font
go.set(msg.url(self.target_gui), "fonts", COMMON.LOCALIZATION.font_all, { key = "game_font" })

In some of my templates this font have different name))

1 Like

Use case was to replace font resource.

So i can make this in one line.

resource.replace_font(game_font_res_hash, new_game_font_res_hash)

In current solution i need to add a script in every scene.

local COMMON = require "libs.common"


go.property("target_gui", msg.url())

function update(self, dt)
	if COMMON.LOCALIZATION.font_all then
		go.set(msg.url(self.target_gui), "fonts", COMMON.LOCALIZATION.font_all, { key = "game_font" })
		msg.post(msg.url(), "disable")
	end
end

I haven’t had time to fully study your problem, but if there’s something we could do to simplify things then please create a feature request on GitHub!

1 Like

It’s possible to build your localization system in a way that each component which is interested in localization register itself in init() and unregister in final(). And then just send message to all the registered comps or directly setup font from your localization system (depends on your project architecture)

1 Like

Problem with registration is that gui scenes will be in different proxy collections.
So i can’t use go.set from root collection.

My current solution is good enought. Every proxy has script that will change font for gui.
I write a lot of in this thread. But i was wrong.

It is easy to change font for one gui.

go.set(msg.url(self.target_gui), "fonts", COMMON.LOCALIZATION.font_all, { key = "game_font" })
local COMMON = require "libs.common"


go.property("target_gui", msg.url())

function update(self, dt)
	if COMMON.LOCALIZATION.font_all then
		go.set(msg.url(self.target_gui), "fonts", COMMON.LOCALIZATION.font_all, { key = "game_font" })
		msg.post(msg.url(), "disable")
	end
end

I am just start experiment with changing font yesterday in evening)
After some time i understand how it worked)

1 Like