Recently I was looking around to see if I could find a faster way to furnish the buildings I was making, and I found a placement system that works great for it, but I want to be able to tilt the objects I'm placing, because plain rotation isn't the best.
The system has 3 scripts, and I added 1 as well.
Server Script :
local datastore = game:GetService("DataStoreService"):GetDataStore("PlacementSystem")
local placementClass = require(game:GetService("ReplicatedStorage"):WaitForChild("Placement"))
local placementObjects = {}
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
--
function remotes.InitPlacement.OnServerInvoke(player, canvasPart)
placementObjects[player] = placementClass.new(canvasPart)
return placementObjects[player].CanvasObjects
end
remotes.InvokePlacement.OnServerEvent:Connect(function(player, func, ...)
if (placementObjects[player]) then
placementClass[func](placementObjects[player], ...)
end
end)
function remotes.DSPlacement.OnServerInvoke(player, saving, useData)
local key = "player_"..player.UserId
local success, result = pcall(function()
if (saving and placementObjects[player]) then
if (useData) then
datastore:SetAsync(key, placementObjects[player]:Serialize())
else
datastore:SetAsync(key, {})
end
elseif (not saving) then
return datastore:GetAsync(key)
end
end)
if (success) then
return saving or result
else
warn(result)
end
end
Module script:
local isServer = game:GetService("RunService"):IsServer()
local furniture = game:GetService("ReplicatedStorage"):WaitForChild("Furniture")
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
local initPlacement = remotes:WaitForChild("InitPlacement")
local invokePlacement = remotes:WaitForChild("InvokePlacement")
local dsPlacement = remotes:WaitForChild("DSPlacement")
--
local Placement = {}
Placement.__index = Placement
-- constructor
function Placement.new(canvasPart)
local self = setmetatable({}, Placement)
self.CanvasPart = canvasPart
if (isServer) then
self.CanvasObjects = Instance.new("Folder")
self.CanvasObjects.Name = "CanvasObjects"
self.CanvasObjects.Parent = canvasPart
else
self.CanvasObjects = initPlacement:InvokeServer(canvasPart)
end
self.Surface = Enum.NormalId.Top
self.GridUnit = 1
return self
end
function Placement.fromSerialization(canvasPart, data)
local self = Placement.new(canvasPart)
local canvasCF = canvasPart.CFrame
data = data or {}
for cf, name in pairs(data) do
local model = furniture:FindFirstChild(name)
if (model) then
local components = {}
for num in string.gmatch(cf, "[^%s,]+") do
components[#components+1] = tonumber(num)
end
self:Place(model, canvasCF * CFrame.new(unpack(components)), false)
end
end
return self
end
-- methods
function Placement:CalcCanvas()
local canvasSize = self.CanvasPart.Size
local up = Vector3.new(0, 1, 0)
local back = -Vector3.FromNormalId(self.Surface)
local dot = back:Dot(Vector3.new(0, 1, 0))
local axis = (math.abs(dot) == 1) and Vector3.new(-dot, 0, 0) or up
local right = CFrame.fromAxisAngle(axis, math.pi/2) * back
local top = back:Cross(right).unit
local cf = self.CanvasPart.CFrame * CFrame.fromMatrix(-back*canvasSize/2, right, top, back)
--local size = Vector2.new((canvasSize * right).magnitude, (canvasSize * top).magnitude)
local size = Vector2.new(100000, 100000)-- gives near infinite range
return cf, size
end
function Placement:CalcPlacementCFrame(model, position, rotation)-- rotation
local cf, size = self:CalcCanvas()
local modelSize = CFrame.fromEulerAnglesYXZ(0, rotation, 0) * model.PrimaryPart.Size
modelSize = Vector3.new(math.abs(modelSize.x), math.abs(modelSize.y), math.abs(modelSize.z))
local lpos = cf:pointToObjectSpace(position);
local size2 = (size - Vector2.new(modelSize.x, modelSize.z))/2
local x = math.clamp(lpos.x, -size2.x, size2.x);
local y = math.clamp(lpos.y, -size2.y, size2.y);
--
--local g = self.GridUnit
--if (g > 0) then
-- x = math.sign(x)*((math.abs(x) - math.abs(x) % g) + (size2.x % g))
-- y = math.sign(y)*((math.abs(y) - math.abs(y) % g) + (size2.y % g))
--end
return cf * CFrame.new(x, y, -modelSize.y/2) * CFrame.Angles(-math.pi/2, rotation, 0)
end
function Placement:CalcPlacementCFrame2(model, position, rotation)-- atempt at tilt
local cf, size = self:CalcCanvas()
local modelSize = CFrame.fromEulerAnglesYXZ(0, rotation, 0) * model.PrimaryPart.Size
modelSize = Vector3.new(math.abs(modelSize.x), math.abs(modelSize.y), math.abs(modelSize.z))
local lpos = cf:pointToObjectSpace(position);
local size2 = (size - Vector2.new(modelSize.x, modelSize.z))/2
local x = math.clamp(lpos.x, -size2.x, size2.x);
local y = math.clamp(lpos.y, -size2.y, size2.y);
--
--local g = self.GridUnit
--if (g > 0) then
-- x = math.sign(x)*((math.abs(x) - math.abs(x) % g) + (size2.x % g))
-- y = math.sign(y)*((math.abs(y) - math.abs(y) % g) + (size2.y % g))
--end
return cf * CFrame.new(x, y, -modelSize.y/2) * CFrame.Angles(rotation, -math.pi/2, 0)
end
function Placement:isColliding(model)
local isColliding = false
local touch = model.PrimaryPart.Touched:Connect(function() end)
local touching = model.PrimaryPart:GetTouchingParts()
for i = 1, #touching do
if (not touching[i]:IsDescendantOf(model)) then
--isColliding = true
break
end
end
touch:Disconnect()
return isColliding
end
function Placement:Place(model, cf, isColliding)
if (not isColliding and isServer) then
local clone = model:Clone()
clone:SetPrimaryPartCFrame(cf)
clone.Parent = self.CanvasObjects
end
if (not isServer) then
invokePlacement:FireServer("Place", model, cf, isColliding)
end
end
function Placement:Serialize()
local serial = {}
local cfi = self.CanvasPart.CFrame:inverse()
local children = self.CanvasObjects:GetChildren()
for i = 1, #children do
serial[tostring(cfi * children[i].PrimaryPart.CFrame)] = children[i].Name
end
return serial
end
function Placement:Save()
local success = dsPlacement:InvokeServer(true, true)
if (success) then
print("Saved")
end
end
function Placement:Clear()
self.CanvasObjects:ClearAllChildren()
if (not isServer) then
invokePlacement:FireServer("Clear")
local success = dsPlacement:InvokeServer(true, false)
if (success) then
print("Cleared")
end
end
end
--
return Placement
Client Script:
local canvas = game.Workspace:WaitForChild("ExampleCanvas")
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
local furniture = game:GetService("ReplicatedStorage"):WaitForChild("Furniture")
local pieces = furniture:GetChildren()
local placementClass = require(game:GetService("ReplicatedStorage"):WaitForChild("Placement"))
local placement = placementClass.fromSerialization(canvas, remotes.DSPlacement:InvokeServer(false, false))
placement.GridUnit = 2
local mouse = game.Players.LocalPlayer:GetMouse()
mouse.TargetFilter = placement.CanvasObjects
local rotation = 0
local rotation2 = 0
local modelCount = 0
local model = nil
--
local function onTilt(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
rotation2 = rotation2 + math.pi/15
end
end
local function onSwitch(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
if (model) then
model:Destroy()
end
modelCount = modelCount + 1 > #pieces and 1 or modelCount + 1
model = pieces[modelCount]:Clone()
model.Parent = placement.CanvasObjects
end
end
local function onRotate(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
rotation = rotation + math.pi/15
end
end
local function onPlace(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
local cf = placement:CalcPlacementCFrame(model, mouse.Hit.p, rotation)
placement:Place(furniture[model.Name], cf, placement:isColliding(model))
end
end
local function onClear(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
model.Parent = nil
placement:Clear()
model.Parent = mouse.TargetFilter
end
end
local function onSave(actionName, userInputState, input)
if (userInputState == Enum.UserInputState.Begin) then
placement:Save()
end
end
--
onSwitch(nil, Enum.UserInputState.Begin, nil)
game:GetService("ContextActionService"):BindAction("switch", onSwitch, false, Enum.KeyCode.E)
game:GetService("ContextActionService"):BindAction("rotate", onRotate, false, Enum.KeyCode.R)
game:GetService("ContextActionService"):BindAction("tilt", onTilt, false, Enum.KeyCode.T)-- new
game:GetService("ContextActionService"):BindAction("place", onPlace, false, Enum.UserInputType.MouseButton1)
game:GetService("ContextActionService"):BindAction("clear", onClear, false, Enum.KeyCode.C)
game:GetService("ContextActionService"):BindAction("save", onSave, false, Enum.KeyCode.F)
game:GetService("RunService").RenderStepped:Connect(function(dt)
local cf = placement:CalcPlacementCFrame(model, mouse.Hit.p, rotation2 and rotation)
model:SetPrimaryPartCFrame(cf)
end)
the script I added was only to adjust the Y level it was placing the furniture on to the Character's HumanoidRootPart Y level.
As a large portion of people probably noticed, there's a lot of stuff in there that kinda works, but for some reason I can only have rotation, or tilting, never both.
so in short, I'd like to know why binding the action doesn't work properly and how I can fix this issue.
Side note: this is not the original version, this is my highly edited version, the original version is here : (http://)https://www.google.com/url?q=https%3A%2F%2Fscriptinghelpers.org%2Fblog%2Fcreating-a-furniture-placement-system&sa=D&sntz=1&usg=AFQjCNHNt9BQ9GkLQZRcR0GzsVCxIhZdBg
