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
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)
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.
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.
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)
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)
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.
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.
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)