Назад к блогу
Tutorial7 мин чтения

Синхронизация погоды FiveM: Скрипты динамической погоды и времени

Синхронизируйте погоду и время на вашем сервере FiveM. Динамические циклы, гром, снег, сезонные паки и лучшие скрипты погоды для создания атмосферы.

Agency Scripts

Основатель и ведущий разработчик Agency Scripts

Проблема с рассинхронизацией погоды

По умолчанию GTA V обрабатывает погоду и время независимо на каждом клиенте. Это означает, что один игрок может ехать под ярким солнцем, в то время как другой игрок рядом с ним видит грозу. Для ролевых серверов это полностью разрушает погружение. Представьте, что пытаетесь скоординировать сцену, где персонаж жалуется на дождь, а у другого игрока на экране ясное небо. Синхронизированная система погоды и времени гарантирует, что каждый игрок на сервере испытывает одинаковую среду в один и тот же момент, что является основой для последовательного ролевого взаимодействия, реалистичных условий вождения и координации событий. В этом уроке мы создадим полную систему синхронизации погоды с настраиваемой скоростью времени, сезонными переходами, голосованием за погоду и событиями отключения света.

Управление состоянием погоды на стороне сервера

Сервер , единственный источник правды для погоды и времени. Он поддерживает текущий тип погоды, следующий для переходов и внутриигровые часы. Каждый клиент синхронизируется с этим состоянием при подключении и получает обновления при изменении погоды. GTA V поддерживает определённый набор типов погоды, включая CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD , и XMAS. Определите логические правила перехода, чтобы погода менялась плавно, а не прыгала от метели к яркому солнцу без промежуточного состояния.

-- server.lua
local Config = {
    timeSpeed    = 2,        -- how many in-game minutes per real second
    syncInterval = 30000,    -- full sync broadcast every 30s
    autoChange   = true,     -- automatic weather cycling
    changeCooldown = 600,    -- seconds between automatic changes
}

local State = {
    weather     = 'CLEAR',
    nextWeather = 'CLOUDS',
    hour        = 12,
    minute      = 0,
    frozen      = false,
    blackout    = false,
    season      = 'summer',
}

local weatherTransitions = {
    EXTRASUNNY = {'CLEAR', 'CLOUDS'},
    CLEAR      = {'EXTRASUNNY', 'CLOUDS', 'OVERCAST'},
    CLOUDS     = {'CLEAR', 'OVERCAST', 'RAIN'},
    OVERCAST   = {'CLOUDS', 'RAIN', 'THUNDER', 'FOGGY'},
    RAIN       = {'OVERCAST', 'THUNDER', 'CLEARING'},
    THUNDER    = {'RAIN', 'OVERCAST'},
    CLEARING   = {'CLEAR', 'CLOUDS'},
    FOGGY      = {'CLEAR', 'CLOUDS', 'SMOG'},
    SMOG       = {'FOGGY', 'CLEAR'},
}

local seasonWeights = {
    summer = {EXTRASUNNY = 30, CLEAR = 30, CLOUDS = 20, RAIN = 10, THUNDER = 5, FOGGY = 5},
    winter = {CLOUDS = 20, OVERCAST = 20, SNOW = 25, SNOWLIGHT = 15, BLIZZARD = 10, FOGGY = 10},
    spring = {CLEAR = 25, CLOUDS = 25, RAIN = 20, CLEARING = 15, FOGGY = 10, OVERCAST = 5},
    autumn = {CLOUDS = 25, OVERCAST = 25, RAIN = 20, FOGGY = 15, CLEAR = 10, SMOG = 5},
}

Цикл прогрессии времени и синхронизации

Система времени работает как серверный цикл, который увеличивает игровой часы в соответствии с настроенной скоростью. A timeSpeed значение 2 означает, что каждая реальная секунда ускоряет игровой час на 2 минуты. Это создаёт цикл день-ночь, который занимает примерно 12 реальных минут на полные 24 игровых часа. Сервер регулярно транслирует текущее время и состояние погоды всем клиентам, а также отправляет мгновенные обновления, когда администратор меняет погоду или вызывает событие отключения электроэнергии. Новые игроки, присоединяющиеся в середине сессии, получают текущее состояние через специальное событие синхронизации, чтобы сразу соответствовать остальным.

