Cannot make draggable objects for placement system to be more mobile friendly

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I’m making a placement system I have already made more compatible for mobile.

  2. What is the issue? Include screenshots / videos if possible!
    I need to make it so that when on mobile, the preview object can be draggable, then they can press a speical place object button to place the object down.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I tried searching for other devforum posts, but none of them really match the same exact issue I’m trying to solve. I’ve tried making it so in the RenderStepped function, that the Mouse.Hit specifically needs to be over the preview object, in order to make it draggable, but that didn’t work either, and I’ve used ai to try and fix this issue, and I don’t really have any other ideas.

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

PlacementClient script:

-- Object placement, destruction, recolor, and general property functions
local PlacementEvent = game.ReplicatedStorage.PlacementEvent
local DestroyEvent = game.ReplicatedStorage.DestroyEvent
local RecolorEvent = game.ReplicatedStorage.RecolorEvent
local ObjectFolder = game.ReplicatedStorage:WaitForChild("ObjectFolder")
local Player = game.Players.LocalPlayer
local Mouse = Player:GetMouse()
local Frame = script.Parent:WaitForChild("ScrollingFrame")
local Destroy = script.Parent:WaitForChild("Destroy")
local Recolor = script.Parent:WaitForChild("Recolor")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local ownedProperty = nil
local PlacingObject = false
local DestroyingObject = false
local RecoloringObject = false
local RotatingObject = false
local red, green, blue = script.Parent.Red, script.Parent.Green, script.Parent.Blue

-- Check to see if the user is a mobile player, if not, then disable mobile placement guis.
if UserInputService.KeyboardEnabled and  UserInputService.MouseEnabled then
	script.Parent.PlaceObject.Visible = false
	script.Parent.RotateObject.Visible = false
else
	script.Parent.PlaceObject.Visible = true
	script.Parent.RotateObject.Visible = true
end

-- Finds the player's owned property if available
local function findOwnedProperty()
	ownedProperty = nil
	for _, propertyType in ipairs(game.Workspace.Properties:GetChildren()) do
		for _, property in ipairs(propertyType:GetChildren()) do
			local propertyInfo = property:FindFirstChild("PropertyInfo")
			if propertyInfo and propertyInfo.PropertyOwner.Value == Player then
				ownedProperty = property
				return
			end
		end
	end
end

-- Initialize owned property at the start
findOwnedProperty()

-- Handles object destruction within owned property
Destroy.MouseButton1Click:Connect(function()
	findOwnedProperty()
	if ownedProperty then
		if not PlacingObject and not DestroyingObject and not RecoloringObject then
			DestroyingObject = true
			Frame.Visible = false

			Mouse.Button1Up:Connect(function()
				if DestroyingObject then
					local target = Mouse.Target
					if target and target:IsDescendantOf(ownedProperty.PlacedItems) then
						DestroyEvent:FireServer(target:FindFirstAncestorOfClass("Model"))
					else
						warn("Target not part of owned property.")
					end
					DestroyingObject = false
					Frame.Visible = true
				end
			end)
		end
	else
		warn("No owned property found.")
	end
end)

-- Handles object recoloring within owned property
Recolor.MouseButton1Click:Connect(function()
	findOwnedProperty()
	if ownedProperty then
		if not PlacingObject and not DestroyingObject and not RecoloringObject then
			RecoloringObject = true
			Frame.Visible = false

			Mouse.Button1Up:Connect(function()
				if RecoloringObject then
					local target = Mouse.Target
					if target and target:IsDescendantOf(ownedProperty.PlacedItems) then
						RecolorEvent:FireServer(target:FindFirstAncestorOfClass("Model"), red.Text, green.Text, blue.Text)
					else
						warn("Target not part of owned property.")
					end
					RecoloringObject = false
					Frame.Visible = true
				end
			end)
		end
	else
		warn("No owned property found.")
	end
end)

