Help with placement system

Hello!

I am currently trying to make a placement system where you can select an object from a GUI that you want to place, and it will be appear wherever your mouse clicks. The object however spawns halfway into the ground.
image
It won’t move from that spot no matter where your mouse clicks. You also can’t spawn another part as it will just overlap the past instance.

Client script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlaceStructure = ReplicatedStorage:WaitForChild("PlaceStructure")
local Structures = ReplicatedStorage:WaitForChild("Structures")

local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local Player = game.Players.LocalPlayer
local StructureFrame = script.Parent.StructureFrame
local Character = Player.Character or Player.Character:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")

local Mouse = Player:GetMouse()

local yBuildingOffset = 5
local MaxPlacingDistance = 50
local rKeyIsPressed = false
local PlacingStructure = false

for _, structureButton in pairs(StructureFrame:GetChildren()) do
	if structureButton:IsA("TextButton") then
		structureButton.MouseButton1Up:Connect(function()
			
			StructureFrame.Visible = false
			
			local yOrientation = 0
			local GoodToPlace = false
			local PlacedStructure
			
			if PlacingStructure == false then
				PlacingStructure = true
				
				local ClientStructure = Structures:FindFirstChild(structureButton.Name):Clone()
				ClientStructure.BrickColor = BrickColor.new("Forest green")
				ClientStructure.Material = "Neon"
				ClientStructure.CanCollide = false
				ClientStructure.Parent = game.Workspace
				
				local StartingCFrame = CFrame.new(0, -2, -15)
				ClientStructure.CFrame = HumanoidRootPart.CFrame:ToWorldSpace(StartingCFrame)
				
				RunService.RenderStepped:Connect(function()
					local MouseRay = Mouse.UnitRay
					local CastRay = Ray.new(MouseRay.Origin, MouseRay.Direction * 1000)
					local IgnoreList = {ClientStructure, Character}
					local hit, position = workspace:FindPartOnRayWithIgnoreList(CastRay, IgnoreList)
					
					if hit and (HumanoidRootPart.Position - ClientStructure.Position).Magnitude < MaxPlacingDistance then --and (hit:IsA("Terrain") or hit.Name:lower() == "terrain") 
						GoodToPlace = true
						ClientStructure.BrickColor = BrickColor.new("Forest green")
					else
						GoodToPlace = false
						ClientStructure.BrickColor = BrickColor.new("Crimson")
					end
					
					local NewAnglesCFrame = CFrame.Angles(0, math.rad(yOrientation), 0)
					local NewCFrame = CFrame.new(position.X, position.Y + yBuildingOffset, position.Z)
					ClientStructure.CFrame = NewCFrame * NewAnglesCFrame
				end)
				
				UserInputService.InputBegan:Connect(function(input) -- GPE
					if input.KeyCode == Enum.KeyCode.R then -- and not GPE then
						rKeyIsPressed = true
						
						local RotationSpeed = 5
						while rKeyIsPressed do
							task.wait()
							if PlacingStructure == true then
								yOrientation = yOrientation + RotationSpeed
							end
						end
					end
				end)
				
				UserInputService.InputEnded:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						rKeyIsPressed = false
					end
				end)
				
				UserInputService.InputBegan:Connect(function(input)
					if input.UserInputType == Enum.UserInputType.MouseButton1 then
						if PlacingStructure == true then
							if GoodToPlace == true then
								local StructureCFrame = ClientStructure.CFrame
								PlacedStructure = PlaceStructure:InvokeServer(ClientStructure.Name, StartingCFrame)
								
								if PlacedStructure == true then
									PlacingStructure = false
									ClientStructure:Destroy()
									StructureFrame.Visible = true
								end
							end
						end
					end
				end)
			end
		end)
	end
end

Server script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlaceStructure = ReplicatedStorage:WaitForChild("PlaceStructure")
local Structures = ReplicatedStorage:WaitForChild("Structures")

