Dynamic Head Movement

This script in Roblox detects and highlights the nearest interactable object in front of a player’s character. It uses Inverse Kinematics (IK) to make the character’s head follow these objects if they’re within a specified radius. The system updates as players join or leave the game, ensuring smooth interaction and cleanup.

Create a Tag named “interactable” and tag it Part or Object you can put the tagged object in different Folder or Same Folder

Create a Folder Named “Ignore” on WorkSpace to block the LookAt
2

Showcase:

If The Clip Wont Start or Work Copy and paste fr

https://www.youtube.com/shorts/QTU4Ds1VEqU

Create Local Script in StarterPlayerScripts

Setting Up Services and Variables

local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")

local Radius = 10 
local Parts = {}

  • RunService: In this script it is used to run functions continuously, such as updating animations or interactions.
  • CollectionService: Think of this as a tagging system. It helps us find and group objects in the game that are tagged with specific tag, like “interactable.”
  • Radius: This defines how far away something can be for a player to interact with it. I have set it to 10 units.
  • Parts: A table that we’ll use to keep track of special parts associated with each player, which helps us manage where they’re looking.

Checking If an Object is In Front of the Character

local function IsInFrontOfCharacter(character, position)

Declaring IsInFrontOfCharacter function to determine if a given position (like an interactable object) is in front of the character and not behind them or to the side.

	local rootPart = character.HumanoidRootPart
	local rootPosition = rootPart.Position
	local direction = (position - rootPosition).unit

	local lookVector = rootPart.CFrame:vectorToWorldSpace(Vector3.new(0, 0, -1))
	local toPosition = (position - rootPosition).unit
	local isInFront = lookVector:Dot(toPosition) > 0

Here, we figure out the direction from the character to the object and compare it with the direction the character is facing. We use a dot product to check if the object is in front of the character (this is a bit of math magic that helps us determine direction).

	if not isInFront then
		return false
	end

If the object isn’t in front, we can immediately say “nope, not in front” and return false.

local ray = Ray.new(rootPosition, direction * (position - rootPosition).Magnitude)
	local hitPart, _ = workspace:FindPartOnRay(ray)

	if hitPart and hitPart:IsDescendantOf(workspace.Ignore) then 
		-- if part is blocked by a wall or obstacle
		return false
	end

	return true

We then send out an invisible line (a “ray”) from the character to the object to see if anything is blocking the view. If the object is behind a wall or some obstacle (not in our “ignore” list), we return false. If everything is clear, we return true, meaning the object is in front and visible.

Finding the Nearest Interactable Object

local function GetNearestInteractable(player)
  • Declaring a function that looks for the closest object that the player can interact with.
	local character = player.Character
	if not character then
		return nil
	end
  • First, we check if the player has a character (sometimes characters might not be loaded or present).
	local headPosition = character.Head.Position
	local nearestInteractable = nil
	local nearestDistance = Radius
  • We get the position of the character’s head and set up a starting point for our search. nearestInteractable will store the closest object we find, and nearestDistance helps us keep track of how far away it is.
	local interactables = {}
	for _, interactable in pairs(CollectionService:GetTagged("interactable")) do
		table.insert(interactables, interactable)
	end
  • We gather all objects tagged as “interactable” into a list.
	table.sort(interactables, function(a, b)
		local distanceA = (headPosition - a.Position).Magnitude
		local distanceB = (headPosition - b.Position).Magnitude
		return distanceA < distanceB
	end)
  • We sort these objects by how close they are to the player’s head, with the nearest ones first.
	for _, interactable in ipairs(interactables) do
		local distance = (headPosition - interactable.Position).Magnitude
		if distance <= Radius and IsInFrontOfCharacter(character, interactable.Position) then
			nearestInteractable = interactable
			nearestDistance = distance
			break
		end
	end
  • We go through the sorted list and check each object to see if it’s within our interaction radius and in front of the character. We stop at the first one that meets these criteria.
	return nearestInteractable
  • Finally, we return the closest valid object, or nil if there aren’t any.

Setting Up for a CharacterAdded

local function CharacterAdded(character)

This function sets up everything when a new character (player) is added to the game.

	local player = game.Players:GetPlayerFromCharacter(character)
	if not player then
		return
	end

