I don’t really know how much of the code would need to go in that loop or where the loop should be in the script, if that matters. I found 1 reference to targetPart
in the script, so I assume that should be the only code included in the loop, but I don’t know for sure.
Currently this is what I have for the script:
local RunService = game:GetService('RunService')
----------------------------------------
-- --
-- Const --
-- --
----------------------------------------
-- i.e. margin of err for fp calc
local EPSILON = 1e-6
-- i.e. max distance between the plane surface and the target position
local MAX_PLANE_DISTANCE = 10
----------------------------------------
-- --
-- Util --
-- --
----------------------------------------
local function getclosestplr()
local bot_position = game.Workspace:WaitForChild('PlanePart3').Position
local distance = math.huge
local closest_player_character = nil
for i, player in pairs(workspace:GetChildren()) do
-- you have put player, but this is really the players character
if player:FindFirstChild("Humanoid") then
local player_position = player.HumanoidRootPart.Position
local distance_from_bot = (bot_position - player_position).magnitude
if distance_from_bot < distance then
distance = distance_from_bot
closest_player_character = player
end
end
end
return closest_player_character:WaitForChild('Head')
end
-- calculate the square magnitude of a vector
local function getSqrMagnitude(vec)
return vec.X*vec.X + vec.Y*vec.Y + vec.Z*vec.Z
end
-- calculate the closest point on a plane
local function getClosestPointOnPlane(normal, distance, vec)
local d = normal:Dot(vec) + distance
return vec - normal*d
end
-- calculate the distance to/from a plane given a plane's properties and a vector
local function getDistanceToPlane(normal, distance, vec)
return normal:Dot(vec) + distance
end
-- given a transform and a normal id, compute the direction vector e.g. LookVector from Enum.NormalId.Front
local function getDirectionVector(transform, normalId)
return transform * Vector3.FromNormalId(normalId) - transform.Position
end
-- compute a plane given a part, which we use as a plane origin,
-- and a normal id, which we use to derive the normal vector
local function computePlaneFromPartSurface(part, surfaceShape, forwardNormalId, upNormalId)
surfaceShape = surfaceShape or Enum.PartType.Block
upNormalId = upNormalId or Enum.NormalId.Top
forwardNormalId = forwardNormalId or Enum.NormalId.Front
local size = part.Size
local transform = part.CFrame
local translation = part.Position
-- compute the plane
local normal = getDirectionVector(transform, forwardNormalId)
local distance = -1 * normal:Dot(translation)
-- compute the part's size from its right & up vector
local upVector = getDirectionVector(transform, upNormalId)
local rightVector = normal:Cross(upVector)
local scale = Vector2.new(
(size*upVector).Magnitude,
(size*rightVector).Magnitude
)
-- compute the travel distance using the largest axis
local travelDistance = math.max(scale.X, scale.Y)
-- compute the area of the surface from its given shape
local area
if surfaceShape == Enum.PartType.Cylinder then
area = math.pi*(travelDistance*0.5 + 1)
else
--[!] Treat it as a block instead...
area = (scale.X*scale.Y)*0.5
end
return translation, scale, normal, distance, area, travelDistance
end
-- compute the eye position given a target position,
-- and the pre-computed plane props
local function computeEyePosition(
position, planeOffset,
planeNormal, planeDistance, planeRadius,
maxTravelDistance, maxSqrRadius, maxPlaneDistance
)
maxSqrRadius = maxSqrRadius or MAX_PLANE_DISTANCE*MAX_PLANE_DISTANCE
maxPlaneDistance = maxPlaneDistance or MAX_PLANE_DISTANCE
maxTravelDistance = maxTravelDistance or planeRadius*0.5
-- get the target position's closest point on the plane
local closestPoint = getClosestPointOnPlane(planeNormal, planeDistance, position)
-- get the depth to/from the plane from the target's position
local distanceToPlane = getDistanceToPlane(planeNormal, planeDistance, position)
-- get the distance of the closest position from the centre point of the plane
local displacement = closestPoint - planeOffset
local magnitude = getSqrMagnitude(displacement)
-- since the eye is pseudo-2d we need to check whether the eye is in front or behind the plane
local isOnCorrectSide = distanceToPlane > 0
-- check whether the eye is within the radius that we care about, and if they're not too far from the plane (in terms of depth)
local isWithinDistance = magnitude <= maxSqrRadius and distanceToPlane <= maxPlaneDistance
-- if we dont meet either condition, go back to the centre of the plane
if not isOnCorrectSide or not isWithinDistance then
return false, planeOffset
end
-- compute the new position of the eye when tracking the target position
displacement = magnitude == 1 and displacement or (magnitude > EPSILON and displacement.Unit or Vector3.zero)
magnitude = math.clamp(magnitude / planeRadius, 0, 1)
return true, planeOffset + displacement*magnitude*maxTravelDistance
end
----------------------------------------
-- --
-- Main --
-- --
----------------------------------------
-- await our prefabs
local planePart = workspace:WaitForChild('PlanePart3')
local targetPart = getclosestplr()
-- create our eye
local eyePart = Instance.new('Part')
eyePart.Name = 'DebugPart'
eyePart.Size = Vector3.new(18,18,0.3)
eyePart.Shape = Enum.PartType.Block
eyePart.Position = planePart.Position
eyePart.Anchored = true
eyePart.CanTouch = false
eyePart.CanCollide = false
eyePart.CanCollide = false
eyePart.Transparency = 1
eyePart.BrickColor = BrickColor.Green()
eyePart.TopSurface = Enum.SurfaceType.Smooth
eyePart.BottomSurface = Enum.SurfaceType.Smooth
eyePart.Parent = workspace
local eyeDecal = Instance.new('Decal')
eyeDecal.Transparency = 0
eyeDecal.Color3 = Color3.new(0, 1, 0)
eyeDecal.Face = Enum.NormalId.Front
eyeDecal.Texture = 'rbxassetid://16687464344'
eyeDecal.Parent = eyePart
-- get our initial plane props
local eyeUpSurface = Enum.NormalId.Top
local eyeFrontSurface = Enum.NormalId.Front
local eyeSurfaceShape = Enum.PartType.Cylinder
local maxEyeViewingRadius = 50
local maxEyeViewingRadiusSqr = maxEyeViewingRadius*maxEyeViewingRadius
local offset, scale, normal, distance, maxAreaSqr, travelDistance = computePlaneFromPartSurface(planePart, eyeSurfaceShape, eyeFrontSurface, eyeUpSurface)
travelDistance *= 0.25 -- limit the travel distance our eye can move
-- update the plane's properties if the plane is moved
local updateTask
planePart:GetPropertyChangedSignal('CFrame'):Connect(function ()
if updateTask then
return
end
updateTask = task.defer(function ()
offset, scale, normal, distance, maxAreaSqr, travelDistance = computePlaneFromPartSurface(planePart, eyeSurfaceShape, eyeFrontSurface, eyeUpSurface)
-- limit the travel distance our eye can move
travelDistance *= 0.25
updateTask = nil
end)
end)
-- update the eye position at runtime
RunService.Stepped:Connect(function (gt, dt)
-- can be replaced with whatever target we want to follow
local position = targetPart.Position
-- det. whether we're watching the target, and where our desired position is
local isWatching, desiredPosition = computeEyePosition(
-- target position & our pre-calculated plane properties
position, offset, normal, distance, maxAreaSqr,
-- the maximum distance our eye can travel
travelDistance,
-- make sure we only position ourself if our target is within x viewing radius
maxEyeViewingRadiusSqr,
-- use our constant value so we're not considering things that are greater than x studs away
MAX_PLANE_DISTANCE
)
-- if we're not already at our desired position - within eps 1e-3 - then interpolate
-- towards that position
position = eyePart.Position
if not desiredPosition:FuzzyEq(position, 1e-3) then
eyePart.Position = position:Lerp(desiredPosition, dt*6)
end
-- e.g. visually show that we're not watching the subject
eyePart.BrickColor = isWatching and BrickColor.Red() or BrickColor.Black()
end)
--[!] Note: ::getDirectionVector() method should be defined as in the previous script
local function computeMaxTravelDistance(eyeTransform, eyeUpSurface, eyeFrontSurface, maxWidth, maxHeight)
maxWidth = maxWidth or 1
maxHeight = maxHeight or 1
eyeUpSurface = eyeUpSurface or Enum.NormalId.Top
eyeFrontSurface = eyeFrontSurface or Enum.NormalId.Front
local upVector = getDirectionVector(eyeTransform, eyeUpSurface)
local lookVector = -1*getDirectionVector(eyeTransform, eyeFrontSurface)
local rightVector = upVector:Cross(lookVector).Unit
return upVector*maxHeight + rightVector*maxWidth
end
-- USAGE
local eyeUpSurface = Enum.NormalId.Top
local eyeFrontSurface = Enum.NormalId.Front
local maxWidth = 5
local maxHeight = 2.5
travelDistance = computeMaxTravelDistance(planePart.CFrame, eyeUpSurface, eyeFrontSurface, maxWidth, maxHeight)