PlaceStructure.OnServerInvoke = function(Player, StructureName, StructureCFrame)
	
	local Crafted
	local RealStructure = Structures:FindFirstChild(StructureName):Clone()
	
	if RealStructure then
		RealStructure.CFrame = StructureCFrame
		RealStructure.Parent = game.Workspace
		Crafted = true
	else
		Crafted = false
	end
	
	return Crafted
end

And yes, I am aware that some things may be deprecated, as this was from an old tutorial. I can change things if need be, but I would prefer to keep it as is. Also, let me know if I need to explain anything better.

If you think you know what went wrong, please let me know. Thank you and have wonderful day! :slight_smile:

1 Like

You are placing the Part at the Position the mouse is pointing at on the baseplate. You need to add 1/2 the height of the Part you are placing to the CFrame Position of the mouse.

There are plenty of posts on the forums about “grid building”, “placing parts in game” etc. Try searching them up for better explanations than I can give.

4 Likes

It’s as @Scottifly said. I believe you can simply change this line

to

local NewCFrame = CFrame.new(position.X, position.Y + ClientStructure.Size.Y, position.Z)

(this is assuming the yOffset variable was to try and fix this issue, but wouldn’t work for every structure)
If the yOffset was different than I thought and was for a completely different purpose, you can do

local NewCFrame = CFrame.new(position.X, position.Y + yBuildingOffset + ClientStructure.Size.Y, position.Z)

If ClientStructure is a model, just change ClientStructure.Size.Y to ClientStructure.PrimaryPart.Size.Y and make the primary part an invisible bounding box of the model.

1 Like

It didn’t really fix anything. It raises the selected structure into the air when clicked, and I would like it to stay on the ground.

You can just spawn it at the mouse’s position but use :MoveTo() which accounts for physics and will move it to the nearest spot above the ground. It will also stop the object from being able to be spawned in a wall.

Can I see a code sample please? I don’t know if it would fit in very well with my current code.

You can use :MoveTo() anywhere you move a model with CFrame. Note that :MoveTo() only works on models.

--CFrame version
RealStructure.CFrame = StructureCFrame

--MoveTo version
--get rid of the structure holder part if the realstructure is already a model
local structureHolder = Instance.new("Model")
structureHolder.Parent = game.Workspace
structureHolder.Name = "structureHolder"

RealStructure.Parent = structureHolder
structureHolder:MoveTo(StructureCFrame.Position) --:MoveTo() uses vector

--edit:fixed rotation. not sure if this works
structureHolder:PivotTo(structureHolder:GetPivot() * CFrame.Angles(StructureCFrame.Rotation))
1 Like

It has to be noted that :MoveTo() will remove any rotation present on the model.
If the rotation of the model has to be preserved a different approach has to be used.
I’m guessing the model would have to be rotated to fit the normal vector of the surface the mouse is pointing at.

Sadly this won’t work for me. The structure is not a model, but a regular part I changed the properties on. This could probably work for me in the future though, as I will most likely include models to be selected later on.

Are you adding the (workspace oriented, not Part oriented) Y size of the placed Part, or 1/2 the Y size?
You need to place the Position of the Part 1/2 the Y value to make it sit right on the ground.
If your Part is 10 studs high in the workspace Y direction then it’s placed Position needs to sit 1/2 of the Y value to be centered 5 studs above the Mouse Postion.

This approach wouldn’t account for the rotation of an object. If a part was flipped so that its up vector was pointing in a different direction than the y axis the part would no longer align correctly.

The code I made accounts for the fact that the structure could be a part and already puts it in a model. I made a comment above saying to remove that part if you decide to change to models later.

@Scottifly Sorry, but I’m not really understanding what you’re saying.

@happya_x I think that could probably work, but I don’t know how to put it into my code.

Hence the reason I stated “workspace oriented, not part oriented”.

I rescripted the whole system in a new game with nothing inside it, so other scripts wouldn’t interfere. But sadly, I’m still having the same issue as before.
Main Client Script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlaceStructure = ReplicatedStorage:WaitForChild("PlaceStructure")
local Structures = ReplicatedStorage:WaitForChild("Structures")

local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local Player = game.Players.LocalPlayer
local StructureFrame = script.Parent.StructureFrame
local Character = Player.Character or Player.Character:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")

