Ultimate-Anzeige
Lektion 24 von 25 im Kurs Roblox-Spiel: Kampfarenen von Coddy.
Die letzte Fähigkeit ist ein Ultimate, und es muss verdient werden. Jeder Treffer, den du landest, fügt dem Zähler, einem Attribut des Spielers, Settings.UltimatePerHit hinzu, bis zu Settings.UltimateMax. Treffer laufen über hurt, das den Angreifer bereits kennt.
math.min hält eine Summe unter einem Höchstwert:
health = math.min(100, health + 30) -- Aus 90 wird 100, nicht 120Der Ultimate-Balken im HUD zeigt den Zähler an: Seine Fill-Breite entspricht dem gefüllten Anteil.
Aufgabe
SchwerFülle eine ultimative Anzeige, während du Treffer landest.
- Wenn ein Spieler beitritt, setze sein
Ultimate-Attribut auf 0. - Füge in
hurt, wenn es einen Angreifer gibt, dessenUltimateden WertSettings.UltimatePerHithinzu, höchstens bisSettings.UltimateMax. - Setze in
BattleClientdie Größe vonhud.Ultimate.Fillaufmeter / Settings.UltimateMax, zu Beginn und immer dann, wenn sichUltimateändert.
Drücke „Spielen“ und triff die Trainingspuppe: Der gelbe Balken füllt sich pro Treffer um ein Zehntel. Drücke dann „Prüfen“.
Probier es selbst
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")
local PathfindingService = game:GetService("PathfindingService")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
local bots = workspace.Bots
local lastSwing = {} -- player -> Zeitpunkt des letzten Schwertschwungs
local combo = {} -- player -> { Treffer in Folge, Zeitpunkt des letzten Treffers }
local lastHit = {} -- humanoid -> der player, der ihn zuletzt getroffen hat
-- Roblox-eigene Schwertanimationen
local slash = Instance.new("Animation")
slash.AnimationId = "rbxassetid://522635514"
local lunge = Instance.new("Animation")
lunge.AnimationId = "rbxassetid://522638767"
print("Swords deal " .. Settings.SwordDamage .. " damage")
-- Eine Zahl, die kurz über der getroffenen Figur schwebt
local function damageNumber(character, amount)
local root = character:FindFirstChild("HumanoidRootPart")
if not root then
return
end
local tag = Instance.new("BillboardGui")
tag.Name = "Damage"
tag.Size = UDim2.fromOffset(60, 30)
tag.StudsOffset = Vector3.new(0, 4.5, 0)
tag.Adornee = root
local text = Instance.new("TextLabel")
text.Size = UDim2.fromScale(1, 1)
text.BackgroundTransparency = 1
text.TextColor3 = Color3.fromRGB(255, 220, 80)
text.TextScaled = true
text.Text = "-" .. math.round(amount)
text.Parent = tag
tag.Parent = character
Debris:AddItem(tag, 0.8)
end
-- Jeder Treffer im Spiel läuft hierüber (attacker: der player, der getroffen hat, oder nil)
local function hurt(humanoid, amount, attacker)
if humanoid.Health <= 0 then
return
end
local character = humanoid.Parent
-- Ein Block fängt den größten Teil des Treffers ab
if character:GetAttribute("Blocking") then
amount *= Settings.BlockMultiplier
end
humanoid:TakeDamage(amount)
damageNumber(character, amount)
if attacker then
lastHit[humanoid] = attacker
end
end
-- Eine Betäubung macht jemanden kurzzeitig bewegungsunfähig
local function stun(humanoid)
local character = humanoid.Parent
if character:GetAttribute("Stunned") then
return
end
character:SetAttribute("Stunned", true)
print(character.Name .. " is stunned")
local speed = humanoid.WalkSpeed
humanoid.WalkSpeed = 0
task.delay(Settings.StunTime, function()
if character.Parent then
humanoid.WalkSpeed = speed
character:SetAttribute("Stunned", nil)
end
end)
end
-- Ein Schwertschwung: erst 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
-- Drei Schwertschwünge in Folge ergeben eine Kombo; der dritte ist ein Ausfallschritt
local c = combo[player]
if c and now - c.last <= Settings.ComboWindow then
c.count = c.count % 3 + 1
else
c = { count = 1 }
combo[player] = c
end
c.last = now
local finisher = c.count == 3
character.Humanoid.Animator:LoadAnimation(finisher and lunge or 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 = {} -- Jeder humanoid nur einmal pro Schwertschwung
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
hurt(humanoid, Settings.ComboDamage[c.count], player)
if finisher then
stun(humanoid)
end
end
end
end
-- Die Fähigkeiten, nach Namen geordnet: was jede einzelne bewirkt
local abilities = {}
-- Das Gerät des Spielers bewegt seinen Charakter selbst, daher findet der Dash dort statt
function abilities.Dash(player)
remotes.Ability:FireClient(player, "Dash")
end
-- Alle um dich herum werden getroffen und von dir weggeschleudert
function abilities.Slam(player, character, root)
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local hit = {}
for _, part in workspace:GetPartBoundsInRadius(root.Position, Settings.SlamRadius, params) do
local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
local theirRoot = part.Parent:FindFirstChild("HumanoidRootPart")
if humanoid and theirRoot and not hit[humanoid] then
hit[humanoid] = true
hurt(humanoid, Settings.SlamDamage, player)
local away = (theirRoot.Position - root.Position) * Vector3.new(1, 0, 1)
if away.Magnitude > 0 then
away = away.Unit
end
theirRoot:ApplyImpulse((away * Settings.SlamKnockback + Vector3.new(0, 40, 0)) * theirRoot.AssemblyMass)
end
end
end
function abilities.Block(player, character)
character:SetAttribute("Blocking", true)
task.delay(Settings.BlockTime, function()
if character.Parent then
character:SetAttribute("Blocking", nil)
end
end)
end
-- Der Client fordert eine Fähigkeit anhand ihres Namens an. Der Server prüft: Ist es eine echte
-- Fähigkeit, lebt der Spieler und ist ihre Abklingzeit abgelaufen? Wann sie wieder bereit ist,
-- steht in einem Attribut des Spielers, damit sein Bildschirm es anzeigen kann.
remotes.Ability.OnServerEvent:Connect(function(player, name)
if typeof(name) ~= "string" or not abilities[name] then
return
end
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if not root or character.Humanoid.Health <= 0 then
return
end
local now = workspace:GetServerTimeNow()
if now < (player:GetAttribute(name .. "ReadyAt") or 0) then
return
end
player:SetAttribute(name .. "ReadyAt", now + Settings.Cooldowns[name])
abilities[name](player, character, root)
end)
-- Jeder Spieler erhält Kills auf der Bestenliste und bei jedem neuen 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()
player:SetAttribute("Streak", 0)
local sword = ServerStorage.Sword:Clone()
sword.Parent = player.Backpack
sword.Activated:Connect(function()
swing(player)
end)
end)
end)
-- Die Trainingspuppe: Ihre Lebensanzeige zeigt ihre Gesundheit an, und eine besiegte
-- Puppe kehrt nach einem Moment als neue Kopie am selben Ort zurück
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)
-- Der nächstgelegene lebende Spielercharakter, den ein Bot sehen kann, und seine Entfernung
local function nearestCharacter(position)
local best, bestDistance = nil, Settings.BotSight
for _, player in Players:GetPlayers() do
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if root and character.Humanoid.Health > 0 then
local distance = (root.Position - position).Magnitude
if distance < bestDistance then
best, bestDistance = character, distance
end
end
end
return best, bestDistance
end
-- Auf einen Charakter zugehen: direkt auf ihn zu, wenn nichts im Weg ist, oder
-- zum nächsten Punkt eines Pfads um das Hindernis herum
local function walkToward(bot, target, path)
local root = bot.HumanoidRootPart
local goal = target.HumanoidRootPart.Position
local params = RaycastParams.new()
params.FilterDescendantsInstances = { bot }
local hit = workspace:Raycast(root.Position, goal - root.Position, params)
if not hit or hit.Instance:IsDescendantOf(target) then
bot.Humanoid:MoveTo(goal)
return
end
path:ComputeAsync(root.Position, goal)
local points = path:GetWaypoints()
if path.Status == Enum.PathStatus.Success and points[2] then
bot.Humanoid:MoveTo(points[2].Position)
end
end
-- Das Gehirn eines Bots: Jede Viertelsekunde sieht er sich um und trifft eine Entscheidung
local function runBot(bot)
local humanoid = bot.Humanoid
local root = bot.HumanoidRootPart
local lastAttack = 0
local path = PathfindingService:CreatePath()
while bot.Parent and humanoid.Health > 0 do
local target, distance = nearestCharacter(root.Position)
local state
if bot:GetAttribute("Stunned") then
state = "Stunned"
elseif humanoid.Health < humanoid.MaxHealth * Settings.RetreatAt then
state = "Retreat"
elseif not target then
state = "Idle"
elseif distance <= Settings.BotRange then
state = "Attack"
else
state = "Chase"
end
bot:SetAttribute("State", state)
if state == "Chase" then
walkToward(bot, target, path)
elseif state == "Attack" then
humanoid:MoveTo(root.Position)
if time() - lastAttack >= Settings.BotAttackCooldown then
lastAttack = time()
humanoid.Animator:LoadAnimation(slash):Play()
hurt(target.Humanoid, Settings.BotDamage)
end
elseif state == "Retreat" and target then
local away = (root.Position - target.HumanoidRootPart.Position) * Vector3.new(1, 0, 1)
if away.Magnitude > 0 then
humanoid:MoveTo(root.Position + away.Unit * 12)
end
else
humanoid:MoveTo(root.Position)
end
task.wait(0.25)
end
end
-- Ein neuer Bot auf der roten Plattform, mit seinen Werten und einer Lebensanzeige
local function spawnBot()
local bot = ServerStorage.Bot:Clone()
bot:PivotTo(workspace.BotSpawn.CFrame + Vector3.new(0, 3, 0))
bot.Humanoid.MaxHealth = Settings.BotHealth
bot.Humanoid.Health = Settings.BotHealth
bot.Humanoid.WalkSpeed = Settings.BotSpeed
bot.Parent = bots
-- Die Physik des Bots auf dem Server belassen, damit der Rückstoß von Slam ihn bewegt
bot.HumanoidRootPart:SetNetworkOwner(nil)
bot.Humanoid.HealthChanged:Connect(function(health)
bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
end)
bot.Humanoid.Died:Connect(function()
-- Der Spieler, der ihn zuletzt getroffen hat, erhält den Kill
local killer = lastHit[bot.Humanoid]
lastHit[bot.Humanoid] = nil
if killer and killer.Parent then
killer.leaderstats.Kills.Value += 1
killer:SetAttribute("Streak", (killer:GetAttribute("Streak") or 0) + 1)
end
task.wait(1)
bot:Destroy()
task.wait(Settings.BotRespawn)
spawnBot()
end)
task.spawn(runBot, bot)
end
for _ = 1, Settings.Bots do
spawnBot()
end
Alle Lektionen in Roblox-Spiel: Kampfarenen
Übe selbstständig: Roblox-Playground