How to make GUI dynamic texture?

I am trying to make dynamic textures for a GUI:

	local fill = string.char(0x33, 0x33, 0x33)
	local ok, _ = gui.new_texture("preview_tex", PREVIEW_WIDTH, PREVIEW_HEIGHT, "rgb",
		string.rep(fill, PREVIEW_WIDTH * PREVIEW_HEIGHT),
		false)
	if ok then
		gui.set_texture(self.preview, "preview_tex")
	end

However, I’m not sure whether I need to create a new texture every time I update the texture data. I don’t see how to edit texture data. I also tried the texture under resources, but GUI nodes can’t seem to use them.

I have something which I don’t think is ideal:

	self.preview_buf = buffer.create(PREVIEW_WIDTH * PREVIEW_HEIGHT,
		{ { name = hash("rgb"), type = buffer.VALUE_TYPE_UINT8, count = 3 } })
	local tstream = buffer.get_stream(self.preview_buf, hash("rgb"))
	for x = 0, PREVIEW_WIDTH - 1 do
		for y = 0, PREVIEW_HEIGHT - 1 do
			i = 3 * (y * PREVIEW_WIDTH + x) + 1
			tstream[i + 0] = 0x33
			tstream[i + 1] = 0x33
			tstream[i + 2] = 0x33
		end
	end
	if gui.new_texture("preview_tex", PREVIEW_WIDTH, PREVIEW_HEIGHT, "rgb",
			buffer.get_bytes(self.preview_buf, hash("rgb")),
			false) then
		gui.set_texture(self.preview, "preview_tex")
	end

I still have to create a new texture each update. I would prefer not needing that.

You can use gui.set_texture_data() to update the data of an existing texture:

1 Like

I see that it takes a string as buffer. Doesn’t that still require copying the full texture data?

That’s true. You can also use resource.create_atlas() to create a texture and then use that to replace an existing texture/atlas in a GUI using go.set():

local texture_id = resource.create_texture("/my_texture.texturec", tparams)
local atlas_id = resource.create_atlas("/my_atlas.texturesetc", aparams)
go.set("#mygui", "textureingui", atlas_id)