-- Additional utility to check if the input is on a GUI
local function isPointOnUI(inputPosition)
	for _, uiElement in pairs(script.Parent:GetDescendants()) do
		if uiElement:IsA("GuiObject") and uiElement.Visible then
			local absolutePosition = uiElement.AbsolutePosition
			local absoluteSize = uiElement.AbsoluteSize
			local minX, maxX = absolutePosition.X, absolutePosition.X + absoluteSize.X
			local minY, maxY = absolutePosition.Y, absolutePosition.Y + absoluteSize.Y

			if inputPosition.X >= minX and inputPosition.X <= maxX and
				inputPosition.Y >= minY and inputPosition.Y <= maxY then
				return true
			end
		end
	end
	return false
end

-- Modify the button connection for placement
for _, Button in pairs(Frame:GetChildren()) do
	if Button:IsA("TextButton") then
		Button.MouseButton1Click:Connect(function()
			if not PlacingObject then
				PlacingObject = true
				Frame.Visible = false

				local RotationAmount = 0
				local PreviewObject = ObjectFolder:FindFirstChild(Button.Name):Clone()
				PreviewObject.Parent = game.Workspace

				for _, Part in pairs(PreviewObject:GetDescendants()) do
					if Part:IsA("BasePart") then
						Part.Transparency = 0.5
						Part.CanCollide = false
					end
				end

				-- Rotation control for mobile
				script.Parent.RotateObject.MouseButton1Down:Connect(function()
					RotatingObject = true
					while RotatingObject do
						wait()
						RotationAmount += 2
					end
				end)

				script.Parent.RotateObject.MouseButton1Up:Connect(function()
					RotatingObject = false
				end)

				-- Update object position and orientation during placement
				RunService.RenderStepped:Connect(function()
					if PlacingObject then
						Mouse.TargetFilter = PreviewObject
						if PreviewObject:FindFirstChild("MainPart") then
							local ObjectCFrame = CFrame.new(Mouse.Hit.Position.X, Mouse.Hit.Position.Y + PreviewObject.PrimaryPart.Size.Y / 2, Mouse.Hit.Position.Z)
							local ObjectAngles = CFrame.Angles(0, math.rad(RotationAmount), 0)
							PreviewObject:SetPrimaryPartCFrame(ObjectCFrame * ObjectAngles)
						end
					end
				end)

				-- Finalize placement for mobile
				script.Parent.PlaceObject.MouseButton1Click:Connect(function()
					if PlacingObject and not isPointOnUI(UserInputService:GetLastInput().Position) then
						PlacingObject = false
						PlacementEvent:FireServer(PreviewObject.Name, PreviewObject.PrimaryPart.CFrame)
						Frame.Visible = true
						PreviewObject:Destroy()
					end
				end)
			end
		end)
	end
end

-- Handles object placement
for _, Button in pairs(Frame:GetChildren()) do
	if Button:IsA("TextButton") then
		Button.MouseButton1Click:Connect(function()
			if not PlacingObject then
				PlacingObject = true
				Frame.Visible = false

				local RotationAmount = 0
				local PreviewObject = ObjectFolder:FindFirstChild(Button.Name):Clone()
				PreviewObject.Parent = game.Workspace

				for _, Part in pairs(PreviewObject:GetDescendants()) do
					if Part:IsA("BasePart") then
						Part.Transparency = 0.5
						Part.CanCollide = false
					end
				end

				-- Rotation control
				UserInputService.InputBegan:Connect(function(Key, GameProcessed)
					if not GameProcessed and (Key.KeyCode == Enum.KeyCode.R or Key.KeyCode == Enum.KeyCode.ButtonX) then
						RotatingObject = true
						while RotatingObject do
							wait()
							RotationAmount += 2
						end
					end
				end)
				
				--Rotation control for mobile
				script.Parent.RotateObject.MouseButton1Down:Connect(function()
					if UserInputService.TouchEnabled and not UserInputService.MouseEnabled() then
						RotatingObject = true
						while RotatingObject do
							wait()
							RotationAmount += 2
						end
					end
				end)

				UserInputService.InputEnded:Connect(function(Key)
					if Key.KeyCode == Enum.KeyCode.R then
						RotatingObject = false
					end
				end)

				-- Update object position and orientation during placement
				RunService.RenderStepped:Connect(function()
					if PlacingObject then
						Mouse.TargetFilter = PreviewObject
						if PreviewObject:FindFirstChild("MainPart") then
							local ObjectCFrame = CFrame.new(Mouse.Hit.Position.X, Mouse.Hit.Position.Y + PreviewObject.PrimaryPart.Size.Y / 2, Mouse.Hit.Position.Z)
							local ObjectAngles = CFrame.Angles(0, math.rad(RotationAmount), 0)
							PreviewObject:SetPrimaryPartCFrame(ObjectCFrame * ObjectAngles)
						end
					end
				end)

				-- Finalize placement
				Mouse.Button1Up:Connect(function()
					if PlacingObject then
						if UserInputService.KeyboardEnabled and  UserInputService.MouseEnabled then
							PlacingObject = false
							PlacementEvent:FireServer(PreviewObject.Name, PreviewObject.PrimaryPart.CFrame)
							Frame.Visible = true
							PreviewObject:Destroy()							
						end
					end
				end)
				
				--Placement Control for mobile
				script.Parent.PlaceObject.MouseButton1Click:Connect(function()
					if PlacingObject then
						if UserInputService.TouchEnabled and not UserInputService.MouseEnabled() then
							PlacingObject = false
							PlacementEvent:FireServer(PreviewObject.Name, PreviewObject.PrimaryPart.CFrame)
							Frame.Visible = true
							PreviewObject:Destroy()
						end
					end
				end)
			end
		end)
	end
