I’m making a Placement System. I already made it for desktop, the problem is that I don’t know how to make it adapt to mobile. I’ve been thinking about using some already invented code and implementing it, but I don’t know if that would be the most efficient, can you help me??
Here is the .rblx:
BuildSystem.rbxl (83.5 KB)
And this is the main ModuleScript that control the Placement:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local boxOutlineTemplate = ReplicatedStorage.BoxOutline
local placeableObjects = ReplicatedStorage:WaitForChild("PlaceableObjects")
local tryPlace = ReplicatedStorage:WaitForChild("TryPlace")
local tryDelete = ReplicatedStorage:WaitForChild("TryDelete")
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local RunService = game:GetService("RunService")
local camera = workspace.CurrentCamera
local PlacementValidator = require(ReplicatedStorage.PlacementValidator)
local PREVIEW_RENDER = "RenderPreview"
local ROTATE_ACTION = "Rotate"
local DELETE_ACTION = "Delete"
local SNAP_ACTION = "Snap"
-- Variable to store the last touch position
local lastTouchPosition = nil
-- Function to snap position to grid based on given size
local function snapToGrid(pos, gridSize)
return Vector3.new(
math.round(pos.X / gridSize) * gridSize,
pos.Y,
math.round(pos.Z / gridSize) * gridSize
)
end
-- Function to cast a ray from a touch position on the screen and get the 3D position
local function castTouch(touchPosition)
local rayOrigin = camera.CFrame.Position
local rayDirection = (camera:ScreenPointToRay(touchPosition.X, touchPosition.Y)).Direction * 1000
return workspace:Raycast(rayOrigin, rayDirection)
end
local ClientPlacer = {}
ClientPlacer.__index = ClientPlacer
function ClientPlacer.new(plot: Model, objectName: string)
local self = setmetatable({
Plot = plot,
Preview = nil,
GridSize = 0.5,
Rotation = 0,
SelectedObjectName = objectName,
}, ClientPlacer)
self:InitiateRenderPreview()
-- Detect touches to place objects and store the position
UserInputService.TouchTapInWorld:Connect(function(touchPosition)
lastTouchPosition = touchPosition
self:TryPlaceBlock(touchPosition)
end)
-- Rotate the object on double tap
UserInputService.TouchTap:Connect(function(_, touchPositions)
if #touchPositions == 2 then
self:RotateBlock()
end
end)
return self
end
-- Initializes the preview of the selected object for placement
function ClientPlacer:InitiateRenderPreview()
local model = placeableObjects:FindFirstChild(self.SelectedObjectName)
if model then
self:PreparePreviewModel(model)
RunService:BindToRenderStep(PREVIEW_RENDER, Enum.RenderPriority.Camera.Value, function() self:RenderPreview() end)
else
warn("Object not found in PlaceableObjects: " .. tostring(self.SelectedObjectName))
end
end
-- Updates the preview model with a new selected object
function ClientPlacer:SetSelectedObject(newObjectName)
self.SelectedObjectName = newObjectName
local model = placeableObjects:FindFirstChild(newObjectName)
if model then
self:PreparePreviewModel(model)
else
warn("Selected object not found in PlaceableObjects: " .. tostring(newObjectName))
end
end
-- Prepares the preview model with necessary settings
function ClientPlacer:PreparePreviewModel(model: Model)
if self.Preview then
self.Preview:Destroy()
end
self.Preview = model:Clone()
local boxOutline = boxOutlineTemplate:Clone()
boxOutline.Adornee = self.Preview
boxOutline.Parent = self.Preview
for _, part in self.Preview:GetDescendants() do
if part:IsA("BasePart") then
part.CanCollide = false
part.CanQuery = false
part.Transparency = 0.5
end
end
self.Preview.Parent = workspace
end
-- Renders the placement preview at the touch position, snapping to grid if enabled
function ClientPlacer:RenderPreview()
if lastTouchPosition then
local cast = castTouch(lastTouchPosition)
if cast and cast.Position then
local position = if self.GridSize > 0 then snapToGrid(cast.Position, self.GridSize) else cast.Position
local cf = CFrame.new(position) * CFrame.Angles(0, self.Rotation, 0)
self.Preview:PivotTo(cf)
local size = self.Preview:GetExtentsSize()
self.Preview.BoxOutline.Color3 =
if PlacementValidator.WithinBounds(self.Plot, size, cf)
and PlacementValidator.NotIntersectingObjects(self.Plot, size, cf)
then Color3.new(0, 0.666667, 1)
else Color3.new(1, 0, 0)
end
end
end
-- Attempts to place the preview object on the plot
function ClientPlacer:TryPlaceBlock(touchPosition)
local cast = castTouch(touchPosition)
if cast and cast.Position then
local position = if self.GridSize > 0 then snapToGrid(cast.Position, self.GridSize) else cast.Position
local cf = CFrame.new(position) * CFrame.Angles(0, self.Rotation, 0)
-- Calls the server to place the object at the desired position
local success = tryPlace:InvokeServer(self.Preview.Name, cf)
if success then
self:SetSelectedObject(self.SelectedObjectName) -- Resets the preview for the next object
else
warn("Placement failed")
end
end
end
-- Rotates the preview object 90 degrees
function ClientPlacer:RotateBlock()
self.Rotation += math.pi / 2 -- 90 degrees
end
-- Cleans up and disables actions when placement mode is deactivated
function ClientPlacer:Destroy()
if self.Preview then
self.Preview:Destroy()
end
RunService:UnbindFromRenderStep(PREVIEW_RENDER)
end
return ClientPlacer