FiveM-Anzeigetafel: Spielerliste, Ping und Berufe
Erstelle eine übersichtliche FiveM-Anzeigetafel mit Spielerzahl, Ping, Berufen und IDs. Finde Scripts als stilvolle Alternative zum TAB-Menü.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Warum ein eigenes Scoreboard erstellen
Die standardmäßige FiveM-Spielerliste funktioniert, bietet aber nicht den Feinschliff und die Informationsdichte, die Rollenspielserver benötigen. Mit einem eigenen Scoreboard kannst du Spielernamen zusammen mit ihrer Charakteridentität, ihrem Jobtitel, ihrer Server-ID und ihrer Verbindungsqualität anzeigen und alles passend zum Branding deines Servers gestalten. Neben der Optik gibt dir ein eigenes Scoreboard die Kontrolle darüber, welche Informationen für verschiedene Spielergruppen sichtbar sind. Normale Spieler sehen möglicherweise Charakternamen und IDs, während Admins zusätzliche Felder wie Steam-Kennungen und Ping-Werte sehen. Wenn du dein eigenes Scoreboard erstellst, kannst du auch Funktionen wie Sortierung, Filter nach Job und Spielerzahlen in Echtzeit ergänzen. Das hilft Spielern und Mitarbeitern, den Server effektiv zu verwalten.
Ressourcenstruktur und Manifest
Eine Scoreboard-Ressource ist relativ schlank und besteht aus einem Client-Skript für die Tastenbelegung und Datenerfassung, einem Server-Skript zum Zusammenstellen der Spielerdaten und einer NUI-Seite zur Darstellung der Anzeige. Wenn du die Ressource eigenständig und ohne externe Abhängigkeiten außer deinem Framework hältst, lässt sie sich einfach installieren und warten. Hier sind das Ressourcenmanifest und die Ordnerstruktur:
-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
description 'Custom Scoreboard System'
author 'Agency Scripts'
version '1.0.0'
ui_page 'html/index.html'
client_scripts {
'client/main.lua',
}
server_scripts {
'server/main.lua',
}
files {
'html/index.html',
'html/style.css',
'html/script.js',
}
lua54 'yes'
Die ui_page Die Direktive verweist auf die NUI-Einstiegsdatei, und der files Block stellt sicher, dass alle NUI-Assets in die Ressource eingebunden werden. Mit lua54 aktivierst du Lua 5.4-Funktionen wie ganzzahlige Division und bitweise Operatoren, die bei der Formatierung und Datenpaketierung in fortgeschritteneren Scoreboard-Implementierungen nützlich sein können.
Serverseitige Erfassung von Spielerdaten
Der Server ist dafür zuständig, die vollständige Spielerliste mit allen Informationen zu erstellen, die das Scoreboard anzeigen muss. Statt jeden Spieler einzeln vom Client abfragen zu lassen, sammelt der Server alle Spielerdaten in einer einzigen Tabelle und sendet sie in einem Payload an den anfragenden Client. Dieser Ansatz skaliert gut, weil die Daten einmal pro Anfrage und nicht einmal pro Spieler zusammengetragen werden. Füge Ping-Werte, Jobinformationen und Spielerkennungen in den Payload ein:
-- server/main.lua
local QBCore = exports['qb-core']:GetCoreObject()
QBCore.Functions.CreateCallback('scoreboard:server:getPlayers', function(source, cb)
local players = {}
local srcPlayer = QBCore.Functions.GetPlayer(source)
local isAdmin = srcPlayer and IsPlayerAceAllowed(source, 'admin')
for _, playerId in ipairs(GetPlayers()) do
local Player = QBCore.Functions.GetPlayer(tonumber(playerId))
if Player then
local playerData = {
serverId = playerId,
name = Player.PlayerData.charinfo.firstname .. ' ' ..
Player.PlayerData.charinfo.lastname,
job = Player.PlayerData.job.label or 'Unemployed',
jobGrade = Player.PlayerData.job.grade.name or '',
onDuty = Player.PlayerData.job.onduty,
ping = GetPlayerPing(playerId),
}
-- Only include sensitive data for admins
if isAdmin then
playerData.steamName = GetPlayerName(playerId)
playerData.identifiers = GetPlayerIdentifiers(playerId)
end
table.insert(players, playerData)
end
end
-- Sort by server ID
table.sort(players, function(a, b)
return tonumber(a.serverId) < tonumber(b.serverId)
end)
cb(players, #players, GetConvar('sv_maxclients', '64'))
end)
Der Callback prüft, ob der anfragende Spieler Admin-Berechtigungen hat, bevor er sensible Informationen wie Steam-Namen und Kennungen hinzufügt. So wird verhindert, dass normale Spieler über das Scoreboard Kontoinformationen abgreifen. Der GetConvar Aufruf ruft die maximale Spielerzahl ab, damit das Scoreboard eine Kapazitätsanzeige wie "32/64 Players" anzeigen kann.
Clientseitige Tastenbelegung und Umschaltlogik
Die Scoreboard sollte sich öffnen, wenn ein Spieler eine bestimmte Taste gedrückt hält, und sich schließen, sobald er sie loslässt. FiveM bietet die RegisterKeyMapping Funktion für frei belegbare Tastenbindungen. Sie ist fest codierten Tastenprüfungen vorzuziehen, da Spieler ihre Steuerung damit über das Spieleinstellungsmenü anpassen können. Verwende ein Muster, bei dem die Scoreboard während des Tastendrucks angezeigt wird: Öffne die NUI beim Drücken der Taste und schließe sie beim Loslassen, um ein flüssiges und unaufdringliches Erlebnis zu schaffen:
-- client/main.lua
local QBCore = exports['qb-core']:GetCoreObject()
local isOpen = false
local refreshInterval = 5000 -- ms between data refreshes while open
RegisterKeyMapping('+scoreboard', 'Open Scoreboard', 'keyboard', 'HOME')
RegisterCommand('+scoreboard', function()
if isOpen then return end
isOpen = true
RefreshAndShow()
end, false)
RegisterCommand('-scoreboard', function()
if not isOpen then return end
isOpen = false
SetNuiFocus(false, false)
SendNUIMessage({action = 'hide'})
end, false)
function RefreshAndShow()
QBCore.Functions.TriggerCallback('scoreboard:server:getPlayers',
function(players, count, maxPlayers)
SendNUIMessage({
action = 'show',
players = players,
playerCount = count,
maxPlayers = maxPlayers,
serverName = 'My Roleplay Server',
})
end
)
end
-- Auto-refresh while scoreboard is open
CreateThread(function()
while true do
Wait(refreshInterval)
if isOpen then
RefreshAndShow()
end
end
end)
Die + und - Präfixe bei den Befehlsnamen erstellen automatisch ein Paar für Drücken und Loslassen. Drückt der Spieler die zugewiesene Taste, wird der +scoreboard Befehl ausgeführt. Lässt er die Taste los, wird -scoreboard ausgeführt. Beachte, dass SetNuiFocus mit false, false aufgerufen wird, da die Scoreboard nur Informationen anzeigt und keine Mauseingaben benötigt. Wenn du eine anklickbare Scoreboard mit Sortier- oder Filterfunktion möchtest, übergib stattdessen true, true und füge in der NUI eine Schaltfläche zum Schließen hinzu.
NUI-Rendering mit HTML und CSS
Die NUI-Ebene stellt die Spielerliste als formatierte Tabelle oder Kartenansicht dar. Verwende einen halbtransparenten Hintergrund, der über dem Spielbild liegt, ohne die Sicht vollständig zu versperren. CSS Grid oder Flexbox eignet sich gut, um die Datenspalten auszurichten. Kennzeichne Ping-Werte farblich, damit Spieler die Verbindungsqualität schnell erkennen können: Grün für einen guten Ping, Gelb für einen mäßigen und Rot für einen schlechten. Hier ist die grundlegende NUI-Struktur:
<!-- html/index.html -->
<div id="scoreboard" class="hidden">
<div class="sb-header">
<h2 id="server-name"></h2>
<span id="player-count"></span>
</div>
<div class="sb-columns">
<span>ID</span>
<span>Name</span>
<span>Job</span>
<span>Ping</span>
</div>
<div id="player-list"></div>
</div>
<script>
window.addEventListener('message', (event) => {
const data = event.data;
if (data.action === 'show') {
const sb = document.getElementById('scoreboard');
sb.classList.remove('hidden');
document.getElementById('server-name').textContent = data.serverName;
document.getElementById('player-count').textContent =
data.playerCount + '/' + data.maxPlayers + ' Players';
const list = document.getElementById('player-list');
list.innerHTML = data.players.map(p => {
const pingClass = p.ping < 80 ? 'ping-good' :
p.ping < 150 ? 'ping-warn' : 'ping-bad';
return `<div class="sb-row">
<span class="sb-id">${p.serverId}</span>
<span class="sb-name">${p.name}</span>
<span class="sb-job">${p.job}</span>
<span class="sb-ping ${pingClass}">${p.ping}ms</span>
</div>`;
}).join('');
}
if (data.action === 'hide') {
document.getElementById('scoreboard').classList.add('hidden');
}
});
</script>
Gestalte die Scoreboard mit einem dunklen, halbtransparenten Hintergrund und fester Positionierung, damit sie mittig auf dem Bildschirm bleibt. Lege eine maximale Höhe mit scrollbarem Inhalt für die Spielerliste fest, damit Server mit 200 oder mehr Spielern die Scoreboard nicht über den Bildschirm hinausschieben. Eine dezente Einblendanimation mit CSS-Übergängen kann das Öffnen und Schließen flüssiger und hochwertiger wirken lassen.
Such- und Filterfunktionen hinzufügen
Auf Servern mit vielen Spielern sind Such- und Filterfunktionen für die Scoreboard unverzichtbar. Füge oben ein Suchfeld hinzu, das die Spielerliste während der Eingabe nach Namen, Server-ID oder Jobtitel filtert. Führe den Filter vollständig in JavaScript auf der NUI-Seite aus, damit keine Verzögerung durch eine Anfrage an den Server entsteht. Außerdem kannst du anklickbare Spaltenüberschriften hinzufügen, um nach ID, Name, Job oder Ping zu sortieren:
// html/script.js - Search and sort logic
let currentPlayers = [];
let sortField = 'serverId';
let sortAsc = true;
function renderPlayers(filter = '') {
let filtered = currentPlayers;
if (filter) {
const term = filter.toLowerCase();
filtered = currentPlayers.filter(p =>
p.name.toLowerCase().includes(term) ||
p.serverId.toString().includes(term) ||
p.job.toLowerCase().includes(term)
);
}
filtered.sort((a, b) => {
let valA = a[sortField];
let valB = b[sortField];
if (typeof valA === 'string') {
valA = valA.toLowerCase();
valB = valB.toLowerCase();
}
if (valA < valB) return sortAsc ? -1 : 1;
if (valA > valB) return sortAsc ? 1 : -1;
return 0;
});
const list = document.getElementById('player-list');
list.innerHTML = filtered.map(p => {
const pingClass = p.ping < 80 ? 'ping-good' :
p.ping < 150 ? 'ping-warn' : 'ping-bad';
const dutyDot = p.onDuty
? ''
: '';
return `
${p.serverId}
${p.name}
${dutyDot}${p.job}
${p.ping}ms
`;
}).join('');
document.getElementById('filtered-count').textContent =
filtered.length + ' angezeigt';
}
document.querySelectorAll('.sb-sort').forEach(btn => {
btn.addEventListener('click', () => {
const field = btn.dataset.field;
if (sortField === field) {
sortAsc = !sortAsc;
} else {
sortField = field;
sortAsc = true;
}
renderPlayers(document.getElementById('sb-search').value);
});
});
Der Sortierstatus wird clientseitig verwaltet, daher erfolgt der Wechsel zwischen aufsteigender und absteigender Reihenfolge sofort. Der Punkt neben dem Jobnamen zeigt auf einen Blick an, ob ein Spieler gerade im Dienst ist. Das ist besonders hilfreich für Admins, die in der Liste den Personalstand verschiedener Abteilungen prüfen.
Admin-Funktionen und zusätzliche Informationen
Wenn ein Admin die Scoreboard öffnet, sollte er zusätzliche Spalten und Aktionsschaltflächen sehen, auf die normale Spieler keinen Zugriff haben. Füge für Admins ein Kontextmenü hinzu, das sich per Rechtsklick auf Spielerzeilen öffnet und Optionen bietet, Spieler direkt über die Scoreboard zu kicken, zu bannen, sich zu ihnen zu teleportieren oder sie zu beobachten. Damit wird die Scoreboard von einer einfachen Anzeige zu einem kompakten Admin-Panel. Prüfe Berechtigungen sowohl client- als auch serverseitig. Der Client prüft, ob Admin-Elemente der Benutzeroberfläche angezeigt werden sollen, und der Server validiert jede Admin-Aktion unabhängig davon:
-- Admin action handler (server)
RegisterNetEvent('scoreboard:server:adminAction', function(targetId, action)
local src = source
if not IsPlayerAceAllowed(src, 'admin') then
DropPlayer(src, 'Unauthorized admin action')
return
end
local target = tonumber(targetId)
if not target or not GetPlayerName(target) then return end
if action == 'kick' then
DropPlayer(target, 'Kicked by admin')
elseif action == 'teleport' then
local targetPed = GetPlayerPed(target)
local coords = GetEntityCoords(targetPed)
TriggerClientEvent('scoreboard:client:teleport', src, coords)
elseif action == 'spectate' then
TriggerClientEvent('scoreboard:client:spectate', src, target)
elseif action == 'freeze' then
local targetPed = GetPlayerPed(target)
FreezeEntityPosition(targetPed, true)
SetTimeout(30000, function()
if DoesEntityExist(targetPed) then
FreezeEntityPosition(targetPed, false)
end
end)
end
end)
Protokolliere Admin-Aktionen immer in einer Datenbank oder über einen Discord-Webhook. Erfasse die Kennung des Admins, den betroffenen Spieler, die ausgeführte Aktion und einen Zeitstempel. Dieser Prüfpfad ist unerlässlich, um Streitigkeiten zwischen Teammitgliedern zu klären und kompromittierte Admin-Konten zu erkennen, die ihre Berechtigungen möglicherweise missbrauchen.
Leistung und Optimierung
Eine Scoreboard, die Bildrateneinbrüche verursacht, verfehlt ihren Zweck. Der häufigste Leistungsfehler ist, Spielerdaten in jedem Frame oder alle paar Millisekunden zu aktualisieren. Das automatische Aktualisierungsintervall sollte mindestens drei bis fünf Sekunden betragen, da sich Spielerdaten wie Ping und Jobstatus nicht schnell genug ändern, um häufigere Aktualisierungen zu rechtfertigen. Vermeide es auf der NUI-Seite, bei jeder Aktualisierung das gesamte DOM zu löschen und neu aufzubauen. Verwende stattdessen einen Diff-Ansatz, der nur geänderte Zeilen aktualisiert, oder nutze zumindest innerHTML nur für den Container der Spielerliste statt für die gesamte Scoreboard. Halte das CSS einfach und vermeide aufwendige Effekte wie Blur oder box-shadow auf jeder Zeile, da diese bei Hunderten von Zeilen rechenintensive GPU-Compositing-Vorgänge auslösen. Implementiere auf sehr großen Servern mit 200 oder mehr Spielern virtuelles Scrollen, bei dem nur sichtbare Zeilen und ein kleiner Puffer gerendert werden. So bleibt die Anzahl der DOM-Knoten unabhängig von der Zahl der verbundenen Spieler überschaubar. Teste deine Scoreboard auf einem voll besetzten Server, um die tatsächlichen Auswirkungen auf die Leistung zu messen. Eine Funktion, die mit 10 Spielern problemlos läuft, kann bei 200 Spielern zum Engpass werden.