Top Down Camera Trigger Script Lagging

Hello, I’m working on a top down trigger script that changes the players camera to top down (looking down from above them) once they’re in the trigger part. I’ve tried this with many different methods all day, and this is the closest I could get to what I want, while not conflicting with my other trigger scripts. It uses Touched Event which I know is unreliable, but I tried Region3, and that makes it lag even more. Anyone have suggestions? more info on request.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("CameraChangeEvent")

local triggerPart = script.Parent

-- Define camera settings for this trigger
local triggerCameraSettings = {
    position = Vector3.new(0, 20, 10), -- Adjust the height as needed
    orientation = Vector3.new(-60, 0, 0),
}

-- Function to update the camera position
local function updateCamera(player)
    local character = player.Character
    local humanoid = character and character:FindFirstChildOfClass("Humanoid")

    if humanoid then
        local newPosition = humanoid.Parent.HumanoidRootPart.Position + triggerCameraSettings.position
        remoteEvent:FireClient(player, {position = newPosition, orientation = triggerCameraSettings.orientation})
    end
end

triggerPart.Touched:Connect(function(otherPart)
    local player = Players:GetPlayerFromCharacter(otherPart.Parent)

    if player then
        updateCamera(player)

        -- Connect the function to the player's character's position change
        local character = player.Character
        local humanoid = character and character:FindFirstChildOfClass("Humanoid")

        if humanoid then
            humanoid.Parent.HumanoidRootPart:GetPropertyChangedSignal("CFrame"):Connect(function()
                updateCamera(player)
            end)
        end
    end
end)

Edit: Also, the top down camera doesn’t follow the player like I’d like it to do, it just updates the players camera position onTouch :frowning:

2 Likes
  1. You get a “touched” property that sets to true on Touched and false on TouchEnded.
  2. Every time “touched” is true, update the camera.
  3. If you want to, you can set fixed Y axis for the camera so it doesn’t move up when jumping.

I think that’s why it doesn’t work.

2 Likes

Try this:
image

CameraScript (LocalScript)

You don’t have to use my UpdateCamera function btw

local PAM = require(script:WaitForChild("PlayerAreaModule"))

local player = game.Players.LocalPlayer
local exampleArea = workspace:WaitForChild("ExampleArea")

local function UpdateCamera(state: string, focusPart)
	local camera = workspace.CurrentCamera
	if state == "Top" and focusPart then
		camera.CameraType = Enum.CameraType.Scriptable
		local focusCF = focusPart.CFrame
		camera.CFrame =
			focusCF *
			CFrame.Angles(math.rad(-60), 0, 0) +
			focusCF.UpVector * 20 -
			focusCF.LookVector * 10
	elseif state == "Default" then
		camera.CameraType = Enum.CameraType.Custom
	else
		error("Invalid arguments")
	end
end

PAM.PlayerEnteredArea(player, exampleArea).Event:Connect(function()
	while PAM.IsPlayerInArea(player, exampleArea) do
		UpdateCamera(
			"Top",
			player.character:WaitForChild("HumanoidRootPart")
		)
		task.wait()
	end
	UpdateCamera("Default")
end)
PlayerAreaModule (ModuleScript)
local module = {}

local function GetPartsInArea(area: BasePart)
	local overlapExclusions = OverlapParams.new()
	overlapExclusions.FilterDescendantsInstances = {
		area-- , <Other parts may go here if desired>
	}
	return workspace:GetPartBoundsInBox(
		area.CFrame,
		area.Size,
		overlapExclusions
	)
end

function module.IsPlayerInArea(player: Player, area: BasePart)
	for _, part in GetPartsInArea(area) do
		if game.Players:GetPlayerFromCharacter(part.Parent) == player then
			return true
		end
	end
	return false
end

function module.PlayerEnteredArea(player: Player, area: BasePart)
	local event = Instance.new("BindableEvent")
	task.spawn(function()
		while player do
			if module.IsPlayerInArea(player, area) then
				event:Fire()
				repeat task.wait()
				until not module.IsPlayerInArea(player, area)
			end
			task.wait()
		end
		event:Destroy()
	end)
	return event
end

return module

Or you can download the scripts at once in this file:
CameraScript.rbxm (2.3 KB)

1 Like

I need the camera not to rotate with the player, but also have a fixed orientation, much like in this script:

local player = game.Players.LocalPlayer
local camera = workspace.CurrentCamera

player.CharacterAdded:Wait()
player.Character:WaitForChild("HumanoidRootPart")

camera.CameraSubject = player.Character.HumanoidRootPart
camera.CameraType = Enum.CameraType.Attach
camera.FieldOfView = 90

local RunService = game:GetService("RunService")

local function onUpdate()
	if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
		camera.CFrame = CFrame.new(player.Character.HumanoidRootPart.Position) * CFrame.new(0,25,15) * CFrame.Angles(math.rad(-60), 0, 0) --The first two numbers are Height(25) and distance(15). The third is the angle(-60), the lower this variable is the more the camera will face the ground 

	end