local Mouse = Player:GetMouse()

local yBuildingOffset = 5
local MaxPlacingDistance = 50
local rKeyIsPressed = false
local PlacingStructure = false

for _, StructureButton in pairs(StructureFrame:GetChildren()) do
	if StructureButton:IsA("TextButton") then
		StructureButton.MouseButton1Click:Connect(function()
			
			StructureFrame.Visible = false
			
			local yOrientation = 0
			local GoodToPlace = false
			local PlacedStructure
			
			if PlacingStructure == false then
				PlacingStructure = true
				
				local ClientStructure = Structures:FindFirstChild(StructureButton.Name):Clone()
				ClientStructure.BrickColor = BrickColor.new("Forest green")
				ClientStructure.Material = "Neon"
				ClientStructure.CanCollide = false
				ClientStructure.Parent = game.Workspace
				
				local StartingCFrame = CFrame.new(0, -2, -15)
				ClientStructure.CFrame = HumanoidRootPart.CFrame:ToWorldSpace(StartingCFrame)
				
				RunService.RenderStepped:Connect(function()
					local MouseRay = Mouse.UnitRay
					local CastRay = Ray.new(MouseRay.Origin, MouseRay.Direction * 1000)
					local IgnoreList = {ClientStructure, Character}
					local hit, position = workspace:FindPartOnRayWithIgnoreList(CastRay, IgnoreList)
					
					if hit and (hit:IsA("Terrain") or hit.Name:lower() == "terrain") and (HumanoidRootPart.Position - ClientStructure.Position).Magnitude < MaxPlacingDistance then
						GoodToPlace = true
						ClientStructure.BrickColor = BrickColor.new("Forest green")
					else
						GoodToPlace = false
						ClientStructure.BrickColor = BrickColor.new("Crimson")
					end
					
					local NewAnglesCFrame = CFrame.Angles(0, math.rad(yOrientation), 0)
					local NewCFrame = CFrame.new(position.X, position.Y + yBuildingOffset + ClientStructure.Size.Y, position.Z)
					ClientStructure.CFrame = NewCFrame * NewAnglesCFrame
				end)
				
				UserInputService.InputBegan:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						rKeyIsPressed = true
						
						local RotationSpeed = 5
						while rKeyIsPressed do
							task.wait()
							if PlacingStructure == true then
								yOrientation = yOrientation + RotationSpeed
							end
						end
					end
				end)
				
				UserInputService.InputEnded:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						rKeyIsPressed = false
					end
				end)
				
				UserInputService.InputBegan:Connect(function(input)
					if input.UserInputType == Enum.UserInputType.MouseButton1 then
						if PlacingStructure == true then
							if GoodToPlace == true then
								local StructureCFrame = ClientStructure.CFrame
								PlacedStructure = PlaceStructure:InvokeServer(ClientStructure.Name, StartingCFrame)
								
								if PlacedStructure == true then
									PlacedStructure = false
									ClientStructure:Destroy()
									StructureFrame.Visible = false
								end
							end
						end
					end
				end)
			end
		end)
	end
end

Server script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlaceStructure = ReplicatedStorage:WaitForChild("PlaceStructure")
local Structures = ReplicatedStorage:WaitForChild("Structures")

PlaceStructure.OnServerInvoke = function(Player, StructureName, StructureCFrame)
	
	local crafted
	local RealStructure = Structures:FindFirstChild(StructureName):Clone()
	
	if RealStructure then
		RealStructure.CFrame = StructureCFrame
		RealStructure.Parent = game.Workspace
		crafted = true
	else
		crafted = false
	end
	
	return crafted
end

please help and thank you

Just going to bump this because I really need to help fixing this.

I made a few small tweaks to the code, but nothing real big as the issue persists. I marked where I think the bug is happening.

Hierarchy:
image

LocalScript:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlaceStructure = ReplicatedStorage:WaitForChild("PlaceStructure")
local Structures = ReplicatedStorage:WaitForChild("Structures")

print(PlaceStructure, Structures)

