Help with movement boundary on custom character eyes

Edit: I am realizing now the pupil only goes out of bounds when the player is facing the cookie it is looking at from a certain angle, I still have not found a solution yet

  1. What do you want to achieve?
    I’m trying to keep the eye pupils of my character inside the bounds I’ve made for them to move around in. The character is a skinned mesh with only bones for the pupils to move.

  2. What is the issue?
    My issue is keeping the eye pupils of my custom starter character inside the bounds I have created. It seems to directly border the bounds on the front and back face instead while the other edges of the boundary work fine.

  3. What solutions have you tried so far?
    I have tried messing around with my script to no avail. No matter what I try does not fix my issue with keeping the eye pupil in bounds.

I’m fairly new to scripting so I’m sure there is an easy solution to this that I am not aware of

My script for the right eye which is in StarterCharacterScripts:

local function findCookie(pos)
	local cookie = nil
	local dist = 100
	local child = workspace:children()

	for i=1, #child do
		if child[i].className == "Model" then
			local c = child[i]:FindFirstChild("Cookie")
			if c ~= nil then
				local check = child[i]:FindFirstChild("Chips")
				if check ~= nil then
					if (check.Position - pos).magnitude < dist then
						cookie = check
						dist =  (check.Position - pos).magnitude
					end
				end
			end
		end
	end
	return cookie 
end

--eye stuff
game:GetService"RunService".Stepped:connect(function()
	local eyebounds = script.Parent.RightEyeBoundary
	local eyebone = script.Parent.RootPart.RightEye
	local origpos = eyebone.WorldPosition
	local part = script.Parent.RightPupil
	
	local cookie = findCookie(origpos)
	if cookie ~= nil then
		
		eyebone.WorldCFrame = CFrame.new(cookie.Position.X, cookie.Position.Y, eyebone.WorldPosition.Z)
		eyebone.WorldOrientation = Vector3.new(eyebounds.Orientation.X, eyebounds.Orientation.Y, eyebounds.Orientation.Z)
		
		local PosX = math.clamp(eyebone.WorldPosition.X, eyebounds.Position.X - eyebounds.Size.X/2 + part.Size.X/2, eyebounds.Position.X + eyebounds.Size.X/2 - part.Size.X/2)
		local PosY = math.clamp(eyebone.WorldPosition.Y, eyebounds.Position.Y - eyebounds.Size.Y/2 + part.Size.Y/2, eyebounds.Position.Y + eyebounds.Size.Y/2 - part.Size.Y/2)
		local PosZ = math.clamp(eyebone.WorldPosition.Z, eyebounds.Position.Z - eyebounds.Size.Z/2 + part.Size.Z/2, eyebounds.Position.Z + eyebounds.Size.Z/2 - part.Size.Z/2)
		
		eyebone.WorldPosition =  Vector3.new(PosX, PosY, PosZ) -- Setting the position of the part to be in bounds
	end
end)
1 Like

You should make it a .Unit and multiply it by your bounds distance.

1 Like

I’m unsure of how to use .Unit in my code, would you be able to provide an example for this?

local limits = (part.Position-target.Position).Unit + part.Position

something like that.

These lines are problematic because you’re clamping the world-space position of the eyebones to a bounding box that is the size of the ‘eyebounds’ part, but has identity orientation in world space, rather than the eyebounds part’s actually orientation. In other words, you’re clamping eyebone.WorldPosition.X to always be within eyebounds.Position.X +/- eyebounds.Size.X/2 on the world space X-axis, but what you want is to clamp it to these distances on the eyebounds Part’s local X-axis. The way it is now, the clamping is only going to work when the character itself is aligned to the world axes.

I can’t see exactly how your eye bones are oriented relative to the eyebounds part, or what is parented to what, but assuming the eye bones are aligned with the bounding box parts, you’ll want to clamp Bone.Position instead of Bone.WorldPosition.

2 Likes

Thank you for the help, I’m somewhat understanding this now!

Edit:

I used WorldPosition and WorldOrientation for the bones because using Position and Orientation seemed to mess up everything I scripted, but now it works after doing what you suggested!
Changing any other WorldPosition to Position in the script currently does not keep the bone where it should be on the player, and keeps it at a weird distance away.