end
 
RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value, onUpdate)

Whenever I try to combine this script with any of my trigger ones, it never works because this is a Local script I believe. I need it in the Server (Trigger) script.

I’m trying to get the camera in the middle of this courtyard to behave the same way, but just more efficiently:

Where’s the LocalScript? We need the client code to help. Region3 is also deprecated, so don’t use it.

1 Like

This place is private by the way.

Apologies, should be public now. Here is the Local Script:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local cameraChangeEvent = ReplicatedStorage:WaitForChild("CameraChangeEvent")
local CurrentCamera = workspace.CurrentCamera

cameraChangeEvent.OnClientEvent:Connect(function(cameraSettings)
    local character = player.Character or player.CharacterAdded:Wait()
    local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

    local newCameraPosition = cameraSettings.position
    local newCameraOrientation = cameraSettings.orientation

    -- Create a CFrame using the position and orientation
    local newCameraCFrame = CFrame.new(newCameraPosition) * CFrame.Angles(math.rad(newCameraOrientation.X), math.rad(newCameraOrientation.Y), math.rad(newCameraOrientation.Z))

    CurrentCamera.CameraType = Enum.CameraType.Scriptable
    CurrentCamera.CFrame = newCameraCFrame
end)

Is this RemoteEvent solely for the purpose of doing the top-down view? I’m asking because I’m wondering whether I can change how it works without affecting other features.

1 Like

I’ve tried to adapt the code to that. Try this:

local PAM = require(script:WaitForChild("PlayerAreaModule"))

local player, camera = game.Players.LocalPlayer, workspace.CurrentCamera
local exampleArea = workspace:WaitForChild("ExampleArea")

local function UpdateCamera()
	local character = player.Character
	local rootPart = (function()
		if character then
			return character:FindFirstChild("HumanoidRootPart")
		end
	end)()
	if rootPart then
		camera.CameraSubject = rootPart
		camera.CameraType = Enum.CameraType.Attach
		camera.FieldOfView = 90

		local function OnUpdate()
			if character and rootPart then
				camera.CFrame = CFrame.new(rootPart.Position) *
					CFrame.new(0, 25, 15) *
					CFrame.Angles(math.rad(-60), 0, 0) 
			end
		end

		game:GetService("RunService"):BindToRenderStep(
			"Camera",
			Enum.RenderPriority.Camera.Value,
			OnUpdate
		)
	end
end

PAM.PlayerEnteredArea(player, exampleArea).Event:Connect(UpdateCamera)
1 Like

It is not, I have Fixed Camera Angles that use it too. I tried creating separate one for Top Down view but I failed miserably at that.

Could you change your server script to this? You can’t connect :GetPropertyChangedSignal to properties related to physics (like CFrame, Position, etc.)

Code:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("CameraChangeEvent")

local triggerPart = script.Parent

local playersTouching = {}

-- Define camera settings for this trigger
local triggerCameraSettings = {
	position = Vector3.new(0, 20, 10), -- Adjust the height as needed
	orientation = Vector3.new(-60, 0, 0),
}

-- Function to update the camera position
local function updateCamera(player)
	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")

	if humanoid then
		local newPosition = humanoid.Parent.HumanoidRootPart.Position + triggerCameraSettings.position
		remoteEvent:FireClient(player, {position = newPosition, orientation = triggerCameraSettings.orientation})
	end
end

triggerPart.Touched:Connect(function(otherPart)
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)

	if player and not table.find(playersTouching, player) then
		table.insert(playersTouching, player)
		
		while task.wait() and table.find(playersTouching, player) do
			updateCamera(player)
		end
	end
end)

triggerPart.TouchEnded:Connect(function(otherPart)
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)
	
	if not player then
		return
	end
	
	local playerIndex = table.find(playersTouching, player)
	
	if playerIndex then
		table.remove(playersTouching, playerIndex)
	end
end)
2 Likes

My bad… I could, but do I change all server scripts to this, or just the one that’s mean’t to trigger the top down camera?

The one that’s meant to trigger the top-down camera. Isn’t that what we’re fixing?

1 Like

Ok, it works perfectly, once again, thank you so much!

1 Like

It isn’t very performant to use loops like that on the server though, so if it’s possible, you should try to make a remote event that triggers the top-down camera so you can do it purely on the client. I can tell you how to do that if you’re interested.

1 Like

Sorry, only saw this now. I’m about to go to sleep, but yes please! I can try implement it in the morning. Thanks for all this help!

Alright.

Make a new RemoteEvent called TopDownEvent and put it in ReplicatedStorage.

Change your server script to this:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("TopDownEvent")

local triggerPart = script.Parent

local playersTouching = {}

-- Define camera settings for this trigger
local triggerCameraSettings = {
	position = Vector3.new(0, 20, 10), -- Adjust the height as needed
	orientation = CFrame.Angles(math.rad(-60), 0, 0),
}

