Ein Bot, der verfolgt
Lektion 14 von 20 im Kurs Roblox-Spiel: Schlachtfelder von Coddy.
Nun ein Gegner. Ein Bot ist ein Modell in ServerStorage.Bot mit einem Humanoid, das auf das rote Feld geklont wird. Sein Verhalten ist eine Schleife in einem eigenen Thread: Alle Viertelsekunde sucht er den nächsten lebenden Spieler, den er sehen kann (innerhalb von Settings.BotSight), und läuft mit Humanoid:MoveTo auf ihn zu.
while bot.Parent and humanoid.Health > 0 do
local target = nearestCharacter(root.Position)
if target then
humanoid:MoveTo(target.HumanoidRootPart.Position)
end
task.wait(0.25)
endDen nächsten zu finden, ist die klassische Schleife: Beginne ohne Ziel und mit der Sichtweite als bester Entfernung, und behalte jeden, der näher ist.
Roblox erlaubt dem Client eines nahe gelegenen Spielers, die Physik eines nicht verankerten Modells auszuführen, und ein Serveraufruf wie ApplyImpulse darauf bewirkt dann nichts. Dein Schlag stößt Bots serverseitig zurück, also belasse die Physik jedes Bots dort: bot.HumanoidRootPart:SetNetworkOwner(nil), sobald der Bot im Workspace ist.
Aufgabe
Schwer- Füge
local bots = workspace.Botshinzu. - Schreibe
nearestCharacter(position): den Charakter des nächstgelegenen lebenden Spielers innerhalb vonSettings.BotSightund dessen Entfernung (oder nil). - Schreibe
runBot(bot): Solange der Bot lebt, läuft er zum nächstgelegenen Charakter oder bleibt stehen und wartet 0.25 Sekunden. - Schreibe
spawnBot(): Klone den Bot 3 Studs überworkspace.BotSpawn, setze MaxHealth, Health und WalkSpeed anhand von Settings, weise ihnbotsals Parent zu, lasse seine Physik auf dem Server berechnen, sorge dafür, dass seine Gesundheitsleiste seiner Gesundheit folgt, und starterunBot. Spawne einen Bot.
Drücke Play und gehe auf das rote Pad zu. Drücke dann Check.
Probier es selbst
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
local lastSwing = {} -- player -> wann zuletzt zugeschlagen wurde
local combo = {} -- player -> { Treffer in Folge, wann der letzte Treffer gelandet ist }
-- 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
local function hurt(humanoid, amount)
if humanoid.Health <= 0 then
return
end
local character = humanoid.Parent
-- Ein Block fängt den Großteil des Treffers ab
if character:GetAttribute("Blocking") then
amount *= Settings.BlockMultiplier
end
humanoid:TakeDamage(amount)
damageNumber(character, amount)
end
-- Eine Betäubung macht jemanden kurz bewegungsunfähig
local function stun(humanoid)
local character = humanoid.Parent
if character:GetAttribute("Stunned") then
return
end
character:SetAttribute("Stunned", true)
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 Schlag: erst die Animation, dann werden alle in der Box 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 Schläge 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()
-- Die Box 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 Schlag
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])
if finisher then
stun(humanoid)
end
end
end
end
-- Die Fähigkeiten nach Namen: 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
-- Alle in deiner Nähe 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)
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,
-- ist als Attribut des Spielers gespeichert, damit es auf seinem Bildschirm angezeigt werden 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 bekommt 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()
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 an derselben Stelle 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.
Alle Lektionen in Roblox-Spiel: Schlachtfelder
4Bot-Charaktere
Ein Bot, der verfolgt