We identify which player the character belongs to. If we can’t find a player, we stop.

	local target = Parts[player]
	local defaultPosition = character:WaitForChild("HumanoidRootPart").CFrame:pointToWorldSpace(Vector3.new(0, 0, -7))
	local isInteracting = false

We prepare a part (target) that helps track where the character is looking. We also define a default position for it and set isInteracting to false initially.

	local ik = Instance.new("IKControl")
	ik.Type = Enum.IKControlType.LookAt
	ik.ChainRoot = character:WaitForChild("UpperTorso")
	ik.EndEffector = character:WaitForChild("Head")
	ik.Weight = 0.999
	ik.Parent = character.Humanoid

We create an IKControl instance, which makes the character’s head and body move naturally when looking at objects.

	local function UpdateIK()

This function updates the character’s IK control based on nearby interactable objects.

		local nearestInteractable = GetNearestInteractable(player)

		if nearestInteractable then
			local headPosition = player.Character.Head.Position
			local targetPosition = nearestInteractable.Position
			local distanceToInteractable = (headPosition - targetPosition).Magnitude

			if distanceToInteractable <= Radius then
				local direction = (targetPosition - headPosition).unit
				local ray = Ray.new(headPosition, direction * distanceToInteractable)
				local hitPart, _ = workspace:FindPartOnRay(ray)

				if hitPart and hitPart:IsDescendantOf(workspace.Ignore) then
					nearestInteractable = nil
				end
			else
				nearestInteractable = nil
			end
		end

		if nearestInteractable then
			isInteracting = true
			ik.Weight = 0.999 -- Enable IK behavior
			ik.Target = nearestInteractable
			target.Position = nearestInteractable.Position
		else
			isInteracting = false
			ik.Weight = 0 -- disable IK behavior
			if player.Character then
				local headPosition = player.Character.Head.Position
				local lookVector = player.Character.Head.CFrame:vectorToWorldSpace(Vector3.new(0, 0, -7))
				target.Position = target.Position:Lerp(headPosition + lookVector, 0.2)
			end
		end
	end

This part determines if there’s a valid interactable object nearby. If so, it adjusts the IK control so the character looks at the object. If not, it stops the IK control.

	character.Humanoid.Died:Connect(function()
		ik:Destroy()
		target:Destroy()
	end)

If the character dies, we clean up by destroying the IK control and target part.

	RunService.RenderStepped:Connect(UpdateIK)

We connect the UpdateIK function to the game loop, so it updates every frame.

Handling New Players

local function PlayerAdded(player)

This function is called whenever a new player joins the game.

	local Target = Instance.new("Part")
	Target.Anchored = true
	Target.CanCollide = false
	Target.CanQuery = false
	Target.CanTouch = false
	Target.Transparency = 0.5
	Target.Size = Vector3.new(1, 1, 1)
	Target.Parent = workspace

	Parts[player] = Target
	if player.Character then
		CharacterAdded(player.Character)
	end

	player.CharacterAdded:Connect(function()
		CharacterAdded(player.Character)
	end)

We create a transparent part for the player to indicate where they’re looking, and store it in the Parts table. If the player already has a character, we set up the interaction system. We also set up an event to do this whenever the player’s character spawns.

Cleaning Up When a Player Leaves

local function PlayerRemoved(player)
	if Parts[player] then
		Parts[player]:Destroy()
		Parts[player] = nil
	end
end

This function is called when a player leaves the game. It removes the target part associated with them from the game and the Parts table.

Setting Up for Current Players

for _, player in ipairs(game.Players:GetPlayers()) do
	PlayerAdded(player)
end

Overall Script:

local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")

local Radius = 10 
local Parts = {}

local function IsInFrontOfCharacter(character, position)
	local rootPart = character.HumanoidRootPart
	local rootPosition = rootPart.Position
	local direction = (position - rootPosition).unit

	local lookVector = rootPart.CFrame:vectorToWorldSpace(Vector3.new(0, 0, -1))
	local toPosition = (position - rootPosition).unit
	local isInFront = lookVector:Dot(toPosition) > 0

	if not isInFront then
		return false
	end

	local ray = Ray.new(rootPosition, direction * (position - rootPosition).Magnitude)
	local hitPart, _ = workspace:FindPartOnRay(ray)

	if hitPart and hitPart:IsDescendantOf(workspace.Ignore) then 
		-- if part is blocked by a wall or obstacle
		return false
	end

	return true
