返回博客
Tutorial3 分钟阅读

FiveM新闻记者系统:脚本直播电视广播

为FiveM添加一个完整的新闻记者职业。直播摄像头、广播UI、提示以及推动全城角色扮演剧情的最佳新闻脚本。

Agency Scripts

Agency Scripts 创始人兼首席开发者

角色扮演中新闻记者的角色

新闻记者职位为您的 FiveM 服务器增添了独特的社交层面,这是其他职位无法提供的。记者作为服务器事件之间的连接纽带,记录犯罪、采访玩家并播报塑造您城市叙事的故事。与大多数孤立运作的职位不同,新闻报道自然与服务器上的每个派系交汇:警察追逐成为突发新闻,帮派冲突成为调查报道,市议会决策成为社论文章。这创造了有机的跨派系角色扮演,服务器所有者常常试图通过机制鼓励但很少实现。一个良好的新闻系统为记者提供捕捉事件、撰写所有玩家可见文章以及在地图各处屏幕上直播报道的工具。当玩家看到自己的行为成为头条新闻时,会强化他们的角色扮演重要性,并鼓励他们创造更引人入胜的故事。

新闻台设置与职位结构

新闻站是记者签到、访问设备、撰写文章和团队协调的中心。在配置中定义站点位置、设备存储和广播工作室。构建职位层级,如实习生、记者、主播和总编辑,每个职位权限递增。实习生可写草稿但需编辑批准后发布。记者可发布文章并使用摄像设备。主播可访问广播工作室进行直播。总编辑管理团队并控制滚动在所有玩家屏幕上的新闻条:

Config.NewsStation = {
    label = 'Weazel News',
    jobName = 'reporter',
    location = vector3(-598.97, -929.54, 23.89),
    studioRoom = vector3(-592.12, -924.88, 23.89),
    equipmentLocker = vector3(-601.33, -932.17, 23.89),
    grades = {
        [0] = { name = 'Intern',   canPublish = false, canBroadcast = false },
        [1] = { name = 'Reporter', canPublish = true,  canBroadcast = false },
        [2] = { name = 'Anchor',   canPublish = true,  canBroadcast = true  },
        [3] = { name = 'Editor',   canPublish = true,  canBroadcast = true,
                canManage = true },
    },
    equipment = {
        { item = 'news_camera',     label = 'News Camera' },
        { item = 'news_microphone', label = 'Microphone' },
        { item = 'press_badge',     label = 'Press Badge' },
        { item = 'news_notepad',    label = 'Notepad' },
    },
    vehicles = {
        { model = 'rumpo',  label = 'News Van',    grade = 1 },
        { model = 'frogger', label = 'News Chopper', grade = 2 },
    },
}

新闻车是移动广播站的关键。车顶配备卫星天线道具,允许记者在靠近车辆的任何地点开始直播。直升机为主播提供空中拍摄能力,适用于警方追捕或全城紧急事件等大型活动。每辆车应按职位等级限制使用,防止实习生第一天就驾驶直升机。

摄像头和录制系统

摄像机系统是让新闻报道感觉真实的核心机制。当记者装备新闻摄像机物品时,切换到第一人称摄像机视角,并显示带有红色REC指示器、计时器和电池电量的录制覆盖层。摄像机应支持使用滚轮输入进行缩放控制和照片拍摄模式,拍摄游戏内截图并作为文章附件存储。使用GTA的原生摄像机系统创建平滑的手持摄像机效果,带有轻微的移动摆动,使画面看起来自然,而非完全静止:

local isRecording = false
local recordingCam = nil
local recordTimer = 0

function StartCamera()
    if isRecording then return end
    isRecording = true

    local playerPed = PlayerPedId()
    recordingCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)

    -- Position camera at player's head height
    local headBone = GetPedBoneCoords(playerPed, 31086, 0.0, 0.0, 0.0)
    SetCamCoord(recordingCam, headBone.x, headBone.y, headBone.z)
    SetCamRot(recordingCam, GetEntityRotation(playerPed, 2))
    SetCamFov(recordingCam, 50.0)
    SetCamActive(recordingCam, true)
    RenderScriptCams(true, true, 500, true, false)

    -- Show recording overlay via NUI
    SendNUIMessage({
        action = 'showRecordingOverlay',
        stationName = 'WEAZEL NEWS',
        reporterName = GetPlayerName(PlayerId())
    })

    -- Camera control thread
    CreateThread(function()
        local currentFov = 50.0
        while isRecording do
            -- Follow player head position
            local head = GetPedBoneCoords(playerPed, 31086, 0.0, 0.0, 0.0)
            SetCamCoord(recordingCam, head.x, head.y, head.z)

            -- Mouse look controls
            local pitch = GetDisabledControlNormal(0, 2) * -5.0
            local yaw = GetDisabledControlNormal(0, 1) * -8.0
            local rot = GetCamRot(recordingCam, 2)
            SetCamRot(recordingCam, rot.x + pitch, rot.y, rot.z + yaw, 2)

            -- Zoom with scroll wheel
            if IsDisabledControlPressed(0, 241) then
                currentFov = math.max(15.0, currentFov - 2.0)
            elseif IsDisabledControlPressed(0, 242) then
                currentFov = math.min(80.0, currentFov + 2.0)
            end
            SetCamFov(recordingCam, currentFov)

            -- Update timer
            recordTimer = recordTimer + 1
            SendNUIMessage({
                action = 'updateTimer',
                time = math.floor(recordTimer / 60)
            })

            DisableAllControlActions(0)
            EnableControlAction(0, 249, true) -- Allow push to talk
            Wait(0)
        end
    end)
