✓ Accepted answer
At the moment, your script is spawning the part at a Y position of 0, which is not necessarily where the ground is. In some cases, the ground may be higher or lower than that, as you surely have noticed. This means that we need some way of figuring out where the ground is at certain point given the X and Z coordinates.
Thankfully, Roblox provides us with just what we need: raycasting. For our purposes, you can think of rays as "half" of a line: they extend infinitely in a certain direction, while in the other direction they have a specific ending point. For technical reasons, Roblox raycasts don't allow rays to extend infinitely, but instead we must set a cap. Anyways, in this case we want to perform a raycast using the HumanoidRootPart's position as a starting point, and the ray's direction should be along the Y axis. In code, this looks like so:
local startPos = player.Character.HumanoidRootPart.Position
local direction = Vector3.new(0, -1, 0).unit
local magnitude = 500
local ray = Ray.new(startPos, direction * magnitude)
local foundPart, endPos = workspace:FindPartOnRay(ray, player.Character, false, true)
if foundPart == nil then
print("Player isn't above a floor!")
else
local part = Instance.new("Part", workspace)
part.Position = endPos
end
FindPartOnRay actually returns two more values: the normal vector of the surface that the ray hit (this can be used for calculating the angle between the ray and the surface) and the material of the thing that was hit. Those aren't useful for our purposes.
1 comment
Good catch on the client-server separation! I forgot to take it into consideration in my answer.