-- Time progression loop (server.lua continued)
CreateThread(function()
    while true do
        Wait(1000)
        if not State.frozen then
            State.minute = State.minute + Config.timeSpeed
            if State.minute >= 60 then
                State.hour = State.hour + math.floor(State.minute / 60)
                State.minute = State.minute % 60
            end
            if State.hour >= 24 then
                State.hour = State.hour % 24
            end
        end
    end
end)

-- Periodic full sync
CreateThread(function()
    while true do
        Wait(Config.syncInterval)
        TriggerClientEvent('weather:fullSync', -1, State)
    end
end)

-- Sync new players on join
AddEventHandler('playerJoining', function()
    local src = source
    Wait(2000)
    TriggerClientEvent('weather:fullSync', src, State)
end)

Клиентское приложение погоды

На клиенте необходимо переопределить нативные системы погоды и времени GTA. Ключевые нативы: SetWeatherTypeNowPersist для мгновенных изменений погоды, SetWeatherTypeTransition для плавного смешивания между двумя погодными состояниями, и NetworkOverrideClockTime для установки внутриигровых часов. Вы должны вызвать NetworkOverrideClockTime каждый кадр, чтобы предотвратить возврат игры к собственному расчету времени. Для переходов погоды GTA поддерживает коэффициент смешивания от 0.0 до 1.0, который позволяет плавно интерполировать между двумя состояниями погоды в течение нескольких секунд, создавая реалистичные переходы, когда облака постепенно собираются перед началом дождя.

-- client.lua
local currentWeather = 'CLEAR'
local nextWeather    = 'CLEAR'
local weatherBlend   = 0.0
local hour, minute   = 12, 0
local blackout       = false

RegisterNetEvent('weather:fullSync', function(state)
    currentWeather = state.weather
    nextWeather    = state.nextWeather or state.weather
    hour           = state.hour
    minute         = state.minute
    blackout       = state.blackout or false
    applyWeather()
end)

function applyWeather()
    ClearOverrideWeather()
    ClearWeatherTypePersist()
    SetWeatherTypeNowPersist(currentWeather)
    if currentWeather ~= nextWeather then
        SetWeatherTypeTransition(
            GetHashKey(currentWeather),
            GetHashKey(nextWeather),
            weatherBlend
        )
    end
    if blackout then
        SetArtificialLightsState(true)
        SetArtificialLightsStateAffectsVehicles(false)
    else
        SetArtificialLightsState(false)
    end
end

-- Override clock every frame
CreateThread(function()
    while true do
        Wait(0)
        NetworkOverrideClockTime(hour, minute, 0)
    end
end)

Система голосования за погоду

Система голосования за погоду позволяет вашему сообществу демократично выбирать погодные условия. Игроки голосуют за предпочитаемый тип погоды, и после окончания голосования побеждает самый популярный вариант. Это хорошо работает для ивентов, где важна вовлечённость игроков, например, решая, будет ли встреча автомобилей при ясной погоде или драматической грозе. Ограничьте голосование одним разом за цикл, чтобы избежать спама, и отображайте текущие результаты через систему уведомлений или простое NUI-окно. Сервер собирает голоса, подсчитывает их по окончании периода и плавно меняет погоду, чтобы все игроки одновременно ощутили изменение.

-- Weather voting (server.lua)
local votes = {}
local voteCooldown = {}
local votingOpen = false
local voteOptions = {'CLEAR', 'RAIN', 'THUNDER', 'FOGGY', 'SNOW'}

