How to rotate GUI node based on mouse input?

I have a top-down character on the main menu. It should rotate according to the current mouse position. Example: If the mouse is above the character, the character should look up.

I tried

		-- the position to look at (mouse/finger)
		local target_position = camera.screen_to_world(CAMERA_ID, vmath.vector3(action.x, action.y, 0))
		-- own position
		local my_position = gui.get_position(self.character)
		self.rotation_x = my_position.x - target_position.x
		self.rotation_y = target_position.y - my_position.y

But this doesn’t work as expected. My character is in the upper right corner of the screen and the rotation is based on a point somewhere in the middle of the screen. I also tried to use the gui.screen_to_local function but with this function I get a similar result.

For context: In-game I use the code mentioned above and it works perfectly but for the main menu (GUI) it doesn’t work.

I found a solution:

function init(self)
    -- set default values
    self.rotation_x = 0
    self.rotation_y = 0
end

local function rotate_character(self, x, y)
	-- calculate the angle that this object has to rotate to look at the given point
	local angle = math.atan2(x, y)
	-- set rotation as a quaternion / rotate this object to look at the target position
	gui.set_rotation(self.character, vmath.quat_rotation_z(angle))
end

function update(self, dt)
	rotate_character(self, self.rotation_x, self.rotation_y)
end

local function gui_to_screen(gui_position)
    -- get the game's design resolution (from game.project)
    local design_width = tonumber(sys.get_config("display.width"))
    local design_height = tonumber(sys.get_config("display.height"))

    -- get the actual screen resolution
    local screen_width, screen_height = window.get_size()

    -- calculate the scale factor
    local scale_x = screen_width / design_width
    local scale_y = screen_height / design_height

    -- map GUI coordinates to screen space
    local screen_x = gui_position.x * scale_x
    local screen_y = gui_position.y * scale_y

    -- return the mapped screen coordinates
    return vmath.vector3(screen_x, screen_y, gui_position.z)
  end

function on_input(self, action_id, action)
	-- on mouse movement
	if action_id == nil then
		-- the position to look at (mouse/finger)
	    local target_position = gui_to_screen(vmath.vector3(action.x, action.y, 0))
	     -- own position
	    local character_position = gui.get_screen_position(self.character)
	    self.rotation_x = character_position.x - target_position.x
	    self.rotation_y = target_position.y - character_position.y
      end
end

Is there an easier solution?

1 Like

Here’s an example, it uses game objects .

Convert the go api to gui - * untested

function init(self)
    -- make sure the script will receive user input
    msg.post(".", "acquire_input_focus")
    self.node = gui.get_node("node") -- get a node
end

local function look_at(target_position)
    -- gui node positon
    local my_position = gui.get_position(self.node)

    -- calculate the angle that this node has to rotate to look at the given point
    local angle = math.atan2(my_position.x - target_position.x, target_position.y - my_position.y)
    -- set rotation as a quaternion
    gui.set_rotation(self.node, vmath.quat_rotation_z(angle))
end

function on_input(self, action_id, action)
    -- mouse/finger movement has action_id set to nil
    if not action_id then
        -- the position to look at (mouse/finger)
        local target_position = vmath.vector3(action.x, action.y, 0)
        -- rotate this object to look at the target position
        look_at(target_position)
    end
end
2 Likes

With your help I was able to simplify it to:

local function gui_to_screen(gui_position)
    -- get the game's design resolution (from game.project)
    local design_width = tonumber(sys.get_config("display.width"))
    local design_height = tonumber(sys.get_config("display.height"))

    -- get the actual screen resolution
    local screen_width, screen_height = window.get_size()

    -- calculate the scale factor
    local scale_x = screen_width / design_width
    local scale_y = screen_height / design_height

    -- map GUI coordinates to screen space
    local screen_x = gui_position.x * scale_x
    local screen_y = gui_position.y * scale_y

    -- return the mapped screen coordinates
    return vmath.vector3(screen_x, screen_y, gui_position.z)
end

function on_input(self, action_id, action)
	-- on mouse movement
	if action_id == nil then
		-- the position to look at (mouse/finger)
        local target_position = gui_to_screen(vmath.vector3(action.x, action.y, 0))
		-- own position
        local character_position = gui.get_screen_position(self.character)
		-- calculate the angle that this object has to rotate to look at the given point
		local angle = math.atan2(character_position.x - target_position.x, target_position.y - character_position.y)
		-- set rotation as a quaternion / rotate this object to look at the target position
		gui.set_rotation(self.character, vmath.quat_rotation_z(angle))
    end
end

…but the gui_to_screen function seems to be necessary otherwise it doesn’t work as expected.

2 Likes