Entity Picking

This example demonstrates how to pick a game object from the 3D scene.

Project files

This example describes method of selecting a game object from the 3D scene on the click of the mouse using collision-based picking:

The models used in this example are from Kenney’s Prototype Kit, licensed under CC0.

Scripts

entity_picking.script

go.property("camera_url", msg.url("/camera#camera"))

--- Performs a raycast from the camera through a screen position to find an entity.
-- @param camera_url url The camera URL to use for screen-to-world conversion
-- @param screen_x number The x-coordinate on the screen
-- @param screen_y number The y-coordinate on the screen
-- @param collision_groups table The collision groups to check against as array of hash values
-- @return table|nil The first entity hit by the ray, or nil if nothing was hit
local function pick_entity(camera_url, screen_x, screen_y, collision_groups)
	local from = camera.screen_to_world(vmath.vector3(screen_x, screen_y, 0), camera_url)
	local to = camera.screen_to_world(vmath.vector3(screen_x, screen_y, 100), camera_url)
	local results = physics.raycast(from, to, collision_groups, { all = false })
	if not results then
		return nil
	end

	return results[1]
end

function init(self)
	-- Use the projection provided by the camera
	msg.post("@render:", "use_camera_projection")

	-- Acquire input focus to receive input events
	msg.post(".", "acquire_input_focus")

	self.input_pressed = false -- Tracks if the input is currently pressed
	self.last_input = nil      -- Stores the last input action received
	self.previous = nil        -- Keeps track of the previously highlighted entity
end

function update(self, dt)
	if not self.last_input then
		-- No input received yet
		return
	end

	local result = pick_entity(self.camera_url, self.last_input.screen_x, self.last_input.screen_y, { hash("target") })
	if result then
		-- Store in the result table the model URL of the entity just for convenience
		result.model_url = msg.url(nil, result.id, "model")

		-- Set the tint of the entity to highlight it
		go.set(result.model_url, "tint.w", 1.5)

		-- If the input is currently pressed, move the camera to the entity
		if self.input_pressed then
			-- We want to move the camera to only the X,Y of the entity, so we get its position
			local move_to = go.get("/camera", "position")
			move_to.x = result.position.x
			move_to.y = result.position.y

			go.cancel_animations("/camera", "position")
			go.animate("/camera", "position", go.PLAYBACK_ONCE_FORWARD, move_to, go.EASING_INOUTQUAD, 0.5)
		end

		-- If the previously highlighted entity is different from the current entity, reset its tint
		if self.previous and self.previous.id ~= result.id then
			go.set(self.previous.model_url, "tint.w", 1)
		end
		self.previous = result
	else
		-- No entity was hit, so reset the tint of the previously highlighted entity
		if self.previous then
			go.set(self.previous.model_url, "tint.w", 1)
			self.previous = nil
		end
	end
end

function on_input(self, action_id, action)
	if action_id == hash("touch") then
		-- "touch" is a screen touch or mouse click. We only want to react to the press event.
		self.input_pressed = action.pressed
	elseif not action_id then
		-- If action_id is nil, it means that the action is a mouse move event.
		-- "action" contains the mouse move event data. We want to store it for later use.
		self.last_input = action
	end
end