"Attaching" Surfaces via Script

Hey all, good evening!

Was wondering if anyone had any idea how to do something like this? For further clarification of what it is, I want to be able to “attach” the bottom surface of that part, to whatever surface is touching the bottom surface.

I figure I am going to be using CFrame vectors to accomplish this (upvector, lookvector, etc.) but beyond these ideas I have no clue where I should start.

Has anybody on these forums ever done anything similar to this?

2 Likes

Maybe you can try playing around with EgoMoose’s Custom Character Controller, maybe try applying it not only to players’ characters, but also parts. However, that would be more of a challenge than a helping hand… At most, you should look at the formulas used in the controller to give you an idea of where to start I guess.

some examples from the character controller

Source: ReplicatedStorage.Controller, Line 204-229

local UP = Vector3.new(0,1,0)

-- where self is a storage table and normal is an UpVector for gravity
-- self.Last.Axis describes the last `cross` used by the function
local function getSurfaceCFrame(self, normal)
	local surfaceCF = CFrame.new()

	local dot = UP:Dot(normal)
	local cross = UP:Cross(normal)
	
	if (cross:Dot(cross) > 0) then
		cross = cross.Unit
		surfaceCF = CFrame.fromAxisAngle(cross, -math.acos(dot))

		self.Last.Axis = cross
	elseif (dot < 0) then
		surfaceCF = CFrame.fromAxisAngle(self.Last.Axis, -math.pi)
	end

	-- returns a CFrame that conforms to the normal provided
	return surfaceCF
end

Source: ReplicatedStorage.Controller.Cast, Line 137-157

-- Draws a ray down from the character and returns results from
-- workspace:FindPartOnRay
function Cast:Line(modifier)
	modifier = modifier or 1
	local char, hum, hrp = self.Character, self.Humanoid, self.HRP
	local hipHeight = (hum.RigType == Enum.HumanoidRigType.R15) and hum.HipHeight or 2
	
	local down = -hrp.CFrame.UpVector
	local origin = hrp.Position
	local height = (hipHeight + hrp.Size.y/2) * modifier
	
	local ray = Ray.new(origin, down * (height + 1))
	local hit, pos, normal = game.Workspace:FindPartOnRay(ray, self.Ignore)
	local valid = (pos - origin).Magnitude - 0.1 <= height
	
	if (valid) then
		normal = (normal ~= normal) and UP or normal
		return hit, pos, normal
	end
	return nil, pos, UP
end

Source: ReplicatedStorage.Controller.Cast, Line 11-23 and 194-227

local function sunflower(n, alpha)
	local i, phi2 = 1, PHI*PHI
	local b = math.ceil(alpha*math.sqrt(n))
	return function()
		if (i <= n) then
			i = i + 1
			local r = i > (n - b) and 1 or math.sqrt(i - 0.5)/math.sqrt(n - (b + 1)/2)
			local t = 2*PI*i/(phi2)
			return r*math.cos(t), r*math.sin(t)
		end
		return
	end
end

--...

local CAST_COUNT = 32

-- casts multiple rays in a cylinder shape, but in a `sunflower` pattern
function Cast:Cylinder(radius, modifier)
	modifier = modifier or 1
	radius = radius or 2

	local char, hum, hrp = self.Character, self.Humanoid, self.HRP
	local hipHeight = (hum.RigType == Enum.HumanoidRigType.R15) and hum.HipHeight or 2
	
	local origin = hrp.CFrame
	local down = -(hipHeight + hrp.Size.y) * origin.UpVector
	local height = (hipHeight + hrp.Size.y/2) * modifier
	
	local count = 0
	local ray = Ray.new(origin.p, down)
	local hit, pos, baseNormal = game.Workspace:FindPartOnRay(ray, self.Ignore)
	local normal = Vector3.new(0, 0, 0)
	
	hit = (pos - origin.p).Magnitude - 0.1 < height and hit or nil
	
	for x, z in sunflower(CAST_COUNT, 2) do
		local start = origin * Vector3.new(x*radius, 0, z*radius)
		
		local ray = Ray.new(start, down)
		local hit2, pos2, normal2 = game.Workspace:FindPartOnRay(ray, self.Ignore)
		local dist = (pos2 - origin.p).Magnitude

		if (hit2 and dist - 0.1 < height) then
			count = count + 1
			normal = normal + normal2
		end
		
		debugger.point(start + down, Color3.new(1, 0, 1))
	end

	--returns an average of normals rather than just one normal
	return hit, pos, (count > 0) and normal / count or baseNormal
end

I recommend you look at the first example at least!

The most common approach I have seen to making something attach to walls is casting tons of rays down in just the right direction to find an average surface normal for your character (or object in your case) to face in. This is what the last two examples I provided help with.
I have seen this type of thing used for hover cars (multiple actually), the Luanoid, and the character controller I mentioned up there, and each achieve different functions overall.

4 Likes

You can try using the Touched event and create an invisible, non collidable hit box part on the underside and move the unanchored original part using body rocket I think. Its a combo of body velocity and body gyro. The gyro would turn to always face the surface and the velocity can be controlled to move it wherever.
This needs to be refined however, but give it a shot and see how it goes

3 Likes

I think this is the right track, but its easier to start from just the cross product and build up than it is to start from the character controller and remove stuff.

Check out this guide:
https://github.com/EgoMoose/Articles/blob/master/Vectors/Cross%20product.md

6 Likes

Can send the link again please? It’s down :frowning:

1 Like

Idk why, but i think it had something to do with discourse formatting the link. I editted the post so you should be able to click it now :grin:.

4 Likes

Wonderful tutorial! Should be able to use this as a base for what I am trying to do. Thank you!

2 Likes