end

PlacementServer script (if needed, please note that texturing is no longer being used):

local PlacementEvent = game.ReplicatedStorage.PlacementEvent
local DestroyEvent = game.ReplicatedStorage:WaitForChild("DestroyEvent")
local RecolorEvent = game.ReplicatedStorage:WaitForChild("RecolorEvent")
local TextureEvent = game.ReplicatedStorage:WaitForChild("TextureEvent")
local ObjectFolder = game.ReplicatedStorage:WaitForChild("ObjectFolder")
local placedObjects = game.Workspace:WaitForChild("PlacedObjects")



-- Utility function to check if a position is within a part's bounds
local function isWithinBounds(boundsPart, position)
	local size = boundsPart.Size
	local cf = boundsPart.CFrame
	local relativePos = cf:pointToObjectSpace(position)
	return math.abs(relativePos.X) <= size.X / 2 and
		math.abs(relativePos.Y) <= size.Y / 2 and
		math.abs(relativePos.Z) <= size.Z / 2
end

PlacementEvent.OnServerEvent:Connect(function(Player, PreviewObject, ObjectCFrame)
	-- Find the player's property by first searching the specific property types (e.g., SmallShop, LargeShop)
	local propertiesFolder = game.Workspace.Properties:GetChildren() -- Get all property type folders (SmallShop, LargeShop, etc.)
	local playerProperty = nil

	for _, propertyType in ipairs(propertiesFolder) do
		local properties = propertyType:GetChildren() -- Get all properties inside this type (e.g., SmallShop1, SmallShop2, etc.)

		for _, property in ipairs(properties) do
			local propertyInfo = property:FindFirstChild("PropertyInfo")
			if propertyInfo then
				local owner = propertyInfo:FindFirstChild("PropertyOwner")
				if owner and owner.Value == Player then
					playerProperty = property
					break
				end
			end
		end

		if playerProperty then
			break -- If we found the property, exit the loop
		end
	end

	if not playerProperty then
		warn("Player does not own a property or it could not be found!")
		return
	end

	-- Check if the placement is within the player's property bounds
	local propertyBounds = playerProperty:FindFirstChild("PropertyBounds")
	if propertyBounds and isWithinBounds(propertyBounds, ObjectCFrame.Position) then
		-- Clone and place the object
		local Object = ObjectFolder:FindFirstChild(PreviewObject):Clone()
		Object:SetPrimaryPartCFrame(ObjectCFrame)

		local placedItemsFolder = playerProperty:FindFirstChild("PlacedItems")
		if placedItemsFolder then
			Object.Parent = placedItemsFolder
			if Object:FindFirstChild("ObjectOwner") then
				if Object.ObjectOwner.Value == nil then
					Object.ObjectOwner.Value = Player
				end
			end
		else
			warn("Placed items folder not found in property!")
			return
		end

		Object.ObjectOwner.Value = Player
	else
		warn("Placement outside of property bounds!")
	end
end)

