Server-Abklingzeiten
Lektion 7 von 20 im Kurs Roblox-Spiel: Schlachtfelder von Coddy.
Ein Exploiter kontrolliert sein eigenes Gerät und kann daher Remotes.Ability hundertmal pro Sekunde mit einem beliebigen Namen auslösen. Eine Abklingzeit im LocalScript würde nur ehrliche Spieler aufhalten: Der Server muss sie durchsetzen.
Der Server speichert für jeden Spieler und jede Fähigkeit als Attribut des Spielers, wann sie wieder bereit ist (in der nächsten Lektion wird sie auf dem Bildschirm angezeigt). workspace:GetServerTimeNow() ist eine Uhr, auf die sich der Server und alle Clients einigen:
local now = workspace:GetServerTimeNow()
if now < (player:GetAttribute(name .. "ReadyAt") or 0) then
return
end
player:SetAttribute(name .. "ReadyAt", now + Settings.Cooldowns[name])Außerdem überprüft er, was ihm gesendet wurde: eine Zeichenfolge, die eine echte Fähigkeit benennt, von einem Spieler, der am Leben ist.
Aufgabe
MittelIm Ability-Handler returnst du vor dem Ausführen der Fähigkeit, wenn:
- der Name kein String ist, der eine Fähigkeit bezeichnet,
- der Spieler keine Charakterwurzel hat oder die Gesundheit seines Humanoids 0 beträgt,
GetServerTimeNow()vor der Bereitschaftszeit des Spielers für diese Fähigkeit liegt, dem Attributname .. "ReadyAt".
Setze andernfalls dieses Attribut auf jetzt plus Settings.Cooldowns[name] und führe die Fähigkeit dann aus. Drücke Play und dann Check: Die Prüfungen lösen das Remote aus, so wie es ein Exploiter tun würde.
Probier es selbst
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
local lastSwing = {} -- player -> Zeitpunkt des letzten Schlags
-- Roblox’ eigene Schwertanimationen
local slash = Instance.new("Animation")
slash.AnimationId = "rbxassetid://522635514"
print("Swords deal " .. Settings.SwordDamage .. " damage")
-- Ein Schwerthieb: Erst kommt die Animation, dann werden alle im Bereich vor dir getroffen
local function swing(player)
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if not root or character.Humanoid.Health <= 0 then
return
end
local now = time()
if lastSwing[player] and now - lastSwing[player] < Settings.SwingCooldown then
return
end
lastSwing[player] = now
character.Humanoid.Animator:LoadAnimation(slash):Play()
-- der Bereich vor dir, niemals dein eigener Körper
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local box = root.CFrame * CFrame.new(0, 0, -3)
local hit = {} -- jeden Humanoid nur einmal pro Schwerthieb
for _, part in workspace:GetPartBoundsInBox(box, Settings.HitboxSize, params) do
local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
if humanoid and not hit[humanoid] then
hit[humanoid] = true
humanoid:TakeDamage(Settings.SwordDamage)
end
end
end
-- Die Fähigkeiten, nach Namen geordnet: was jede einzelne bewirkt
local abilities = {}
function abilities.Dash(player, character, root)
root.AssemblyLinearVelocity = root.CFrame.LookVector * Settings.DashSpeed + Vector3.new(0, 15, 0)
end
-- Der Client fordert eine Fähigkeit anhand ihres Namens an; der Server führt sie aus
remotes.Ability.OnServerEvent:Connect(function(player, name)
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if abilities[name] and root then
abilities[name](player, character, root)
end
end)
-- Jeder Spieler bekommt Kills auf der Bestenliste und in jedem Leben ein Schwert
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local kills = Instance.new("IntValue")
kills.Name = "Kills"
kills.Parent = leaderstats
player.CharacterAdded:Connect(function()
local sword = ServerStorage.Sword:Clone()
sword.Parent = player.Backpack
sword.Activated:Connect(function()
swing(player)
end)
end)
end)
-- Die Trainingspuppe: Ihre Lebensanzeige passt sich ihrer Gesundheit an, und eine besiegte
-- Puppe erscheint nach kurzer Zeit als frische Kopie am selben Ort wieder
local function setupDummy(dummy)
local humanoid = dummy.Humanoid
humanoid.HealthChanged:Connect(function(health)
dummy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / humanoid.MaxHealth, 1)
end)
humanoid.Died:Connect(function()
local spot = dummy:GetPivot()
task.wait(0.5)
dummy:Destroy()
task.wait(Settings.DummyRespawn)
local fresh = ServerStorage.Dummy:Clone()
fresh:PivotTo(spot)
fresh.Parent = workspace
setupDummy(fresh)
end)
end
setupDummy(workspace.Dummy)
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.