Unfortunately, I’m still having an issue with the eye positioning on the Y axis, which I will show in a video below

The script I have attempted now:

game:GetService"RunService".Stepped:connect(function()
	local eyebounds = script.Parent.RightEyeBoundary
	local eyebone = script.Parent.RootPart.RightEye
	local origpos = eyebone.Position
	local part = script.Parent.RightPupil
	
	local boundsrel = eyebounds.CFrame:ToObjectSpace(eyebone.WorldCFrame)

	local cookie = findCookie(origpos)
	if cookie ~= nil then

		eyebone.WorldCFrame = CFrame.new(cookie.Position.X, cookie.Position.Y, eyebone.Position.Z)
		eyebone.WorldOrientation = Vector3.new(eyebounds.Orientation.X, eyebounds.Orientation.Y, eyebounds.Orientation.Z)

		local PosX = math.clamp(eyebone.Position.X, boundsrel.Position.X - eyebounds.Size.X/2 + part.Size.X/2, boundsrel.Position.X + eyebounds.Size.X/2 - part.Size.X/2)
		local PosY = math.clamp(eyebone.Position.Y, boundsrel.Position.Y - eyebounds.Size.Y/2 + part.Size.Y/2, boundsrel.Position.Y + eyebounds.Size.Y/2 - part.Size.Y/2)
		local PosZ = math.clamp(eyebone.Position.Z, boundsrel.Position.Z - eyebounds.Size.Z/2 + part.Size.Z/2, boundsrel.Position.Z + eyebounds.Size.Z/2 - part.Size.Z/2)

		eyebone.Position =  Vector3.new(PosX, PosY, PosZ) -- Setting the position of the part to be in bounds
	end
end)

image
image

I don’t think boundsrel.Position should still be a part of this either, since that’s also a world-space part position. Instead, you should probably either save each eye Bone’s initial Position (before you make changes), and offset from that. If the bounds Parts are not centered on the pupils, then you’d additionally have to capture the offset between the eye bone initial WorldPosition and the boundsrel.Position, and use that as the center to which you then add the +/- half box size to. But that offset would be static, not something you need to recompute every frame (assuming the bounds box doesn’t change size or offset at runtime)

Alternately, instead of saving the bounding box to bone initial position offset, you could just do a one-time calculation for the limits of the eye Bone travel in its own coordinate space. You’d then have to store just two Vector3, with the min and max values for x, y, z that the bone is allowed to move away from it’s starting location.

Worth noting, Bones also have a Transform property that can be used to animate them, which lets you set a relative offset and rotation each frame. If you use that though, you have to remember that it needs to be set on each client, because the Transform property doesn’t replicate to the server. This can be done easily now with a Script that has runcontext set to client. This is a bigger change from your current code though, and comes with it’s own new gotchas, but I thought I’d let you know that it’s there if you need it.

2 Likes

I got it working!
Code:

--eye stuff
game:GetService"RunService".Stepped:connect(function()
	local eyebounds = script.Parent.RightEyeBoundary
	local eyebone = script.Parent.RootPart.RightEye
	local origpos = eyebone.WorldPosition
	local part = script.Parent.RightPupil

	local cookie = findCookie(origpos)
	if cookie ~= nil then
		
		local pos = cookie.Position
		local bounds = eyebounds.Size
		local halfObjectSize = part.Size * 0.5
		local corner = eyebounds.CFrame * CFrame.new(bounds * 0.5)
		local relativeCF = corner:toObjectSpace(CFrame.new(pos))
		
		relativeCF = CFrame.new(
			math.clamp(relativeCF.X, -bounds.X + halfObjectSize.X, -halfObjectSize.X),
			math.clamp(relativeCF.Y, -bounds.Y + halfObjectSize.Y, -halfObjectSize.Y),
			math.clamp(relativeCF.Z, -bounds.Z + halfObjectSize.Z, -halfObjectSize.Z)
		)
		eyebone.WorldCFrame = corner * relativeCF
	end
end)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.