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.