Problem with Button(GUI) Animation and lua-tables (SOLVED)

Hey there.

I am at a gui animation for my buttons.
Cant find the problem why it works with some GUI and dont work with other GUIs.

The buttons should pop-up (increase size) with the ping-pong-once effect. Most of the buttons work fine, but others seems to “stock” -meaning, the button dont get bigger and smaller again, it gets just a bit bigger and stops! On a mouse-move it gets a little big bitter and stops again until the max. size is reached.

To prevent calling the animate_button() every pixel-movement with the mouse I am trying to handle this with a lua-table.
That table stores a key-value which simply is the node-Id and TRUE if its already animating or NIL if not (or never).

Strange is, the lua table stores tons of same keys. I’ve tried that script in a lua-editor. Normally it shouldnt do that. One key per table like table[1] = 1. If table[1] = 2 then its 2 - not even have 2 entries. But if I printout the table there are tons of same entries.

Here are some code to look at:
the functions for animating:

function on_animation(self, node)
	if self.animatedbuttons[node] then
		print("MAINMENU: animation stopped: " .. tostring(node))
		self.animatedbuttons[node] = nil
		pprint(self.animatedbuttons)
	end
end

function animate_button(self, node)
	if self.animatedbuttons[node] == nil then
		gui.animate(node, gui.PROP_SCALE, vmath.vector3(1.1, 1.1, 1.1), gui.EASING_LINEAR, 0.3, 0.1, on_animation, gui.PLAYBACK_ONCE_PINGPONG)
		print("MAINMENU: animation started: " .. tostring(node))
		self.animatedbuttons[node] = true
	end
end

And in different GUI.scripts calls like animate_button(self, nodeID).
Here is the simple and easy mainmenu-gui-script where the animations stucks:

function init(self)
    msg.post(".","acquire_input_focus")
	gui.set_text(gui.get_node("game-logo/logo"), sys.get_config("project.title"))
	gui.set_text(gui.get_node("game-logo/versiontext"), sys.get_config("project.version"))
	gui.set_text(gui.get_node("defoldtext"), sys.get_engine_info().version)
	
	self.animatedbuttons = {}
end

function on_input(self, action_id, action)
	if GLOBAL.GAMESTATE <= GLOBAL.GAMESTATE_PAUSE then
		local start = gui.get_node("button_start")
		local options = gui.get_node("button_options")
		local quit = gui.get_node("button_quit")
		
		local clicked = action_id == GLOBAL.LEFTCLICKED and action.released
		
		if gui.pick_node(start, action.x, action.y) then -- CLICK ON START
			animate_button(self, start)
			if clicked then
				GLOBAL.GAMESTATE = GLOBAL.GAMESTATE_PLAY
				msg.post(".", "disable")
				msg.post("main:/gamecam", "enable")
				print("GAME STARTED")
			end
			
		elseif gui.pick_node(options, action.x, action.y) then -- CLICK ON OPTIONS
			animate_button(self,options)
			
		elseif gui.pick_node(quit, action.x, action.y) then -- CLICK ON QUIT
			animate_button(self,quit)
			if clicked then
				print("GAME QUIT)")
				msg.post("@system:", "exit", {code = 0})
			end
		end

	end
end

Otherwise here is a much more complicated gui-script where the button-animation DONT WORK for the first simple calls (move, engage up to suicide) BUT … at the end at the FOR-LOOP, where the dynamiclly genereated buttons are, IT WORKS PERFEKT.

