Hello! I’m currently trying to make placement system for my Roblox game, however, I’ve ran through an issue, I wan’t the block to be rotated using the Pivot system, like, the previewPart would rotate based on the Pivot’s position, so anyone can help?
Here’s my client-script:
-- Fun fact: You can declare multiple variables in one line like this:
-- WaitForChild is used here because it takes time to replicate objects to client and this function waits for object with certain name to be on the client
local players, contextActionService, replicatedStorage = game:GetService("Players"), game:GetService("ContextActionService"), game:GetService("ReplicatedStorage")
local localPlayer = players.LocalPlayer
local ignoreForMouse, buildingModel = workspace.__PLOTS__:WaitForChild(localPlayer.Name.."'s PLOT"):WaitForChild("IgnoreForMouse"), workspace.__PLOTS__:WaitForChild(localPlayer.Name.."'s PLOT"):WaitForChild("BuildingModel")
local previewPart = localPlayer:WaitForChild("PREVIEW_PART")
local mouse = localPlayer:GetMouse()
local tweenservice = game:GetService("TweenService")
local tool = script.Parent
local remoteFunction = tool:WaitForChild("RemoteFunction")
local canUse, isEquipped, isBuillding, targetToDestroy, orginalColor = true, false, true, nil, nil
local selectionBox, baseplate = previewPart.Value:WaitForChild("SelectionBox"), workspace.__PLOTS__:WaitForChild(localPlayer.Name.."'s PLOT"):WaitForChild("__BASEPLATE__")
local BuildingUi = tool.__BUILDING_UI__
local O_M_O_E = localPlayer.O_M_O_E
local rotation = tool.__ROTATION__
local character = localPlayer.Character
if not character or not character.Parent then character = localPlayer.CharacterAdded:Wait() end
local humanoid = character:WaitForChild("Humanoid")
local function calculateOffset(positionNumber) -- It is used for math to calculate block position
if positionNumber * 10 % 3 == 1 then
return -1
elseif positionNumber * 10 % 3 == 2 then
return 1
else
return 0
end
end
local function UI_ANIMATION_1()
local __1__ = tweenservice:Create(BuildingUi.__BACKGROUND__,TweenInfo.new(1,Enum.EasingStyle.Elastic),{Position = UDim2.new(0.5,0,0.5,0)});
__1__:Play();
local __2__ = tweenservice:Create(workspace.CurrentCamera,TweenInfo.new(1,Enum.EasingStyle.Elastic),{FieldOfView = 50});
__2__:Play();
local __3__ = tweenservice:Create(game.Lighting._UI_BLUR,TweenInfo.new(1,Enum.EasingStyle.Elastic),{Size = 14});
__3__:Play();
end
local function UI_ANIMATION_2()
local __1__ = tweenservice:Create(BuildingUi.__BACKGROUND__,TweenInfo.new(1,Enum.EasingStyle.Elastic),{Position = UDim2.new(0.5,0,1.4,0)});
__1__:Play();
local __2__ = tweenservice:Create(workspace.CurrentCamera,TweenInfo.new(1,Enum.EasingStyle.Elastic),{FieldOfView = 70});
__2__:Play();
local __3__ = tweenservice:Create(game.Lighting._UI_BLUR,TweenInfo.new(1,Enum.EasingStyle.Elastic),{Size = 0});
__3__:Play();
local __4__ = tweenservice:Create(BuildingUi.__BOTTOM__,TweenInfo.new(1,Enum.EasingStyle.Elastic),{Position = UDim2.new(0.5,0,1.4,0)});
__4__:Play();
end
local function UI_ANIMATION_3()
local __1__ = tweenservice:Create(BuildingUi.__BOTTOM__,TweenInfo.new(1,Enum.EasingStyle.Elastic),{Position = UDim2.new(0.5,0,0.762,0)});
__1__:Play();
end
local function toggleMode(actionName, inputState, inputObject) -- Toggled modes (building / destroying)
if inputState == Enum.UserInputState.Begin and canUse then -- It activates every time when a buttons state is changing so it is important to check the state of the button. Here, the two conditions can be true only if a player started holding RMB (it doesn't matter for how long)
isBuillding = not isBuillding
end
end
selectionBox.Color3 = Color3.fromRGB(0, 255, 0)
mouse.TargetFilter = ignoreForMouse -- Player's mouse should ignore the preview part and pretend that it doesn't exist
game:GetService("RunService").RenderStepped:Connect(function() -- This event fires "for every frame generated by a client". The definition from Roblox API reference is: Fires every frame prior to the frame being rendered
local mouseTarget, mouseHit, mouseSurface = mouse.Target, mouse.Hit, mouse.TargetSurface
if mouseTarget and isEquipped and humanoid.Health ~= 0 then -- Player's mouse should point at something, player's character should have the tool equipped and the character should be not dead
if isBuillding then -- If using building mode, there nothing to request to destroy
targetToDestroy = nil -- /\
if mouseTarget == baseplate then -- Trying to place a block on the Baseplate
-- That's the whole math for grid system for this building system. Get a piece of paper and something to write and do the math by yourself if you struggle to understand it
local x,z = math.floor(mouseHit.X)+0.5, math.floor(mouseHit.Z)+0.5
local blockSizeX = previewPart.Value.Size.X
local blockSizeZ = previewPart.Value.Size.Z
x = x - ((blockSizeX % 2 == 0) and 0.5 or 0) -- If the blockSize is even then subtract 0.5, if not then subtract 0
z = z - ((blockSizeZ % 2 == 0) and 0.5 or 0) -- Do the same for the Z axis.
previewPart.Value.CFrame = CFrame.new(
x+calculateOffset(x),
(mouseTarget.Size.Y + previewPart.Value.Size.Y) / 2 + mouseTarget.Position.Y,
z+calculateOffset(z)
) * CFrame.Angles(0,math.rad(tool.__ROTATION__.Value),0)
previewPart.Value.Parent = ignoreForMouse -- Ready to place a block
elseif mouseTarget.Parent == buildingModel then
if mouseSurface == Enum.NormalId.Back then -- The preview block should move away from an actual block in a different direction depending on a pointed surface
previewPart.Value.CFrame = mouseTarget.CFrame:ToWorldSpace(CFrame.new(0, 0, mouseTarget.Size.Z)) -- This is less complex math, just moving the preview block far enough from an actual block
elseif mouseSurface == Enum.NormalId.Bottom then
previewPart.Value.CFrame = mouseTarget.CFrame:ToWorldSpace(CFrame.new(0, -mouseTarget.Size.Y, 0))
elseif mouseSurface == Enum.NormalId.Front then
previewPart.Value.CFrame = mouseTarget.CFrame:ToWorldSpace(CFrame.new(0, 0, -mouseTarget.Size.Z))
elseif mouseSurface == Enum.NormalId.Left then
previewPart.Value.CFrame = mouseTarget.CFrame:ToWorldSpace(CFrame.new(-mouseTarget.Size.X, 0, 0))
elseif mouseSurface == Enum.NormalId.Right then
previewPart.Value.CFrame = mouseTarget.CFrame:ToWorldSpace(CFrame.new(mouseTarget.Size.X, 0, 0))
elseif mouseSurface == Enum.NormalId.Top then
local x,z = math.floor(mouseHit.X)+0.5, math.floor(mouseHit.Z)+0.5
local blockSizeX = previewPart.Value.Size.X
local blockSizeZ = previewPart.Value.Size.Z
x = x - ((blockSizeX % 2 == 0) and 0.5 or 0) -- If the blockSize is even then subtract 0.5, if not then subtract 0
z = z - ((blockSizeZ % 2 == 0) and 0.5 or 0) -- Do the same for the Z axis.
previewPart.Value.CFrame = CFrame.new(
x+calculateOffset(x),
(mouseTarget.Size.Y + previewPart.Value.Size.Y) / 2 + mouseTarget.Position.Y,
z+calculateOffset(z)
) * CFrame.Angles(0,math.rad(tool.__ROTATION__.Value),0)
end
previewPart.Value.Parent = ignoreForMouse -- Ready to place a block
else
previewPart.Value.Parent = replicatedStorage.__PREVIEW_PARTS__ -- Move the preview part back to replicated storage because not enough conditions were met to place place or destroy block preview (an example: trying to place a block 500 blocks away while max distance is smaller than 500)
end
elseif mouseTarget and mouseTarget.Parent == buildingModel then -- Player wants to destory a block
targetToDestroy = mouseTarget
previewPart.Value.CFrame = mouseTarget.CFrame * CFrame.Angles(0,math.rad(tool.__ROTATION__.Value),0) -- Move the preview block to a potential block to destroy position
previewPart.Value.Parent = ignoreForMouse
else
previewPart.Value.Parent = replicatedStorage.__PREVIEW_PARTS__
end
end
end)
tool.Equipped:Connect(function() -- Set isEquipped boolean to true if the tool is equipped
isEquipped = true
BuildingUi.Parent = localPlayer.PlayerGui
BuildingUi.Enabled = true
if O_M_O_E.Value == true then
UI_ANIMATION_1()
elseif O_M_O_E.Value == false then
UI_ANIMATION_3()
end
end)
tool.Unequipped:Connect(function() -- Set isEquipped boolean to false if the tool is unequpped
isEquipped = false
previewPart.Value.Parent = replicatedStorage.__PREVIEW_PARTS__
BuildingUi.Parent = tool
UI_ANIMATION_2()
BuildingUi.Enabled = false
end)
humanoid.Died:Connect(function() -- Doing some clearing so the building system will work after respawn
previewPart.Value.Parent = replicatedStorage.__PREVIEW_PARTS__
selectionBox.Color3 = Color3.fromRGB(0, 255, 0)
end)
tool.Activated:Connect(function()
if canUse and previewPart.Value.Parent == ignoreForMouse and humanoid.Health ~= 0 then -- Check if no block is being destroyed or placed by this tool, if preview part in visible and if the player's character is alive (health not equal to 0)
canUse = false -- It will prevent from spamming blocks
orginalColor = selectionBox.Color3
selectionBox.Color3 = Color3.fromRGB(0, 255, 0)
remoteFunction:InvokeServer(previewPart.Value.CFrame,targetToDestroy) -- This event will "freeze" here until placing or destroying a block is completed
workspace.__SFX__._PLACE_:Play()
previewPart.Value.Parent = replicatedStorage.__PREVIEW_PARTS__ -- Preview at a destroyed block position is not needed
canUse = true -- After waiting to place or destroy a block, player can use this tool again
selectionBox.Color3 = Color3.fromRGB(0, 255, 0)
end
end)