Hello! I wanna create multiplatform top-down camera system but I encountered a problem when I wanted to use the same system o mobile as on PC.
Problem: On PC it works fine but on mobile when I start slowly, it works somehow, but when I accelerate, the whole screen starts shaking. I don’t know why this is not working.
PC:
Mobile:
Maybe Raycasts may not work well on mobile devices.
This is my module script:
Thanks for any help!
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local GuiService = game:GetService("GuiService")
--local RenderMap = require(ReplicatedStorage.ClientModules.RenderMap)
local ShopCamera = {}
ShopCamera.Connections = {}
ShopCamera.Tweens = {}
ShopCamera.zoomActive = false
ShopCamera.Running = false
ShopCamera.UpdateCamera = true
local camera = workspace.CurrentCamera
local cameraPlayerStartCFrame = nil
local mapShopFolder = workspace:WaitForChild("MapShopFolder")
local backgroundModel = mapShopFolder:WaitForChild("BackgroundModel")
local background = backgroundModel:WaitForChild("Background")
local zoom = 25
local zoomSensitivity = 5
local minZoom, maxZoom = 10, 50
local zoomValue = Instance.new("NumberValue")
zoomValue.Value = zoom
local CAMERA_ROTATION = CFrame.Angles(math.rad(-90), 0, 0)
local mousePressed = false
local mouseAnchorPoint = nil
local cameraPosition = Vector3.new(
background.Position.X,
background.Position.Y + zoomValue.Value,
background.Position.Z
)
local function IsMobile()
return UserInputService.TouchEnabled
end
local function ClampCameraPosition(pos)
local cf, size = backgroundModel:GetBoundingBox()
local halfViewZ = zoomValue.Value * math.tan(math.rad(camera.FieldOfView / 2))
local aspect = camera.ViewportSize.X / camera.ViewportSize.Y
local halfViewX = halfViewZ * aspect
local minX = cf.Position.X - size.X/2 + halfViewX
local maxX = cf.Position.X + size.X/2 - halfViewX
local minZ = cf.Position.Z - size.Z/2 + halfViewZ
local maxZ = cf.Position.Z + size.Z/2 - halfViewZ
return Vector3.new(
math.clamp(pos.X, minX, maxX),
pos.Y,
math.clamp(pos.Z, minZ, maxZ)
)
end
function ShopCamera.TweenCameraTo(targetCFrame, duration)
if not ShopCamera.Running or ShopCamera.Tweens.cameraTween then return end
local TargetCFrame =
CFrame.new(background.Position)
* targetCFrame
* CFrame.Angles(math.rad(-90), 0, 0)
ShopCamera.UpdateCamera = false
local tweenInfo = TweenInfo.new(
duration,
Enum.EasingStyle.Sine,
Enum.EasingDirection.Out
)
ShopCamera.Tweens.cameraTween = TweenService:Create(
camera,
tweenInfo,
{CFrame = TargetCFrame}
)
ShopCamera.Tweens.cameraTween:Play()
ShopCamera.Tweens.cameraTween.Completed:Connect(function()
cameraPosition = ClampCameraPosition(TargetCFrame.Position)
zoomValue.Value = TargetCFrame.Position.Y - background.Position.Y
ShopCamera.UpdateCamera = true
ShopCamera.Tweens.cameraTween = nil
end)
end
local function TweenZoom(newZoom)
if ShopCamera.Tweens.zoomTween then
ShopCamera.Tweens.zoomTween:Cancel()
end
ShopCamera.Tweens.zoomTween = TweenService:Create(
zoomValue,
TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out),
{Value = newZoom}
)
ShopCamera.Tweens.zoomTween:Play()
end
local function RaycastFromMouse(pos)
local ray = camera:ScreenPointToRay(pos.X, pos.Y)
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Include
params.FilterDescendantsInstances = {background}
local result = workspace:Raycast(ray.Origin, ray.Direction * 1000, params)
return result
end
local function Start()
--RenderMap.Render()
local touchesPositions = {}
local lastTouchPosition = nil
local zoomLastDistance = nil
cameraPlayerStartCFrame = camera.CFrame
camera.CameraType = Enum.CameraType.Scriptable
camera.CFrame = CFrame.new(cameraPosition) * CAMERA_ROTATION
if IsMobile() then
GuiService.TouchControlsEnabled = false
ShopCamera.Connections.TouchStartedConnection = UserInputService.TouchStarted:Connect(function(touch)
touchesPositions[touch] = touch.Position
local result = RaycastFromMouse(touch.Position)
if result then
mouseAnchorPoint = result.Position
end
end)
ShopCamera.Connections.TouchMovedConnection = UserInputService.TouchMoved:Connect(function(touch)
if not ShopCamera.UpdateCamera then return end
touchesPositions[touch] = touch.Position
local touchesCount = 0
for _ in pairs(touchesPositions) do touchesCount += 1 end
if touchesCount == 1 and not ShopCamera.zoomActive then
local result = RaycastFromMouse(touch.Position)
if result and mouseAnchorPoint then
local currentPoint = result.Position
local delta = mouseAnchorPoint - currentPoint
cameraPosition = ClampCameraPosition(Vector3.new(
cameraPosition.X + delta.X,
background.Position.Y + zoomValue.Value,
cameraPosition.Z + delta.Z
))
end
end
if touchesCount == 2 then
ShopCamera.zoomActive = true
local touches = {}
for _, pos in pairs(touchesPositions) do
table.insert(touches, pos)
end
local zoomCurrentDistance = (touches[1] - touches[2]).Magnitude
if zoomLastDistance then
local delta = zoomCurrentDistance - zoomLastDistance
local newZoom = zoomValue.Value - delta * zoomSensitivity
newZoom = math.clamp(newZoom, minZoom, maxZoom)
TweenZoom(newZoom)
end
zoomLastDistance = zoomCurrentDistance
end
end)
ShopCamera.Connections.TouchEndedConnection = UserInputService.TouchEnded:Connect(function(touch)
touchesPositions[touch] = nil
local touchesCount = 0
for _ in pairs(touchesPositions) do touchesCount += 1 end
if touchesCount < 2 then
zoomLastDistance = nil
end
if touchesCount == 0 then
mouseAnchorPoint = nil
ShopCamera.zoomActive = false
end
touchesCount = 0
end)
else
-- PC --
ShopCamera.Connections.InputBeganConnection =
UserInputService.InputBegan:Connect(function(input, gp)
if gp or not ShopCamera.UpdateCamera then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
mousePressed = true
local mousePos = UserInputService:GetMouseLocation()
local result = RaycastFromMouse(mousePos)
if result then
mouseAnchorPoint = result.Position
end
end
end)
ShopCamera.Connections.InputEndedConnection =
UserInputService.InputEnded:Connect(function(input, gp)
if gp or not ShopCamera.UpdateCamera then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
mousePressed = false
mouseAnchorPoint = nil
end
end)
ShopCamera.Connections.InputChangedConnection =
UserInputService.InputChanged:Connect(function(input, gp)
if gp or not ShopCamera.UpdateCamera then return end
if input.UserInputType == Enum.UserInputType.MouseWheel then
local newZoom = zoomValue.Value + (-input.Position.Z * zoomSensitivity)
newZoom = math.clamp(newZoom, minZoom, maxZoom)
TweenZoom(newZoom)
end
end)
end
ShopCamera.Connections.RenderSteppedConnection =
RunService.RenderStepped:Connect(function(dt)
if not ShopCamera.UpdateCamera then return end
if mousePressed and mouseAnchorPoint then
local mousePos = UserInputService:GetMouseLocation()
local result = RaycastFromMouse(mousePos)
if result then
local currentPoint = result.Position
local delta = mouseAnchorPoint - currentPoint
cameraPosition = ClampCameraPosition(Vector3.new(
cameraPosition.X + delta.X,
background.Position.Y + zoomValue.Value,
cameraPosition.Z + delta.Z
))
end
end
local clamped = ClampCameraPosition(cameraPosition)
camera.CFrame = CFrame.new(
clamped.X,
background.Position.Y + zoomValue.Value,
clamped.Z
) * CAMERA_ROTATION
end)
end
local function Stop()
for _, c in pairs(ShopCamera.Connections) do
c:Disconnect()
end
for _, t in pairs(ShopCamera.Tweens) do
t:Cancel()
t = nil
end
--RenderMap.Close()
ShopCamera.Connections = {}
mousePressed = false
mouseAnchorPoint = nil
camera.CFrame = cameraPlayerStartCFrame
camera.CameraType = Enum.CameraType.Custom
GuiService.TouchControlsEnabled = true
end
function ShopCamera.Toggle()
ShopCamera.Running = not ShopCamera.Running
if ShopCamera.Running then Start() else Stop() end
return ShopCamera.Running
end
return ShopCamera