-- Handle object destruction
DestroyEvent.OnServerEvent:Connect(function(player, targetObject)
	local ownedProperty = nil

	-- Find the player's property
	for _, propertyType in ipairs(game.Workspace.Properties:GetChildren()) do
		local properties = propertyType:GetChildren()

		for _, property in ipairs(properties) do
			local propertyInfo = property:FindFirstChild("PropertyInfo")
			if propertyInfo then
				local owner = propertyInfo:FindFirstChild("PropertyOwner")
				if owner and owner.Value == player then
					ownedProperty = property
					break
				end
			end
		end

		if ownedProperty then
			break
		end
	end

	-- Check if the targetObject is part of the ownedProperty's PlacedItems
	if targetObject then --and ownedProperty then
		local placedItemsFolder = ownedProperty:FindFirstChild("PlacedItems")
		if placedItemsFolder and targetObject:IsDescendantOf(placedItemsFolder) then
			local objectOwner = targetObject:FindFirstChild("ObjectOwner")
			print(player.Name.." is player deleting!")
		--	if objectOwner and objectOwner.Value == player then
				targetObject:Destroy() -- Safe to destroy
				ownedProperty = nil
			--else
				--warn("Player tried to destroy an object they do not own.")
			--end
		else
			warn("Attempted to destroy an object not placed in owned property.")
		end
	end
end)

-- Handle object recoloring
RecolorEvent.OnServerEvent:Connect(function(player, targetObject, red, green, blue)
	local ownedProperty = nil

	-- Find the player's property
	for _, propertyType in ipairs(game.Workspace.Properties:GetChildren()) do
		local properties = propertyType:GetChildren()

		for _, property in ipairs(properties) do
			local propertyInfo = property:FindFirstChild("PropertyInfo")
			if propertyInfo then
				local owner = propertyInfo:FindFirstChild("PropertyOwner")
				if owner and owner.Value == player then
					ownedProperty = property
					break
				end
			end
		end

		if ownedProperty then
			break
		end
	end

	-- Check if the targetObject is part of the ownedProperty's PlacedItems
	if targetObject then --and ownedProperty then
		local placedItemsFolder = ownedProperty:FindFirstChild("PlacedItems")
		if placedItemsFolder and targetObject:IsDescendantOf(placedItemsFolder) then
			local objectOwner = targetObject:FindFirstChild("ObjectOwner")
			--if objectOwner and objectOwner.Value == player then
				-- Recolor logic
				local r = tonumber(red)
				local g = tonumber(green)
				local b = tonumber(blue)

				if r and g and b then
					local newColor = Color3.fromRGB(r, g, b)
					for _, part in ipairs(targetObject:GetDescendants()) do
						if part:IsA("BasePart") then
							part.BrickColor = BrickColor.new(newColor)
						end
					end
				else
					warn("Invalid RGB values provided")
				end
			else
				warn("Player tried to recolor an object they do not own.")
			end
		--else
			--warn("Attempted to recolor an object not placed in owned property.")
		----end
	end
end)
--end)

-- Handle object texturing
TextureEvent.OnServerEvent:Connect(function(player, targetObject, selectedMaterial)
	if targetObject and targetObject:IsDescendantOf(placedObjects) then
		local parentModel = targetObject:FindFirstAncestorOfClass("Model")
		local objectOwner = parentModel:WaitForChild("ObjectOwner")

		if objectOwner.Value == player then
			for _, part in ipairs(parentModel:GetDescendants()) do
				if part:IsA("Part") then
					part.Material = Enum.Material[selectedMaterial]
				end
			end
		end
	end
end)

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

I made a quick edit because I accdently copied and pasted the wrong code.

For Mobile User you could create extra button saying;

Place Object Button
Lock Object Position Button