Zurück zum Blog
Tutorial7 Min. Lesezeit

FiveM-Wettersynchronisierung: Dynamische Wetter- und Zeitskripte

Synchronisiere Wetter und Uhrzeit auf deinem FiveM-Server. Entdecke dynamische Zyklen, Gewitter, Schnee, Jahreszeitenpakete und passende Skripte.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Das Problem mit nicht synchronisiertem Wetter

Standardmäßig verwaltet GTA V Wetter und Uhrzeit auf jedem Client unabhängig voneinander. Das bedeutet, dass ein Spieler bei strahlendem Sonnenschein fahren kann, während ein anderer Spieler direkt neben ihm ein Gewitter sieht. Auf Roleplay-Servern zerstört das die Immersion vollständig. Stell dir vor, du willst eine Szene koordinieren, in der sich ein Charakter über den Regen beschwert, während auf dem Bildschirm eines anderen Spielers klarer Himmel zu sehen ist. Ein synchronisiertes Wetter- und Zeitsystem sorgt dafür, dass alle Spieler auf dem Server gleichzeitig dieselbe Umgebung erleben. Das ist entscheidend für ein konsistentes Rollenspiel, realistische Fahrbedingungen und die Koordination von Events. In diesem Tutorial erstellen wir ein vollständiges Wettersynchronisierungssystem mit konfigurierbarer Zeitgeschwindigkeit, Jahreszeitenwechseln, Wetterabstimmungen und Blackout-Events.

Serverseitige Verwaltung des Wetterstatus

Der Server ist die einzige maßgebliche Quelle für Wetter und Uhrzeit. Er verwaltet den aktuellen Wettertyp, den nächsten Wettertyp für Übergänge und die Spielzeituhr. Jeder Client synchronisiert sich beim Beitreten mit diesem Status und erhält Aktualisierungen, sobald sich das Wetter ändert. GTA V unterstützt bestimmte Wettertypen, darunter CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD , und XMASDefiniere sinnvolle Übergangsregeln, damit sich das Wetter natürlich verändert, statt ohne Zwischenstufe von einem Schneesturm zu strahlendem Sonnenschein zu springen.

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

Zeitverlauf und Synchronisierungsschleife

Das Zeitsystem läuft in einer serverseitigen Schleife, die die Spielzeituhr anhand der konfigurierten Geschwindigkeit vorstellt. Ein Wert von timeSpeed 2 bedeutet, dass jede Sekunde in Echtzeit die Spielzeituhr um 2 Minuten vorstellt. So dauert ein Tag-Nacht-Zyklus für volle 24 Spielstunden ungefähr 12 Minuten in Echtzeit. Der Server sendet allen Clients regelmäßig den aktuellen Zeit- und Wetterstatus und übermittelt sofortige Aktualisierungen, wenn ein Admin das Wetter ändert oder ein Blackout-Event auslöst. Neue Spieler, die mitten in einer Sitzung beitreten, erhalten den aktuellen Status über ein eigenes Sync-Event, damit sie sofort denselben Zustand wie alle anderen haben.

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

Clientseitige Wetteranwendung

Auf dem Client musst du die nativen Wetter- und Zeitsysteme von GTA überschreiben. Die wichtigsten Natives sind SetWeatherTypeNowPersist für sofortige Wetterwechsel, SetWeatherTypeTransition für weiche Übergänge zwischen zwei Wetterzuständen und NetworkOverrideClockTime zum Einstellen der Spielzeituhr. Du musst NetworkOverrideClockTime bei jedem Frame aufrufen, damit das Spiel nicht zu seiner eigenen Zeitberechnung zurückkehrt. Bei Wetterübergängen unterstützt GTA einen Blending-Faktor zwischen 0.0 und 1.0, mit dem du über mehrere Sekunden sanft zwischen zwei Wetterzuständen überblenden kannst. So entstehen realistische Übergänge, bei denen nach und nach Wolken aufziehen, bevor es zu regnen beginnt.

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

Wetterabstimmungssystem

Ein Wetterabstimmungssystem ermöglicht es deiner Community, demokratisch über das Wetter zu entscheiden. Spieler können für ihren bevorzugten Wettertyp stimmen. Nach Ablauf der Abstimmungszeit gewinnt die beliebteste Option. Das eignet sich gut für Events, bei denen du die Spieler einbeziehen möchtest, etwa bei der Entscheidung, ob ein Autotreffen bei klarem Himmel oder einem dramatischen Gewitter stattfindet. Begrenze die Abstimmung auf eine Stimme pro Zyklus, um Spam zu verhindern, und zeige die aktuellen Stimmenzahlen über dein Benachrichtigungssystem oder eine einfache NUI-Einblendung an. Der Server sammelt die Stimmen, zählt sie nach Ablauf der Abstimmungszeit und übernimmt das gewählte Wetter durch einen sanften Übergang, damit alle Spieler die Änderung gleichzeitig erleben.

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

Jahreszeitensystem und Auswirkungen des Wetters auf das Gameplay

Ein Jahreszeitensystem bringt zusätzliche Tiefe, die auf den meisten Servern fehlt. Indem du den Spielkalender verfolgst oder echte Monate den Jahreszeiten zuordnest, kannst du die Wetterwahrscheinlichkeiten entsprechend gewichten. Im Sommer überwiegt klares, sonniges Wetter mit gelegentlichen Gewittern, während sich im Winter die Wahrscheinlichkeit zugunsten von Schnee, bedecktem Himmel und Nebel verschiebt. Jahreszeiten können neben der Optik auch die Spielmechanik beeinflussen. Bei Regen könntest du die Haftung von Fahrzeugen verringern, indem du einen Handling-Modifikator auf alle aktiven Fahrzeuge anwendest. Nebel könnte die Sichtweite von KI-Peds verringern und Stealth-Gameplay dadurch attraktiver machen. Schnee könnte die Bewegungsgeschwindigkeit der Spieler leicht verringern und Winterkleidung erforderlich machen, um einen langsamen Gesundheitsverlust zu vermeiden. Solche kleinen Details schaffen eine immersive Welt, die lebendig wirkt und auf ihre Umgebung reagiert.

Blackout-Events und Admin-Steuerung

Blackout-Events sind ein wirkungsvolles Mittel für Server-Events und Rollenspielszenarien. Das SetArtificialLightsState Native deaktiviert alle künstlichen Lichtquellen in der Spielwelt und stürzt die Stadt in Dunkelheit. In Kombination mit bedecktem Himmel oder Nebel entsteht so eine unglaublich stimmungsvolle Umgebung, die sich perfekt für Horror-Events, Raubüberfälle oder Survival-Rollenspiel eignet. Admin-Befehle sollten die vollständige Kontrolle über das Wettersystem ermöglichen: die Zeit anhalten, bestimmte Uhrzeiten einstellen, Wettertypen erzwingen, Blackouts auslösen und Wetterabstimmungen starten. Schütze diese Befehle mit ACE-Berechtigungen, damit nur autorisierte Mitarbeiter die Umgebung verändern können. Ein gut durchdachtes Wettersystem wird zu einem Werkzeug fürs Storytelling, mit dem Admins die Stimmung für jedes Event und Szenario auf deinem Server festlegen können.

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

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.