Por que os sistemas de câmeras personalizados são importantes
A câmera padrão do GTA V funciona bem para jogos em geral, mas servidores de RPG e modos de jogo personalizados geralmente precisam de um comportamento de câmera especializado. As telas de criação de personagens requerem câmeras orbitais que permitem aos jogadores girar em torno de seu personagem. As cenas precisam de caminhos de câmera programados com transições suaves. Os sistemas CCTV precisam de visualizações de vigilância fixas. As visualizações de propriedades precisam de câmeras flythrough. Construir um sistema de câmera flexível que lide com todos esses casos de uso melhorará drasticamente a qualidade de apresentação do seu servidor. Este tutorial aborda detalhadamente a API da câmera FiveM com exemplos do mundo real que você pode usar imediatamente.
Fundamentos da câmera
As câmeras FiveM usam as funções nativas CreateCam e CreateCamWithParams. Uma câmera é uma entidade com posição, rotação e campo de visão. Você pode criar múltiplas câmeras e alternar entre elas ou interpolar suavemente de uma posição de câmera para outra. O conceito principal é que a renderização por meio de uma câmera de script desativa a câmera normal do jogo, então você precisa lidar com a transição de volta com cuidado.
-- client/camera_core.lua
local CameraSystem = {
activeCam = nil,
isActive = false,
}
function CameraSystem.Create(coords, rot, fov)
local cam = CreateCamWithParams(
'DEFAULT_SCRIPTED_CAMERA',
coords.x, coords.y, coords.z,
rot.x, rot.y, rot.z,
fov or 60.0,
false, 0
)
return cam
end
function CameraSystem.Activate(cam, transitionTime)
transitionTime = transitionTime or 1000
SetCamActive(cam, true)
RenderScriptCams(true, true, transitionTime, true, false)
CameraSystem.activeCam = cam
CameraSystem.isActive = true
end
function CameraSystem.Deactivate(transitionTime)
transitionTime = transitionTime or 1000
RenderScriptCams(false, true, transitionTime, true, false)
if CameraSystem.activeCam then
SetCamActive(CameraSystem.activeCam, false)
DestroyCam(CameraSystem.activeCam, false)
CameraSystem.activeCam = nil
end
CameraSystem.isActive = false
end
-- Clean up on resource stop
AddEventHandler('onResourceStop', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
if CameraSystem.isActive then
CameraSystem.Deactivate(0)
end
end)
Câmera orbital para criação de personagens
Uma câmera orbital gira em torno de um ponto central, permitindo que o jogador gire a visualização arrastando o mouse. Esta é a câmera padrão para criação de personagens, lojas de roupas e barbearias. A câmera mantém uma distância fixa do alvo e converte o movimento do mouse em rotação angular em torno do ponto central.
-- client/orbit_camera.lua
local OrbitCam = {
active = false,
cam = nil,
target = nil,
distance = 2.0,
angleH = 0.0,
angleV = 20.0,
minV = -30.0,
maxV = 60.0,
sensitivity = 0.3,
fov = 45.0,
}
function OrbitCam.Start(targetEntity, distance, height)
OrbitCam.target = targetEntity
OrbitCam.distance = distance or 2.0
OrbitCam.angleH = GetEntityHeading(targetEntity) + 180.0
OrbitCam.angleV = 20.0
local targetCoords = GetEntityCoords(targetEntity)
local camPos = OrbitCam.CalculatePosition(targetCoords)
OrbitCam.cam = CreateCamWithParams(
'DEFAULT_SCRIPTED_CAMERA',
camPos.x, camPos.y, camPos.z,
0.0, 0.0, 0.0,
OrbitCam.fov, false, 0
)
PointCamAtCoord(OrbitCam.cam, targetCoords.x, targetCoords.y, targetCoords.z + 0.5)
SetCamActive(OrbitCam.cam, true)
RenderScriptCams(true, true, 800, true, false)
OrbitCam.active = true
OrbitCam.UpdateLoop()
end
function OrbitCam.CalculatePosition(center)
local hRad = math.rad(OrbitCam.angleH)
local vRad = math.rad(OrbitCam.angleV)
local x = center.x + OrbitCam.distance * math.cos(vRad) * math.sin(hRad)
local y = center.y + OrbitCam.distance * math.cos(vRad) * math.cos(hRad)
local z = center.z + 0.5 + OrbitCam.distance * math.sin(vRad)
return vector3(x, y, z)
end
function OrbitCam.UpdateLoop()
CreateThread(function()
while OrbitCam.active do
DisableAllControlActions(0)
EnableControlAction(0, 1, true) -- Mouse X
EnableControlAction(0, 2, true) -- Mouse Y
EnableControlAction(0, 241, true) -- Scroll Up
EnableControlAction(0, 242, true) -- Scroll Down
-- Mouse rotation
local mouseX = GetDisabledControlNormal(0, 1) * OrbitCam.sensitivity * 8.0
local mouseY = GetDisabledControlNormal(0, 2) * OrbitCam.sensitivity * 8.0
OrbitCam.angleH = OrbitCam.angleH - mouseX
OrbitCam.angleV = math.max(OrbitCam.minV,
math.min(OrbitCam.maxV, OrbitCam.angleV + mouseY))
-- Scroll zoom
if IsDisabledControlPressed(0, 241) then
OrbitCam.distance = math.max(0.5, OrbitCam.distance - 0.1)
elseif IsDisabledControlPressed(0, 242) then
OrbitCam.distance = math.min(5.0, OrbitCam.distance + 0.1)
end
local targetCoords = GetEntityCoords(OrbitCam.target)
local camPos = OrbitCam.CalculatePosition(targetCoords)
SetCamCoord(OrbitCam.cam, camPos.x, camPos.y, camPos.z)
PointCamAtCoord(OrbitCam.cam, targetCoords.x, targetCoords.y, targetCoords.z + 0.5)
Wait(0)
end
end)
end
function OrbitCam.Stop()
OrbitCam.active = false
RenderScriptCams(false, true, 800, true, false)
if OrbitCam.cam then
SetCamActive(OrbitCam.cam, false)
DestroyCam(OrbitCam.cam, false)
OrbitCam.cam = nil
end
end
Interpolação e transições de câmera
Transições suaves de câmera entre duas posições criam efeitos cinematográficos para cenas, tours por propriedades e telas de carregamento. FiveM fornece SetCamActiveWithInterp que lida com a interpolação nativamente, mas você também pode criar funções de atenuação personalizadas para obter mais controle sobre a curva de transição.
-- client/camera_transition.lua
local function TransitionCamera(fromPos, fromRot, toPos, toRot, duration, fov)
fov = fov or 50.0
local camFrom = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA',
fromPos.x, fromPos.y, fromPos.z,
fromRot.x, fromRot.y, fromRot.z,
fov, false, 0)
local camTo = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA',
toPos.x, toPos.y, toPos.z,
toRot.x, toRot.y, toRot.z,
fov, false, 0)
SetCamActive(camFrom, true)
RenderScriptCams(true, false, 0, true, false)
-- Interpolate from first camera to second
SetCamActiveWithInterp(camTo, camFrom, duration, 1, 1)
Wait(duration)
-- Clean up the first camera
DestroyCam(camFrom, false)
return camTo
end
-- Usage: Flythrough preview of a property
local function PropertyPreview(waypoints, duration)
local perSegment = duration / (#waypoints - 1)
local currentCam = nil
for i = 1, #waypoints - 1 do
local wp = waypoints[i]
local nextWp = waypoints[i + 1]
currentCam = TransitionCamera(
wp.pos, wp.rot,
nextWp.pos, nextWp.rot,
perSegment, wp.fov or 60.0
)
end
-- Return to gameplay camera
Wait(500)
RenderScriptCams(false, true, 1000, true, false)
if currentCam then DestroyCam(currentCam, false) end
end
Sistema de câmeras de vigilância CFTV
Um sistema CCTV usa câmeras fixas posicionadas ao redor do mapa pelas quais os jogadores podem navegar. Cada câmera possui posição e rotação predefinidas, e o sistema aplica um efeito de pós-processamento para simular a aparência das imagens de segurança. Isso é comumente usado em delegacias de polícia e interiores de empresas.
-- client/cctv.lua
local CCTV = {
cameras = {},
currentIndex = 0,
activeCam = nil,
active = false,
}
function CCTV.AddCamera(name, coords, rot, fov)
table.insert(CCTV.cameras, {
name = name,
coords = coords,
rot = rot,
fov = fov or 60.0,
})
end
function CCTV.Start(startIndex)
CCTV.currentIndex = startIndex or 1
CCTV.active = true
CCTV.SwitchTo(CCTV.currentIndex)
CreateThread(function()
while CCTV.active do
-- Apply security camera filter
SetTimecycleModifier('CAMERA_secuirity')
SetTimecycleModifierStrength(1.0)
DisableAllControlActions(0)
EnableControlAction(0, 174, true) -- Left Arrow
EnableControlAction(0, 175, true) -- Right Arrow
EnableControlAction(0, 202, true) -- Escape
-- Cycle cameras
if IsDisabledControlJustPressed(0, 175) then
local next = CCTV.currentIndex + 1
if next > #CCTV.cameras then next = 1 end
CCTV.SwitchTo(next)
elseif IsDisabledControlJustPressed(0, 174) then
local prev = CCTV.currentIndex - 1
if prev < 1 then prev = #CCTV.cameras end
CCTV.SwitchTo(prev)
elseif IsDisabledControlJustPressed(0, 202) then
CCTV.Stop()
end
Wait(0)
end
end)
end
function CCTV.SwitchTo(index)
local data = CCTV.cameras[index]
if not data then return end
local newCam = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA',
data.coords.x, data.coords.y, data.coords.z,
data.rot.x, data.rot.y, data.rot.z,
data.fov, false, 0)
if CCTV.activeCam then
SetCamActiveWithInterp(newCam, CCTV.activeCam, 500, 1, 1)
Wait(500)
DestroyCam(CCTV.activeCam, false)
else
SetCamActive(newCam, true)
RenderScriptCams(true, true, 500, true, false)
end
CCTV.activeCam = newCam
CCTV.currentIndex = index
end
function CCTV.Stop()
CCTV.active = false
ClearTimecycleModifier()
RenderScriptCams(false, true, 800, true, false)
if CCTV.activeCam then
DestroyCam(CCTV.activeCam, false)
CCTV.activeCam = nil
end
end
Práticas recomendadas para sistemas de câmera
- Sempre destrua as câmeras quando terminar. Câmeras não destruídas são um vazamento de memória. Rastreie cada alça de câmera e limpe-as em
onResourceStop. - Desative os controles durante as sequências de câmera. Os jogadores não devem poder andar, atirar ou interagir enquanto uma câmera com script estiver ativa, a menos que seja permitido intencionalmente.
- Use valores de FOV apropriados. O jogo normal usa 50-60 FOV. As fotos cinematográficas usam 30-40 FOV para uma aparência telefoto. Planos de estabelecimento amplos usam 70-90 FOV.
- Oculte o HUD durante sequências de câmera. Use
DisplayHud(false)eDisplayRadar(false)para remover elementos de jogo durante câmeras cinematográficas. - Teste transições em diferentes taxas de quadros. A interpolação da câmera pode parecer diferente em 30 fps versus 144 fps. Teste em hardware de baixo custo para garantir um comportamento suave.
