Hello Developers.
I’m currently experiencing an issue where my preview placement part keeps clipping with the placed part. I don’t want the preview part to be like this:
Right side
Left side
Front side
Back side
I tried to read this article but I’m clueless…
My current code:
local Players = game:GetService('Players')
local TweenService = game:GetService('TweenService')
local RunService = game:GetService('RunService')
local Packages = script.Parent.Parent.Packages
local Janitor = require(Packages.Janitor)
local Signal = require(Packages.DissatisfiedPSignal)
local Spring = require(Packages.Spring)
local EventsManager = require(script.Parent.Parent.Parent.Shared.Managers.EventsManager)
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character:FindFirstChild('HumanoidRootPart') or Character.PrimaryPart or Character:WaitForChild('HumanoidRootPart')
local Mouse = Player:GetMouse()
local Camera = workspace.CurrentCamera
local CurrentActiveClass = nil
export type PlacementData = {
Object: Instance,
PrimaryPart: BasePart?,
IsPlacing: boolean,
SnapGridEnabled: boolean,
ValidPlacementColor: Color3,
InvalidPlacementColor: Color3,
PlacementRange: number,
Rotation: number,
SnapGrid: number,
ObjectLastHalfYSize: number,
CurrentRotation: number,
ObjectLastPosition: Vector3,
ObjectLastSize: Vector3 | number,
Cleaner: typeof(Janitor.new()),
ObjectMoved: typeof(Signal.new()),
ObjectPlaced: typeof(Signal.new()),
PlacingStateChanged: typeof(Signal.new()),
PlacementDestroying: typeof(Signal.new()),
SetPlacingEnabled: (self: PlacementData, Value: boolean) -> (),
SetSnapGridEnabled: (self: PlacementData, Value: boolean) -> (),
ResetRotation: (self: PlacementData) -> (),
Place: (self: PlacementData) -> (),
Rotate: (self: PlacementData, CustomInfo: TweenInfo?) -> (),
Destroy: (self: PlacementData) -> (),
}
local function CheckAndDeepFill<T>(GivenTbl: T, Source: T): T
for Key, Value in Source do
if typeof(Value) == 'table' then
if typeof(GivenTbl[Key]) ~= 'table' then
GivenTbl[Key] = {}
end
CheckAndDeepFill(GivenTbl[Key], Value)
else
if GivenTbl[Key] == nil or typeof(GivenTbl[Key]) ~= typeof(Value) then
GivenTbl[Key] = Value
end
end
end
return GivenTbl
end
local function Snap(Value: number, Grid: number)
return math.round(Value / Grid) * Grid
end
local MouseRaycastParams = RaycastParams.new()
MouseRaycastParams.FilterType = Enum.RaycastFilterType.Include
MouseRaycastParams.FilterDescendantsInstances = {workspace.PlacePlot, workspace.Placements}
local PlacementOverlapParams = OverlapParams.new()
PlacementOverlapParams.FilterType = Enum.RaycastFilterType.Include
PlacementOverlapParams.FilterDescendantsInstances = {workspace.Placements}
local Highlighter = Instance.new('Highlight')
Highlighter.Name = 'PlacementOutline'
Highlighter.OutlineTransparency = 0
Highlighter.FillTransparency = .75
Highlighter.FillColor = Color3.fromRGB(255, 0, 0)
Highlighter.OutlineColor = Color3.fromRGB(255, 0, 0)
Highlighter.DepthMode = Enum.HighlightDepthMode.Occluded
local Placement = {}
Placement.__index = Placement
if RunService:IsServer() then
-- For auto-complete purpose.
Placement = nil
return Placement
end
-- Create new placement with given data(s).
function Placement.NewPlacement(Data: PlacementData) : PlacementData
Data = Data or {}
if typeof(Data.Object) ~= 'Instance' then
warn(`Cannot found object or object is invalid in given placement data!`)
return
end
local self = setmetatable(CheckAndDeepFill(Data, {
IsPlacing = false,
SnapGridEnabled = false,
ValidPlacementColor = Color3.fromRGB(0, 255, 0),
InvalidPlacementColor = Color3.fromRGB(255, 0, 0),
PlacementRange = 100,
Rotation = 15,
SnapGrid = 1,
CurrentRotation = 0,
}), Placement)
local Cleaner = Janitor.new()
local function FastCreateSignal() return Cleaner:Add(Signal.new(), 'DisconnectAll') end
self.Cleaner = Cleaner
self.Object = Cleaner:Add(Data.Object:Clone())
self.ObjectMoved = FastCreateSignal()
self.ObjectPlaced = FastCreateSignal()
self.PlacingStateChanged = FastCreateSignal()
self.PlacementDestroying = FastCreateSignal()
self.Object.Parent = nil
if self.Object:IsA('Model') then
self.ObjectLastHalfYSize = self.Object:GetExtentsSize().Y / (self.Object:GetExtentsSize().Y * 2)
self.ObjectLastSize = self.Object:GetScale()
self.ObjectLastPosition = self.Object:GetPivot().Position
for _, Obj in self.Object:GetDescendants() do
if self.Object.PrimaryPart and Obj:IsA('BasePart') then
Obj.CanCollide = false
Obj.Anchored = Obj == self.Object.PrimaryPart
elseif not self.Object.PrimaryPart and Obj:IsA('BasePart') then
Obj.CanCollide = false
Obj.Anchored = true
end
end
self.PrimaryPart = self.Object.PrimaryPart
elseif self.Object:IsA('BasePart') then
self.ObjectLastHalfYSize = self.Object.Size.Y / 2
self.ObjectLastSize = self.Object.Size
self.ObjectLastPosition = self.Object.Position
self.Object.CanCollide = false
self.Object.Anchored = true
end
return self
end
-- Toggle placing on & off.
function Placement.SetPlacingEnabled(self: PlacementData, Value: boolean)
self.IsPlacing = Value
local UnitRay = Camera:ScreenPointToRay(Mouse.X, Mouse.Y)
local MouseCastResult = workspace:Raycast(UnitRay.Origin, UnitRay.Direction * math.clamp(self.PlacementRange, 100, math.huge), MouseRaycastParams)
if Value then
if MouseCastResult then
local X = MouseCastResult.Position.X
local Y = MouseCastResult.Position.Y + self.ObjectLastHalfYSize
local Z = MouseCastResult.Position.Z
local CurrentRotation = self.CurrentRotation
if self.Object:IsA('BasePart') then
self.Object.CFrame = CFrame.new(Vector3.new(X, Y, Z)) * CFrame.Angles(0, math.rad(CurrentRotation), 0)
self.Object.Size *= Vector3.new(.25, .25, .25)
Spring.target(self.Object, .5, 6, {
Size = self.ObjectLastSize,
})
elseif self.Object:IsA('Model') then
self.Object:ScaleTo(self.Object:GetScale() * .25)
local NumberValue = Instance.new('NumberValue')
NumberValue.Value = self.Object:GetScale()
NumberValue.Changed:Connect(function(NewValue: number)
self.Object:ScaleTo(NewValue)
end)
Spring.target(NumberValue, .5, 6, {
Value = self.ObjectLastSize,
})
Spring.completed(NumberValue, function()
NumberValue:Destroy()
end)
self.Object:PivotTo(CFrame.new(X, Y, Z) * CFrame.Angles(0, math.rad(CurrentRotation), 0))
end
end
CurrentActiveClass = self
Highlighter.Parent = self.Object
self.Object.Parent = workspace
else
Spring.stop(self.Object)
CurrentActiveClass = nil
Highlighter.Parent = nil
self.CurrentRotation = 0
self.Object.Parent = nil
end
end
-- Toggle snap grid on & off.
function Placement.SetSnapGridEnabled(self: PlacementData, Value: boolean)
self.SnapGridEnabled = Value
end
-- Place the object down.
function Placement.Place(self: PlacementData)
self.ObjectPlaced:Fire()
-- Test
if self.Object:IsA('Model') then
EventsManager.PlaceObject:Fire('TestModel', {
CFrame = self.Object:GetPivot(),
Parent = workspace.Placements
})
elseif self.Object:IsA('BasePart') then
EventsManager.PlaceObject:Fire('TestPart', {
CFrame = self.Object.CFrame,
Parent = workspace.Placements
})
end
end
function Placement.ResetRotation(self: PlacementData)
if not self.IsPlacing then return end
self.CurrentRotation = 0
end
-- Rotate the object.
function Placement.Rotate(self: PlacementData, CustomInfo: TweenInfo?)
local Goal = {}
self.CurrentRotation += self.Rotation
if self.SnapGridEnabled then
self.CurrentRotation = Snap(self.CurrentRotation, self.Rotation)
end
if self.CurrentRotation > 180 then
self.CurrentRotation -= 360
elseif self.CurrentRotation < -180 then
self.CurrentRotation += 360
end
if self.Object:IsA('Model') then
Goal.CFrame = self.Object:GetPivot() * CFrame.Angles(0, math.rad(self.Rotation), 0)
elseif self.Object:IsA('BasePart') then
Goal.CFrame = self.Object.CFrame * CFrame.Angles(0, math.rad(self.Rotation), 0)
end
end
-- Destroy placement class and make it cannot be use anymore.
function Placement.Destroy(self: PlacementData)
self.PlacementDestroying:Fire()
if CurrentActiveClass == self then
CurrentActiveClass = nil
end
Highlighter.Parent = nil
self.Cleaner:Destroy()
end
RunService.PreSimulation:Connect(function()
if CurrentActiveClass then
local UnitRay = Camera:ScreenPointToRay(Mouse.X, Mouse.Y)
local MouseCastResult = workspace:Raycast(UnitRay.Origin, UnitRay.Direction * 100000, MouseRaycastParams)
if not MouseCastResult or not MouseCastResult.Instance or (HumanoidRootPart.Position - MouseCastResult.Position).Magnitude >= CurrentActiveClass.PlacementRange then return end
local X = MouseCastResult.Position.X
local Y = MouseCastResult.Position.Y + CurrentActiveClass.ObjectLastHalfYSize
local Z = MouseCastResult.Position.Z
if CurrentActiveClass.SnapGridEnabled then
X = Snap(X, CurrentActiveClass.SnapGrid)
Z = Snap(Z, CurrentActiveClass.SnapGrid)
end
local CurrentRotation = CurrentActiveClass.CurrentRotation
if CurrentActiveClass.Object:IsA('BasePart') then
TweenService:Create(CurrentActiveClass.Object, TweenInfo.new(.06), {CFrame = CFrame.new(X, Y, Z) * CFrame.Angles(0, math.rad(CurrentRotation), 0)}):Play()
elseif CurrentActiveClass.Object:IsA('Model') then
if CurrentActiveClass.PrimaryPart then
TweenService:Create(CurrentActiveClass.PrimaryPart, TweenInfo.new(.06), {CFrame = CFrame.new(X, Y, Z) * CFrame.Angles(0, math.rad(CurrentRotation), 0)}):Play()
else
CurrentActiveClass.Object:PivotTo(CFrame.new(X, Y, Z) * CFrame.Angles(0, math.rad(CurrentRotation), 0))
end
end
end
end)
return Placement