(This is missing from the docs but I will make sure it is added soon: https://github.com/defold/doc/pull/469/files)

I just tried it but it doesn’t work:

	self.preview_buf = buffer.create(MAP_WIDTH * MAP_HEIGHT,
		{ { name = hash("rgb"), type = buffer.VALUE_TYPE_UINT8, count = 3 } })
	local tstream = buffer.get_stream(self.preview_buf, hash("rgb"))
	for x = 0, MAP_WIDTH - 1 do
		for y = 0, MAP_HEIGHT - 1 do
			i = 3 * (y * MAP_WIDTH + x) + 1
			tstream[i + 0] = 0x33
			tstream[i + 1] = 0x33
			tstream[i + 2] = 0x33
		end
	end
	-- Dot
	tstream[1] = 0xFF
	local tparams = {
		width = MAP_WIDTH,
		height = MAP_HEIGHT,
		type = graphics.TEXTURE_TYPE_2D,
		format = graphics.TEXTURE_FORMAT_RGB,
	}
	self.preview_tex = resource.create_texture("/preview.texturec", tparams, self.preview_buf)
	local aparams = {
		texture = "/preview.texturec",
		animations = {
			id = "preview",
			width = MAP_WIDTH,
			height = MAP_HEIGHT,
			frame_start = 1,
			frame_end = 1,
		},
		geometries = {
			{
				id       = 'preview',
				vertices = {
					0, 0,
					0, MAP_HEIGHT,
					MAP_WIDTH, MAP_HEIGHT,
					MAP_WIDTH, 0
				},
				uvs      = {
					0, 0,
					0, MAP_HEIGHT,
					MAP_WIDTH, MAP_HEIGHT,
					MAP_WIDTH, 0
				},
				indices  = { 0, 1, 2, 0, 2, 3 }
			}
		}
	}
	self.preview_atlas = resource.create_atlas("/preview.texturesetc", aparams)
	gui.set_texture(self.preview, self.preview_atlas)

I got the error:

ERROR:SCRIPT: generator/generator_menu.gui_script:67: bad argument #7 to 'create_atlas' (table expected, got number)

There’s no argument #7, I think?

The animations field should be a list of table:

		animations = {
			{
				id = "preview",
				width = MAP_WIDTH,
				height = MAP_HEIGHT,
				frame_start = 1,
				frame_end = 2,
			}
		},

Also note that the start and end frame shouldn’t be the same.

Thank you, that was hard to spot. Is frame_end non-inclusive?

Also, the example doesn’t work after the fix:

gui.set(self.preview, "textureingui", self.preview_atlas) 
-- ^ property 'textureingui' not found
gui.set("#preview", "textureingui", self.preview_atlas)
-- ^ Bad argument #1 to 'set' (NodeProxy expected, got string)
gui.set_texture(self.preview, self.preview_atlas)
-- ^ Texture '/preview.texturesetc' is not specified in scene
go.set("#preview", "textureingui", self.preview_atlas)
-- ^ You can only access go.* functions and values from a script instance (.script file)

You are supposed to do this from a .script file and then use go.set() to set/change a texture property on a gui component.

Hmmm, Ok, so I can’t put all of it in my gui script.

If I move it to a component script and set an atlas as texture, will it be automatically updated when I update the image data in the backing buffer? Or do I need to set it every time I update?

Or alternatively, should I be using something else all together? I am trying to drawing a sort of minimap preview.

Correct.
The idea is that it’s the same rules as in the editor.

I moved the texture creation into a component script, but I can’t modify the property (if I am understanding the documentation patches right):

-- menu.gui_script
go.property("preview_tex", resource.texture())
-- menu.script
go.set("#gui", "preview_tex", self.preview_atlas)
-- ^ '#gui' does not have any property called 'preview_tex'

I can’t create an empty texture from the editor either, because it asks for a file.

You need to have at least an empty texture/atlas added to the .gui file before you can replace it with something else.

NOTE: The go.property(“foo”, resource.texture("/path/to.atlas")) in the documentation is a way to reference an atlas so that it can be used from scripts to for instance replace the texture on a sprite or in your case a gui.

BUT you want to dynamically create a texture so you don’t need a resource property.


Please follow these steps:

  1. Start by creating a dummy atlas and add it to your gui. This is so that you have something to replace with a dynamically created texture. Like this:

  1. Create your dynamic texture from a script component and replace the dummy texture. That really should be all there is to it.
-- your.script
function init(self)
    local texture_id = resource.create_texture("/my_texture.texturec", tparams)
    local atlas_id = resource.create_atlas("/my_atlas.texturesetc", aparams)
    go.set("#mygui", "dummy", atlas_id)
end

I have this:

...
	self.preview_atlas = resource.create_atlas("/preview.texturesetc", aparams)
	go.set("#gui", "dummy", self.preview_atlas)

And an outline like this (.gui):

And for the component:

However, I still get this error:

'#gui' does not have any property called 'dummy'

Sorry for all of the back and forth with this. Let me double check. I’ll create an example.

It should be

go.set("#gui", “textures”, self.my_atlas, {key = “my_atlas”})

“textures” is the property name.
“key” is a key in the textures hashmap

Here is example how to use it

1 Like

Yep, I figured that out too :slight_smile: I’ve updated the documentation PR and added three new examples.

Thank you, now the texture displays on the box node.

When I update my backing buffer, do I need to call set_texture() as well? Currently it seems like only this updates the displayed texture:

...
	self.preview_atlas = resource.create_atlas("/preview.texturesetc", aparams)
	go.set("#gui", "textures", self.preview_atlas, { key = "preview_tex" })
	timer.delay(1, false, function()
		tstream[100] = 0xFF
		resource.set_texture(self.preview_tex, tparams, self.preview_buf) 
        -- ^ here
	end)

Or, is there some other more convenient method?

Also, why do I have to use an Atlas, instead of plain textures? Is there a reason why GUI doesn’t support it?