end

通过NUI显示的录制覆盖应复制专业新闻摄像机取景器,角落有框架,带有电台标志水印和滚动的下三分之一条显示记者姓名。记者停止录制时,覆盖淡出并平滑过渡回常规游戏摄像机。存储录制时长和位置数据,以便撰写相关报道时引用。

文章撰写与发布

为记者提供完整的文章编辑器,可从新闻站或其现场的记事本物品访问。编辑器应使用基于 NUI 的富文本界面,记者输入标题,撰写文章正文,选择犯罪、政治、体育或娱乐等类别,并附加用摄像系统拍摄的照片。发布的文章出现在游戏内报纸中,可从城市各处的报纸箱道具访问,若服务器使用电话系统,也可从电话应用访问。文章存储在数据库中,包含时间戳、作者信息和浏览次数,以跟踪哪些故事产生最多玩家参与:

RegisterNetEvent('news:server:publishArticle', function(articleData)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)

    -- Verify job and permissions
    if Player.PlayerData.job.name ~= 'reporter' then return end
    local grade = Config.NewsStation.grades[Player.PlayerData.job.grade.level]
    if not grade or not grade.canPublish then
        TriggerClientEvent('QBCore:Notify', src,
            'Your rank cannot publish articles', 'error')
        return
    end

    -- Sanitize input
    local headline = articleData.headline:sub(1, 120)
    local body = articleData.body:sub(1, 5000)
    local category = articleData.category or 'general'

    -- Insert into database
    local articleId = MySQL.insert.await(
        'INSERT INTO news_articles (author_id, author_name, headline, body, category, created_at) VALUES (?, ?, ?, ?, ?, NOW())',
        {Player.PlayerData.citizenid, Player.PlayerData.charinfo.firstname ..
         ' ' .. Player.PlayerData.charinfo.lastname,
         headline, body, category}
    )

    -- Broadcast notification to all players
    TriggerClientEvent('news:client:newArticle', -1, {
        id = articleId,
        headline = headline,
        category = category,
        author = Player.PlayerData.charinfo.firstname .. ' ' ..
                 Player.PlayerData.charinfo.lastname
    })

    -- Update news ticker
    TriggerClientEvent('news:client:updateTicker', -1,
        'BREAKING: ' .. headline)

    TriggerClientEvent('QBCore:Notify', src,
        'Article published successfully!', 'success')
end)

直播广播系统

实时直播是新闻记者系统中最激动人心的功能,因为它们创造了服务器范围内的事件,所有玩家都可以实时见证。当主播从演播室或新闻车附近开始直播时,他们通过语音聊天的音频应在可配置范围内或根据您的偏好在整个服务器范围内传递给每个玩家。在每个玩家的屏幕上显示广播横幅,显示电台标志、主播姓名和直播指示器。广播可以选择在地图内置的室内游戏电视屏幕上使用渲染目标显示摄像头画面。实现广播冷却时间以防止刷屏,并要求主播角色以防止每个实习生第一天就直播。广播系统应优雅地处理边缘情况:如果主播在直播中断开连接,自动结束广播并显示技术故障信息。如果主播在现场广播期间离新闻车太远,警告他们信号正在丢失,如果他们未在合理距离内返回,则结束广播。

新闻跑马灯和通知

新闻条是持续存在的UI元素,在每位玩家屏幕底部滚动头条,类似有线新闻频道。将其实现为NUI层,读取最新发布文章并定时轮换头条。允许总编辑推送紧急突发新闻,覆盖常规新闻条,显示红色高亮横幅和警报音。玩家可自行关闭新闻条,避免干扰,并通过手机或设置菜单切换开关。服务器端限制新闻条更新频率防止滥用,新闻条队列保留最近五条头条,避免玩家被旧新闻淹没。玩家点击新闻条头条时,在NUI报纸查看器中打开完整文章,无需访问实体报纸箱即可阅读完整故事。

采访和新闻发布会机制

为采访添加专用机制,超越普通对话。当记者使用麦克风道具锁定另一玩家时,发送采访请求,目标玩家可接受或拒绝。接受采访后创建可见的新闻互动,双方面对面,记者持麦克风道具,附近玩家头顶显示新闻标识。采访期间的所有对话可选地记录为附加在记者笔记本上的文字稿,便于后续撰写文章。对于新闻发布会,在市政厅、警察局和医院创建指定讲台位置,官员可同时向多名记者发表讲话。讲台位置增强发言者的声音范围,并在所有附近玩家可见的下三分之一图形中显示其姓名和职务,营造真实新闻发布会氛围,促进政治和机构角色扮演。

性能与集成技巧

新闻系统通过新闻条和广播功能触及服务器上的每位玩家,因此性能优化至关重要。将新闻条作为轻量级NUI元素运行,长时间间隔更新而非每帧更新。客户端缓存已发布文章,仅在玩家打开报纸查看器或收到新文章通知时请求更新。直播广播通过现有语音聊天系统传输音频,避免创建独立音频通道以减少带宽开销。数据库中存储文章数量有限,三十天以上的条目归档至单独表,玩家可通过报纸档案菜单访问。记者职位变更、资源重启或玩家断开连接时,积极清理摄像实体和NUI覆盖,防止残留视觉痕迹。考虑与手机系统集成,使玩家可直接从手机阅读新闻文章,订阅特定类别,并接收感兴趣领域的突发新闻推送。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。