Basically the script doesn’t set an Y Pos to the model, and that’s why its position is going to be on the same height, to solve that, you should use mouse.hit.Y
, but there’s another problem, the model is now in the middle of the mouse, because the model is moving to the center of the mouse. So I used the model:GetBoundingBox()
to get the actual height of the model, and divide it to 2 so the model won’t float while placing it, using:
local cf, size = model:GetBoundingBox()
local roundedX = math.floor(pos.X)
local roundedZ = math.floor(pos.Z)
local roundedY = math.floor(pos.Y)
local roundedsizeY = math.floor(size.Y)
CFrame = CFrame.new(roundedX, roundedY + (roundedsizeY / 2), roundedZ) * CFrame.Angles(0, math.rad(orientationY), 0)
I also applied the var posY
to the place building event so it won’t be placed on the baseplate even though there’s an object on it. You can also do limits like using math.clamp() to the X and Z positions, even to the Y so it won’t be outside the map, but it’s yours to do it!
PlacementHandler script:
local RS = game:GetService("ReplicatedStorage")
local PlaceObject = RS:WaitForChild("PlaceObject")
local EditObject = RS:WaitForChild("EditObject")
local Objects = RS:WaitForChild("Objects")
local proximityPrompt = script:WaitForChild("ProximityPrompt")
function WeldModel(model)
for objs, obj in pairs(model:GetDescendants()) do
if obj:IsA("BasePart") and obj ~= model.PrimaryPart then
local weldConstraint = Instance.new("WeldConstraint")
weldConstraint.Part0 = obj
weldConstraint.Part1 = model.PrimaryPart
weldConstraint.Parent = obj
obj.Anchored = false
obj.CanCollide = false
end
end
model.PrimaryPart.CanCollide = false
end
function InitModel(model)
local hitbox = Instance.new("Part")
local orientation, size = model:GetBoundingBox()
hitbox.Size = size
hitbox.CFrame = orientation
hitbox.Transparency = 1
hitbox.Parent = model
hitbox.Anchored = true
model.PrimaryPart = hitbox
WeldModel(model)
return
end
PlaceObject.OnServerEvent:Connect(function(plr, objName, posX, posZ, posY, orientationY)
local foundObject = Objects:FindFirstChild(objName)
if foundObject then
if plr.leaderstats.Money.Value >= foundObject.Price.Value then
plr.leaderstats.Money.Value -= foundObject.Price.Value
local placedObject = foundObject:Clone()
InitModel(placedObject)
placedObject:SetPrimaryPartCFrame(CFrame.new(math.floor(posX),
math.floor(posY), math.floor(posZ)) * CFrame.Angles(0, math.rad(orientationY), 0))
if placedObject:FindFirstChild("Collisions") then
for _, collision in pairs(placedObject.Collisions:GetChildren()) do
collision.CanCollide = true
end
end
placedObject.Parent = workspace:WaitForChild("PlacedObjects")
end
end
end)
--[[ EditObject.OnServerEvent:Connect(function(plr, obj, posX, posZ, posY, orientationY, price)
if obj then
if plr.leaderstats.Money.Value >= price then
plr.leaderstats.Money.Value -= price
local placedObject = obj:Clone()
InitModel(placedObject)
placedObject:SetPrimaryPartCFrame(CFrame.new(math.floor(posX),
math.floor(posY), math.floor(posZ)) * CFrame.Angles(0, math.rad(orientationY), 0))
local prompt = proximityPrompt:Clone()
prompt.Enabled = true
prompt.Parent = placedObject.PrimaryPart
obj:Destroy()
placedObject.Parent = workspace:WaitForChild("PlacedObjects")
end
end
end) --]]
LocalPlacementHandler:
local RS = game:GetService("ReplicatedStorage")
local TS = game:GetService("TweenService")
local TI = TweenInfo.new(.4, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
local Objects = RS:WaitForChild("Objects")
local placementModule = require(script.Parent:WaitForChild("LocalPlacementModule"))
local objectsGroup = script.Parent:WaitForChild("ObjectsGroup")
local scrollingFrame = objectsGroup:WaitForChild("ScrollingFrame")
local objectButton = script:WaitForChild("ObjectButton"):Clone()
local isEnabled = false
local function loadObjects()
local amount = 0
for _, object in pairs(Objects:GetChildren()) do
amount += 1
local button = objectButton:Clone()
button.Name = object.Name
button.Text = object.Name
button.Parent = scrollingFrame
button.Visible = true
button.MouseButton1Click:Connect(function()
if Objects:FindFirstChild(button.Name) then
objectsGroup.Visible = false
isEnabled = false
placementModule:BuildModel(Objects[button.Name])
end
end)
end
scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, amount * (objectButton.Size.Y.Offset + 12) * amount)
end
script.Parent:WaitForChild("BuildButton").MouseButton1Click:Connect(function()
if not placementModule.Vars.Placing then
isEnabled = not isEnabled
if isEnabled then
objectsGroup.Visible = true
local Tween = TS:Create(objectsGroup, TI, {GroupTransparency = 0})
Tween:Play()
else
local Tween = TS:Create(objectsGroup, TI, {GroupTransparency = 1})
Tween:Play()
Tween.Completed:Wait()
objectsGroup.Visible = false
end
end
end)
loadObjects()
Module:
local module = {}
local UIS = game:GetService("UserInputService")
local TS = game:GetService("TweenService")
local RS = game:GetService("ReplicatedStorage")
local TI = TweenInfo.new(.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
local PlacingGroup = script.Parent:WaitForChild("PlacingGroup")
local NameLabel = PlacingGroup:WaitForChild("NameLabel")
local PriceLabel = PlacingGroup:WaitForChild("PriceLabel")
local PlaceObject = RS:WaitForChild("PlaceObject")
local EditObject = RS:WaitForChild("EditObject")
local PlacedObjects = workspace:WaitForChild("PlacedObjects")
local highlight = script:WaitForChild("Highlight")
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()
local selectedModel = nil
local connection = nil
module.Vars = {
["Debounce"] = false;
["Placing"] = false;
["Rotating"] = false;
}
function module:ShowBuildInterface(model)
NameLabel.Text = model.Name
PriceLabel.Text = "$" .. model.Price.Value
local Tween = TS:Create(PlacingGroup, TI, {GroupTransparency = 0, Position = UDim2.new(.5, 0, 1, 0)})
Tween:Play()
Tween.Completed:Wait()
return
end
function module:HideBuildInterface()
local Tween = TS:Create(PlacingGroup, TI, {GroupTransparency = 1, Position = UDim2.new(.5, 0, 1.05, 0)})
Tween:Play()
Tween.Completed:Wait()
return
end
function module:WeldModel(model)
for objs, obj in pairs(model:GetDescendants()) do
if obj:IsA("BasePart") and obj ~= model.PrimaryPart then
local weldConstraint = Instance.new("WeldConstraint")
weldConstraint.Part0 = obj
weldConstraint.Part1 = model.PrimaryPart
weldConstraint.Parent = obj
obj.Anchored = false
obj.CanCollide = false
end
end
mouse.TargetFilter = model
module.Vars.Debounce = false
model.PrimaryPart.CanCollide = false
end
function module:InitModel(model)
module.Vars.Debounce = true
local hitbox = Instance.new("Part")
local orientation, size = model:GetBoundingBox()
hitbox.Size = size
hitbox.CFrame = orientation
hitbox.Transparency = 1
hitbox.Parent = model
hitbox.Anchored = true
model.PrimaryPart = hitbox
module:WeldModel(model)
return
end
function module:MoveModel(model)
if not module.Vars.Rotating then
local cf, size = model:GetBoundingBox()
module.Vars.Debounce = true
highlight.Enabled = true
highlight.Adornee = model
local pos = mouse.Hit.Position
local roundedX = math.floor(pos.X)
local roundedZ = math.floor(pos.Z)
local roundedY = math.floor(pos.Y)
local roundedsizeY = math.floor(size.Y)
local orientationY = model.PrimaryPart.Orientation.Y
local Tween = TS:Create(model.PrimaryPart, TI,
{CFrame = CFrame.new(roundedX, roundedY + (roundedsizeY / 2), roundedZ) * CFrame.Angles(0, math.rad(orientationY), 0)})
Tween:Play()
Tween.Completed:Wait()
module.Vars.Debounce = false
end
end
function module:RotateModel(model)
module.Vars.Rotating = true
local Tween = TS:Create(model.PrimaryPart, TI,
{CFrame = model.PrimaryPart.CFrame * CFrame.Angles(math.rad(0), math.rad(90), math.rad(0))})
Tween:Play()
Tween.Completed:Wait()
module.Vars.Rotating = false
end
function module:BuildModel(model, isEditing)
if not module.Vars.Placing then
module.Vars.Placing = true
selectedModel = model
if not isEditing then
selectedModel = model:Clone()
end
module:InitModel(selectedModel)
selectedModel.Parent = workspace:WaitForChild("TemporaryObjects")
module:ShowBuildInterface(selectedModel)
module:MoveModel(selectedModel)
connection = mouse.Move:Connect(function()
module:MoveModel(selectedModel)
end)
mouse.Button1Down:Connect(function()
if not module.Vars.Debounce and module.Vars.Placing then
module.Vars.Placing = false
if connection then
connection:Disconnect()
connection = nil
end
if not isEditing then
PlaceObject:FireServer(selectedModel.Name, selectedModel.PrimaryPart.Position.X,
selectedModel.PrimaryPart.Position.Z,selectedModel.PrimaryPart.Position.Y, selectedModel.PrimaryPart.Orientation.Y)
else
EditObject:FireServer(selectedModel, selectedModel.PrimaryPart.Position.X,
selectedModel.PrimaryPart.Position.Z,selectedModel.PrimaryPart.Position.Y, selectedModel.PrimaryPart.Orientation.Y)
end
workspace:WaitForChild("TemporaryObjects"):ClearAllChildren()
module:HideBuildInterface()
end
end)
end
end
UIS.InputBegan:Connect(function(input, gameEvent)
if not gameEvent then
if input.KeyCode == Enum.KeyCode.R then
if selectedModel and not module.Vars.Rotating then
module:RotateModel(selectedModel)
end
end
end
end)
--[[ local function checkForProximityPrompts()
task.wait()
for models, model in pairs(PlacedObjects:GetChildren()) do
if model:IsA("Model") then
if model.PrimaryPart then
model.PrimaryPart:WaitForChild("ProximityPrompt").Triggered:Connect(function(plr)
if not module.Vars.Placing and not module.Vars.Debounce then
module:BuildModel(model, true)
end
end)
end
end
end
end --]]
-- PlacedObjects.ChildAdded:Connect(checkForProximityPrompts)
-- PlacedObjects.ChildRemoved:Connect(checkForProximityPrompts)
return module