function on_input(self, action_id, action)
	local clicked = action_id == GLOBAL.LEFTCLICKED and action.released
	
	print("UNITGUI: CLICK")

	-- UNITMENU like MOVE, ENGAGE, SUICIDE OR BUILD
	local move = gui.get_node("unitmenu/movebutton")
	local fire = gui.get_node("unitmenu/firebutton")
	local build = gui.get_node("unitmenu/buildbutton")	
	local suicide = gui.get_node("unitmenu/suicidebutton")

	-- CLICK on MOVE
	if gui.pick_node(move, action.x, action.y) then
		animate_button(self,move)
		if clicked then
			unitMenu(false)
			buildMenu_Structures(false)
			msg.post("main:/crosshair#script", "moveunit")
			return true
		end
	-- CLICK on ENGAGE/FIRE
	elseif gui.pick_node(fire, action.x, action.y) then
		animate_button(self,fire)
		if clicked then
			unitMenu(false)
			buildMenu_Structures(false)
			msg.post("main:/crosshair#script", "engageunit")
			return true
		end		
	-- CLICK on BUILD	
	elseif gui.pick_node(build, action.x, action.y) then
		animate_button(self,build)
		if clicked then
			msg.post("main:/crosshair#script", "buildMenu_Structures")
			buildMenu_Structures(true, gui.get_position(build))
			return true
		end
	-- CLICK on SUICIDE
	elseif gui.pick_node(suicide, action.x, action.y) then			
		animate_button(self,suicide)
		if clicked then
			unitMenu(false)
			buildMenu_Structures(false)
			msg.post("main:/crosshair#script", "suicideunit")
			return true			
		end
	else
		
		-- CLICK on the BUILD-MENU		
		for v, i in ipairs(buildStructureMenuItems) do
			if gui.pick_node(buildStructureMenuItems[v][hash("buildmenu/button_buildstructure_base")], action.x, action.y) then
				animate_button(self, buildStructureMenuItems[v][hash("buildmenu/button_buildstructure_base")])
				if clicked then
					unitMenu(false)
					print("CLICK ON " .. buildStructureMenuItems[v]["sprite"])
					-- TODO: disable new dyn menu
					msg.post("main:/crosshair#script", "buildStructure", { structure = buildStructureMenuItems[v]["sprite"] , factory_url = buildStructureMenuItems[v]["spawner"] })
					return true
				end
			end
		end
	end
end

Any ideas why?
And … any ideas why lua stores tons of same keys?

Here is a sample-debug print:

DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation stopped: box@(640, 350, 0)
DEBUG:SCRIPT: 
{
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
}

//EDIT
I’ve made a quick video to show. At the beginning I’ll move the mouse over the 3 main-menu buttons. If I move faster over all 3 buttons they dont animate anymore. They “stuck”. Later you’ll see a longer list of buttons working fine. The other 3 buttons at the units have the same problem like the main-menu.

As an extra precaution maybe you could:

  1. Reset the scale in animate_button before starting the animation, gui.set_scale(node, vmath.vector3(1.0))
  2. Cancel any running animation before starting a new animation, gui.cancel_animation(node, gui.PROP_SCALE)

What do you mean? Same keys in buildStructureMenuItems? What type are the keys?

Hey @britzl.

Thanks for answering.
I tried to avoid resetting size and stopping the animation because if you “run” over the buttons the size-reset looks a little bit weired.
But it works (of cause). I tried to make it a little more smooth looking (thats why I try to use the table for).

To the table:
Like in the code above every time a animation for a special button/node starts the table will filled with
table[nodeID] = true

The nodeID is just the return of gui.get_node(“name”) - like you can see in the debug above.
I thought it could be working because in a table there are unique keys.
So the key is f.e. box@(640, 250, 0). Every box has its own coordinates so every key should be unique :wink:
If a key is set to true (could be any value) then the animate_button() wont start it again (see code above).

But, strangely, the table is full of duplicates. Dont know why.

for example… if I move the mouse over the first button of the main menu (start) this will printed at debug:
(should be only ONE entry), between {} is the content of the table (animatedbuttons())

DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: MAINMENU: animation started: box@(640, 350, 0)
DEBUG:SCRIPT: 
{
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
  box@(640, 350, 0) = true,
}

Both animate_button() and on_animate() are global functions. Do you have the same functions defined in multiple gui_script files? This is probably not what’s causing your problems this time, but still, be wary of global functions and their potential side effects!

What if you also print gui.get_id(node) in both animate_button() and on_animate(), are they the same?

Yes, these functions are global and just declared ONE time (in a global.lua).
Also the table (animatedbuttons{}) are defined there.

I also tried it with different tables like self.animatedbuttons{} for each gui-script. same effect.
Handling this list/table globally shouldn’t make any trouble. the nodes are unique.

I’ve printed. Result:

DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ON ANIM: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ANIM BUTT: [button_start]
DEBUG:SCRIPT: ON ANIM: [button_start]

Hmm, weird. I can take a look at your project tomorrow if you like (invite bjorn.ritzl@king.com)?

Invited. Have to update first.

The files you may look at are
global_settings.lua (holds couple of global vars incl the animate_button())
mainmenu.gui_script

other gui_script like unit and structure simply also calls that animate_button() function.

Ah, I see. You can compare two nodes using the equality operator == like this:

local n1 = gui.get_node("foo")
local n2 = gui.get_node("foo") -- note the same node

print(n1 == n2) -- true

But when you use the nodes as keys in a table they will not be the same as they are two user data instances:

local n1 = gui.get_node("foo")
local n2 = gui.get_node("foo") -- note the same node

local t = {}
t[n1] = true
t[n2] = true

pprint(t) -- two entries!!!

What you need to do is to follow the hunch I had with gui ids since those are hashes and thus comparable and possible to use as keys in a table in the way you want to. The only thing you need to look out for is that cloned gui nodes will have no ids. You need to assign those manually, maybe like this based on your code:

for v, i in ipairs(GLOBAL.unitType) do
	local nodeID = gui.clone_tree(gui.get_node("button_buildunit"))
	for id,node in pairs(nodeID) do
		gui.set_id(node, id .. v)
	end
	...

Hey @britzl.

Thanks for looking at.
Hm okay. I know what you mean. But strange anyway that a table can hold same keys with different values.

So the problem is simply the same IDs for the box-nodes?

//EDIT:
But…why this dont work with the mainmenu? These buttons are NOT cloned!

Okay, to have this clear and for all those who may have the same problem and read this later:

Using a table to organize the animations is possible, but just works with the gui-IDs (gui.get_id()).

Hint: If you want to use dynamiclly generated buttons like me (means: having a template button “handcrafted” and clone it later using script and maybe change text etc) you HAVE TO SET an unique ID for each cloned node!
Otherwise the lua-table wont work well.

I’ve used the code of britzl which does the work well to define the IDs of the cloned nodes…

		-- clone the template-button...
		local nodeID = gui.clone_tree(gui.get_node("button_buildunit"))
		-- give the (cloned) button and all his childs enumerated IDs
		for id,node in pairs(nodeID) do
			gui.set_id(node, id .. v)
		end	

Now to the animation stuff. I simply use a kind of blob effect with pingpong. If you hover over a button the button will get little bigger and return to its original size. For this little effect I use the following code:

function on_animation(self, node)
	-- Is button-animation stored in table?
	if self.animatedbuttons[gui.get_id(node)] then
		-- clear the node-id from table
		self.animatedbuttons[gui.get_id(node)] = nil
	end
end

function animate_button(self, node)
	-- is there already an animation running?
	if self.animatedbuttons[gui.get_id(node)] == nil then
		-- animate that button!
		gui.animate(node, gui.PROP_SCALE, vmath.vector3(1.1, 1.1, 1.1), gui.EASING_LINEAR, 0.3, 0.1, on_animation, gui.PLAYBACK_ONCE_PINGPONG)
		-- store the node-ID in table with value TRUE (means, button is animating!)
		self.animatedbuttons[gui.get_id(node)] = true
	end
end

To call the animation simply add a line like this to your gui-script input() function (sample code):

		if gui.pick_node(start, action.x, action.y) then -- CLICK ON START
			animate_button(self, start) --animate button!
			if clicked then
				GLOBAL.GAMESTATE = GLOBAL.GAMESTATE_PLAY
				print("GAME STARTED")
			end
		end

It’s not the same keys. For gui nodes we’re able to check for equality using the equality operator since we have implemented the __eq metamethod. And the thing is that even though we can do gui.get_node("foo") == gui.get_node("foo") and get true back we need to remember that gui nodes are of the type userdata:

print(type(gui.get_node("foo"))) -- "userdata"

And when you put two instances of userdata (for instance two node or two vector3) as a key in a Lua table they will be hashed and not treated as the same. There is nothing we can do about this and you as a Lua developer unfortunately has to find ways around it. Like using the node id as a key instead of the node itself.

1 Like