RegisterCommand('voteweather', function(source, args)
    if not votingOpen then
        TriggerClientEvent('chat:addMessage', source, {args = {'Weather', 'No vote is currently active.'}})
        return
    end
    if voteCooldown[source] then
        TriggerClientEvent('chat:addMessage', source, {args = {'Weather', 'You already voted this round.'}})
        return
    end
    local choice = string.upper(args[1] or '')
    local valid = false
    for _, opt in ipairs(voteOptions) do
        if opt == choice then valid = true break end
    end
    if not valid then
        TriggerClientEvent('chat:addMessage', source, {
            args = {'Weather', 'Options: ' .. table.concat(voteOptions, ', ')}
        })
        return
    end
    votes[choice] = (votes[choice] or 0) + 1
    voteCooldown[source] = true
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Weather', GetPlayerName(source) .. ' voted for ' .. choice}
    })
end, false)

function startWeatherVote()
    votes = {}
    voteCooldown = {}
    votingOpen = true
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Weather', 'Weather vote started! Use /voteweather [type]. Options: ' .. table.concat(voteOptions, ', ')}
    })
    SetTimeout(60000, function()
        votingOpen = false
        local winner, maxVotes = 'CLEAR', 0
        for weather, count in pairs(votes) do
            if count > maxVotes then winner = weather; maxVotes = count end
        end
        transitionWeather(winner)
        TriggerClientEvent('chat:addMessage', -1, {
            args = {'Weather', 'Vote ended! Weather changing to: ' .. winner}
        })
    end)
end

Сезонная система и погодные эффекты на игровой процесс

Сезонная система добавляет глубину, которую большинство серверов упускают. Отслеживая внутриигровой календарь или сопоставляя реальные месяцы с сезонами, можно корректировать вероятности погоды. Лето предпочитает ясную и солнечную погоду с редкими грозами, а зима смещает баланс в сторону снега, пасмурного неба и тумана. Сезоны также могут влиять на игровые механики помимо визуальных эффектов. Во время дождя можно уменьшать сцепление транспорта, активируя модификатор управления на всех активных машинах. Туман снижает дальность прорисовки AI-пешеходов, делая стелс-геймплей более эффективным. Снег слегка замедляет скорость передвижения игроков и требует зимней одежды, чтобы избежать медленной потери здоровья. Эти мелкие детали создают живой мир, который реагирует на окружающую среду.

События отключения электроэнергии и админские инструменты

События отключения электроэнергии , мощный инструмент для серверных событий и ролевых сценариев. SetArtificialLightsState native отключает все искусственные источники света в игровом мире, погружая город во тьму. В сочетании с пасмурной или туманной погодой это создаёт невероятно атмосферную среду, идеально подходящую для хоррор-событий, ограблений или ролевых игр на выживание. Админ-команды должны обеспечивать полный контроль над системой погоды: заморозку времени, установку конкретных часов, принудительный выбор типов погоды, вызов отключений электроэнергии и запуск голосований по погоде. Защитите эти команды с помощью ace-разрешений, чтобы только уполномоченный персонал мог изменять окружение. Хорошо продуманная система погоды становится инструментом повествования, который админы используют для создания настроения на каждом событии и сценарии вашего сервера.

-- Admin commands (server.lua)
RegisterCommand('setweather', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.setweather') then return end
    local weather = string.upper(args[1] or 'CLEAR')
    transitionWeather(weather)
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Admin', 'Weather changed to ' .. weather}
    })
end, true)

RegisterCommand('settime', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.settime') then return end
    State.hour   = tonumber(args[1]) or 12
    State.minute = tonumber(args[2]) or 0
    TriggerClientEvent('weather:fullSync', -1, State)
end, true)

RegisterCommand('blackout', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.blackout') then return end
    State.blackout = not State.blackout
    TriggerClientEvent('weather:fullSync', -1, State)
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Admin', 'Blackout ' .. (State.blackout and 'enabled' or 'disabled')}
    })
end, true)

RegisterCommand('freezetime', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.freezetime') then return end
    State.frozen = not State.frozen
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Admin', 'Time ' .. (State.frozen and 'frozen' or 'unfrozen')}
    })
end, true)

Готовы начать?

Возьмите скрипты в нашем магазине или заходите в Discord за поддержкой, обновлениями и анонсами.