end

local function GetNearestInteractable(player)
	local character = player.Character
	if not character then
		return nil
	end

	local headPosition = character.Head.Position
	local nearestInteractable = nil
	local nearestDistance = Radius

	local interactables = {}
	for _, interactable in pairs(CollectionService:GetTagged("interactable")) do --tag Name
		table.insert(interactables, interactable)
	end

	table.sort(interactables, function(a, b)
		local distanceA = (headPosition - a.Position).Magnitude
		local distanceB = (headPosition - b.Position).Magnitude
		return distanceA < distanceB
	end)

	for _, interactable in ipairs(interactables) do
		local distance = (headPosition - interactable.Position).Magnitude
		if distance <= Radius and IsInFrontOfCharacter(character, interactable.Position) then
			nearestInteractable = interactable
			nearestDistance = distance
			break
		end
	end

	return nearestInteractable
end


local function CharacterAdded(character)
	local player = game.Players:GetPlayerFromCharacter(character)
	if not player then
		return
	end

	local target = Parts[player]
	local defaultPosition = character:WaitForChild("HumanoidRootPart").CFrame:pointToWorldSpace(Vector3.new(0, 0, -7))
	local isInteracting = false

	local ik = Instance.new("IKControl")
	ik.Type = Enum.IKControlType.LookAt
	ik.ChainRoot = character:WaitForChild("UpperTorso")
	ik.EndEffector = character:WaitForChild("Head")
	ik.Weight = 0.999
	ik.Parent = character.Humanoid

	local function UpdateIK()
		local nearestInteractable = GetNearestInteractable(player)

		if nearestInteractable then
			local headPosition = player.Character.Head.Position
			local targetPosition = nearestInteractable.Position
			local distanceToInteractable = (headPosition - targetPosition).Magnitude

			if distanceToInteractable <= Radius then
				local direction = (targetPosition - headPosition).unit
				local ray = Ray.new(headPosition, direction * distanceToInteractable)
				local hitPart, _ = workspace:FindPartOnRay(ray)

				if hitPart and hitPart:IsDescendantOf(workspace.Ignore) then
					nearestInteractable = nil
				end
			else
				nearestInteractable = nil
			end
		end

		if nearestInteractable then
			isInteracting = true
			ik.Weight = 0.999 -- Enable IK behavior
			ik.Target = nearestInteractable
			target.Position = nearestInteractable.Position
		else
			isInteracting = false
			ik.Weight = 0 -- disable IK behavior
			if player.Character then
				local headPosition = player.Character.Head.Position
				local lookVector = player.Character.Head.CFrame:vectorToWorldSpace(Vector3.new(0, 0, -7))
				target.Position = target.Position:Lerp(headPosition + lookVector, 0.2)

			end
		end
	end


	character.Humanoid.Died:Connect(function()
		ik:Destroy()
		target:Destroy()
	end)

	RunService.RenderStepped:Connect(UpdateIK)
end


local function PlayerAdded(player)
	local Target = Instance.new("Part")
	Target.Anchored = true
	Target.CanCollide = false
	Target.CanQuery = false
	Target.CanTouch = false
	Target.Transparency = 0.5
	Target.Size = Vector3.new(1, 1, 1)
	Target.Parent = workspace

	Parts[player] = Target
	if player.Character then
		CharacterAdded(player.Character)
	end

	player.CharacterAdded:Connect(function()
		CharacterAdded(player.Character)
	end)
end

local function PlayerRemoved(player)
	if Parts[player] then
		Parts[player]:Destroy()
		Parts[player] = nil
	end
end

for _, player in ipairs(game.Players:GetPlayers()) do
	PlayerAdded(player)
end

game.Players.PlayerAdded:Connect(PlayerAdded)
game.Players.PlayerRemoving:Connect(PlayerRemoved)

:alien:
bye bye.

9 Likes

Kool, but you need to advertise this better.
The name is horrible and kinda misleading.
Call it Dynamic Head or something.

3 Likes