triggerPart.Touched:Connect(function(otherPart)
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)

	if player and not table.find(playersTouching, player) then
		table.insert(playersTouching, player)
		
		remoteEvent:FireClient(player, triggerCameraSettings)
	end
end)

triggerPart.TouchEnded:Connect(function(otherPart)
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)

	if not player then
		return
	end

	local playerIndex = table.find(playersTouching, player)

	if playerIndex then
		table.remove(playersTouching, playerIndex)
		
		remoteEvent:FireClient(player)
	end
end)

Add a new LocalScript in StarterPlayerScripts and paste this inside:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer
local cameraChangeEvent = ReplicatedStorage:WaitForChild("TopDownEvent")
local CurrentCamera = workspace.CurrentCamera

cameraChangeEvent.OnClientEvent:Connect(function(cameraSettings)
	if cameraSettings then		
		local character = player.Character or player.CharacterAdded:Wait()
		local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

		local newCameraOrientation = cameraSettings.orientation
		
		RunService:BindToRenderStep("TopDownCamera", Enum.RenderPriority.Camera.Value + 1, function()
			local newCameraPosition = humanoidRootPart.Position + cameraSettings.position

			-- Create a CFrame using the position and orientation
			local newCameraCFrame = CFrame.new(newCameraPosition) * newCameraOrientation

			CurrentCamera.CameraType = Enum.CameraType.Scriptable
			CurrentCamera.CFrame = newCameraCFrame
		end)
	else
		RunService:UnbindFromRenderStep("TopDownCamera")
	end
end)

In the previous system, both the client and server had to work, because the server had to fire a RemoteEvent every frame, and the client had to receive that RemoteEvent every frame.

Now, only the client has to do something every frame.

2 Likes

The camera won’t follow the player around though, and only triggers once for some reason…

Alright, scrap what I put earlier.

Remove your server script and put just a LocalScript in StarterPlayerScripts.

In that LocalScript, paste this inside:

local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer
local triggerPart = workspace.MyCoolPart --//Reference your part

local CurrentCamera = workspace.CurrentCamera

local isTouching = false

-- Define camera settings for this trigger
local triggerCameraSettings = {
	position = Vector3.new(0, 20, 10), -- Adjust the height as needed
	orientation = Vector3.new(-60, 0, 0),
}

-- Function to update the camera position
local function updateCamera()
	local character = LocalPlayer.Character
	local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")

	if humanoidRootPart then		
		local newPosition = humanoidRootPart.Position + triggerCameraSettings.position

		local newCameraPosition = newPosition
		local newCameraOrientation = triggerCameraSettings.orientation

		-- Create a CFrame using the position and orientation
		local newCameraCFrame = CFrame.new(newCameraPosition) * CFrame.Angles(math.rad(newCameraOrientation.X), math.rad(newCameraOrientation.Y), math.rad(newCameraOrientation.Z))

		CurrentCamera.CameraType = Enum.CameraType.Scriptable
		CurrentCamera.CFrame = newCameraCFrame
	end
end

triggerPart.Touched:Connect(function(otherPart)	
	if otherPart.Parent == LocalPlayer.Character then
		isTouching = true
		
		while task.wait() and isTouching do
			updateCamera()
		end
	end
end)

triggerPart.TouchEnded:Connect(function(otherPart)
	if otherPart.Parent == LocalPlayer.Character then
		isTouching = false
	end
end)

Make sure to change the variable TriggerPart.

If that works, you could also try this variation which I assume would be smoother (only reason I didn’t put it first was because it might not work):

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer
local triggerPart = workspace.MyCoolPart --//Reference your part

local CurrentCamera = workspace.CurrentCamera

-- Define camera settings for this trigger
local triggerCameraSettings = {
	position = Vector3.new(0, 20, 10), -- Adjust the height as needed
	orientation = Vector3.new(-60, 0, 0),
}

-- Function to update the camera position
local function updateCamera()
	local character = LocalPlayer.Character
	local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")

	if humanoidRootPart then		
		local newCameraPosition = humanoidRootPart.Position + triggerCameraSettings.position
		local newCameraOrientation = triggerCameraSettings.orientation

		-- Create a CFrame using the position and orientation
		local newCameraCFrame = CFrame.new(newCameraPosition) * CFrame.Angles(math.rad(newCameraOrientation.X), math.rad(newCameraOrientation.Y), math.rad(newCameraOrientation.Z))

		CurrentCamera.CameraType = Enum.CameraType.Scriptable
		CurrentCamera.CFrame = newCameraCFrame
	end
end

triggerPart.Touched:Connect(function(otherPart)	
	if otherPart.Parent == LocalPlayer.Character then		
		RunService:BindToRenderStep("TopDownCamera", Enum.RenderPriority.Camera.Value + 1, updateCamera)
	end
end)

triggerPart.TouchEnded:Connect(function(otherPart)
	if otherPart.Parent == LocalPlayer.Character then
		RunService:UnbindFromRenderStep("TopDownCamera")
	end
end)
1 Like