local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local Player = game.Players.LocalPlayer
local StructureFrame = script.Parent:WaitForChild("StructureFrame")
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")

local Mouse = Player:GetMouse()

local yBuildingOffset = 5
local MaxPlacingDistance = 50
local rKeyIsPressed = false
local PlacingStructure = false

for _, structureButton in pairs(StructureFrame:GetChildren()) do
	if structureButton:IsA("TextButton") then
		structureButton.MouseButton1Click:Connect(function()
			
			StructureFrame.Visible = false
			
			local yOrientation = 0
			local GoodToPlace = false
			local PlacedStructure
			
			if PlacingStructure == false then
				PlacingStructure = true
				
				local ClientStructure = Structures:FindFirstChild(structureButton.Name):Clone()
				ClientStructure.BrickColor = BrickColor.new("Forest green")
				ClientStructure.Material = "Neon"
				ClientStructure.CanCollide = false
				ClientStructure.Parent = game.Workspace
				
				--ISSUE
				local StartingCFrame = CFrame.new(0, -2, -15) 
				ClientStructure.CFrame = HumanoidRootPart.CFrame:ToWorldSpace(StartingCFrame)
				--
				
				RunService.RenderStepped:Connect(function()
					local MouseRay = Mouse.UnitRay
					local CastRay = Ray.new(MouseRay.Origin, MouseRay.Direction * 1000)
					local IgnoreList = {ClientStructure, Character}
					local hit, position = workspace:FindPartOnRayWithIgnoreList(CastRay, IgnoreList) --Maybe replace this with the workspace:Raycast() function and use RaycastParams.
					
					if hit then --and (HumanoidRootPart.Position - ClientStructure.Position).Magnitude < MaxPlacingDistance and (hit:IsA("Terrain") or hit.Name:lower() == "terrain") 
						GoodToPlace = true
						ClientStructure.BrickColor = BrickColor.new("Forest green")
					else
						GoodToPlace = false
						ClientStructure.BrickColor = BrickColor.new("Crimson")
					end
					
					local NewAnglesCFrame = CFrame.Angles(0, math.rad(yOrientation), 0)
					local NewCFrame = CFrame.new(position.X, position.Y + yBuildingOffset, position.Z)
					ClientStructure.CFrame = NewCFrame * NewAnglesCFrame
				end)
				
				UserInputService.InputBegan:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						rKeyIsPressed = true
						
						local RotationSpeed = 5
						while rKeyIsPressed do
							task.wait()
							if PlacingStructure == true then
								yOrientation += RotationSpeed
							end
						end
					end
				end)
				
				UserInputService.InputEnded:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.R then
						rKeyIsPressed = false
					end
				end)
				
				UserInputService.InputBegan:Connect(function(input)
					if input.UserInputType == Enum.UserInputType.MouseButton1 then
						if PlacingStructure == true then
							if GoodToPlace == true then
								local StructureCFrame = ClientStructure.CFrame
								PlacedStructure = PlaceStructure:InvokeServer(ClientStructure.Name, StartingCFrame)
								
								if PlacedStructure == true then
									PlacingStructure = false
									ClientStructure:Destroy()
									StructureFrame.Visible = true
									GoodToPlace = false --Don't know if I have to set this back to false or not but I did anyway lol
								end
							end
						end
					end
				end)
			end
		end)
	end
end

Server:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PlaceStructure = ReplicatedStorage:WaitForChild("PlaceStructure")
local Structures = ReplicatedStorage:WaitForChild("Structures")

PlaceStructure.OnServerInvoke = function(Player, StructureName, StructureCFrame)
	local Crafted  = false
	local RealStructure = Structures:FindFirstChild(StructureName):Clone()
	
	if RealStructure then
		RealStructure.CFrame = StructureCFrame
		RealStructure.Parent = game.Workspace.PlacedStructures
		Crafted = true
	else
		Crafted = false
	end
	return Crafted
end

Also, for some reason, whenever an object is placed, WoodWall is duplicated twice. I just noticed this and am not real sure why this is occurring either.