العودة إلى المدونة
Tutorial5 دقيقة قراءة

نظام الكاميرا في FiveM: كاميرات مخصصة، CCTV وسينمائية

اتقن برمجة كاميرا FiveM. كاميرا حرة، CCTV للشرطة، لقطات سينمائية وواجهات كاميرا مخصصة مع كود عملي وأفضل الموارد.

Agency Scripts

المؤسس والمطور الرئيسي في Agency Scripts

لماذا تهم أنظمة الكاميرا المخصصة

الكاميرا الافتراضية في GTA تعمل جيدًا للعب العام، لكن خوادم اللعب التمثيلي وأنماط اللعبة المخصصة غالبًا ما تحتاج إلى سلوك كاميرا متخصص. تتطلب شاشات إنشاء الشخصيات كاميرات دوران تسمح للاعبين بالتدوير حول شخصياتهم. تحتاج المشاهد السينمائية إلى مسارات كاميرا مبرمجة مع انتقالات سلسة. تحتاج أنظمة المراقبة CCTV إلى زوايا مراقبة ثابتة. تحتاج معاينات العقارات إلى كاميرات طيران. بناء نظام كاميرا مرن يتعامل مع كل هذه الاستخدامات سيحسن بشكل كبير جودة العرض في خادمك. يغطي هذا الدرس واجهة برمجة تطبيقات كاميرا FiveM بعمق مع أمثلة واقعية يمكنك استخدامها فورًا.

أساسيات الكاميرا

كاميرات FiveM تستخدم الأصلية CreateCam و CreateCamWithParams functions. الكاميرا هي كيان له موقع، دوران، ومجال رؤية. يمكنك إنشاء عدة كاميرات والتبديل بينها، أو التداخل بسلاسة من موقع كاميرا إلى آخر. المفهوم الأساسي هو أن العرض من خلال كاميرا سكريبت يعطل كاميرا اللعب العادية، لذا تحتاج إلى التعامل مع الانتقال بعناية للعودة.

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

كاميرا المدار لإنشاء الشخصية

كاميرا تدور حول نقطة مركزية، تتيح للاعب تدوير العرض بسحب الماوس. هذه هي الكاميرا القياسية لإنشاء الشخصيات، متاجر الملابس، وصالونات الحلاقة. تحافظ الكاميرا على مسافة ثابتة من الهدف وتحول حركة الماوس إلى دوران زاوي حول النقطة المركزية.

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

التداخل والانتقالات في الكاميرا

الانتقالات السلسة للكاميرا بين موضعين تخلق تأثيرات سينمائية للمشاهد المقطوعة، جولات الممتلكات، وشاشات التحميل. FiveM يوفر SetCamActiveWithInterp الذي يتعامل مع الاستيفاء محليًا، لكن يمكنك أيضًا بناء دوال تيسير مخصصة لمزيد من التحكم في منحنى الانتقال.

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

نظام كاميرات المراقبة CCTV

يستخدم نظام CCTV كاميرات ثابتة موضوعة حول الخريطة يمكن للاعبين التنقل بينها. كل كاميرا لها موقع ودوران محددان مسبقًا، ويطبق النظام تأثير معالجة لاحقة لمحاكاة مظهر لقطات الأمان. يُستخدم هذا عادة في مراكز الشرطة وداخل الأعمال.

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

أفضل ممارسات نظام الكاميرا

  • دمر دائمًا الكاميرات عند الانتهاء. الكاميرات غير المدمرة تسبب تسرب في الذاكرة. تتبع كل مقبض كاميرا وقم بتنظيفها في onResourceStop.
  • تعطيل التحكمات أثناء تسلسلات الكاميرا. يجب ألا يتمكن اللاعبون من المشي، أو إطلاق النار، أو التفاعل أثناء تفعيل كاميرا سكربتية إلا إذا سُمح بذلك عمدًا.
  • استخدم قيم FOV المناسبة. اللعب العادي يستخدم مجال رؤية 50-60. اللقطات السينمائية تستخدم مجال رؤية 30-40 لمظهر تليفوتوغرافي. اللقطات الافتتاحية الواسعة تستخدم مجال رؤية 70-90.
  • إخفاء HUD أثناء تسلسلات الكاميرا. استخدم DisplayHud(false) و DisplayRadar(false) لإزالة عناصر اللعب أثناء الكاميرات السينمائية.
  • اختبر الانتقالات عند معدلات إطارات مختلفة. يمكن أن يبدو التداخل في الكاميرا مختلفًا عند 30fps مقابل 144fps. اختبر على أجهزة منخفضة الأداء لضمان سلاسة السلوك.

جاهز للبدء؟

احصل على السكربتات من متجرنا، أو انضم إلى Discord للدعم والتحديثات ونظرة على ما هو قادم.