Menu
CoddyTech

Ein Bot taucht auf

Lektion 16 von 25 im Kurs Roblox-Spiel: Kampfarenen von Coddy.

Jetzt kommt ein Gegner. Ein Bot ist ein Modell in ServerStorage.Bot mit einem Humanoid, das auf die rote Fläche geklont wird, mit seinen Werten aus Settings und einem Lebensbalken wie beim Dummy.

Roblox lässt den Client eines Spielers in der Nähe die Physik eines nicht verankerten Modells ausführen, und ein Serveraufruf wie ApplyImpulse darauf bewirkt dann nichts. Dein Hieb stößt Bots vom Server aus zurück. Sorge deshalb dafür, dass die Physik jedes Bots dort berechnet wird, sobald sich der Bot im Workspace befindet:

crate.Parent = workspace
crate:SetNetworkOwner(nil)
challenge icon

Aufgabe

Schwer

Platziere einen Bot in der Arena.

  1. Füge local bots = workspace.Bots hinzu. Schreibe spawnBot(): Klone ServerStorage.Bot 3 Studs über workspace.BotSpawn; MaxHealth und Health Settings.BotHealth, WalkSpeed Settings.BotSpeed.
  2. Ordne ihn bots unter, dann bot.HumanoidRootPart:SetNetworkOwner(nil).
  3. Passe die Größe von bot.HealthBar.Back.Fill bei HealthChanged an, wie bei der Trainingspuppe. Rufe spawnBot() auf.

Drücke „Spielen“: Ein roter Bot steht auf dem roten Feld. Drücke dann „Prüfen“.

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 -> Zeitpunkt des letzten Schwertschwungs
local combo = {} -- player -> { aufeinanderfolgende Treffer, Zeitpunkt des letzten Treffers }

-- 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 wird hier verarbeitet
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 für einen Moment 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 hintereinander 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])
			if finisher then
				stun(humanoid)
			end
		end
	end
end

-- Die Fähigkeiten nach Namen: was jede einzelne bewirkt
local abilities = {}

-- Das eigene Gerät eines Spielers bewegt seinen Charakter, deshalb wird der Dash dort ausgeführt.
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)
			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 für jedes Leben ein Schwert.
Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function()
		local sword = ServerStorage.Sword:Clone()
		sword.Parent = player.Backpack
		sword.Activated:Connect(function()
			swing(player)
		end)
	end)
end)

-- Die Trainingspuppe: Ihr Lebensbalken zeigt ihre Gesundheit an, und eine besiegte
-- Puppe erscheint kurz darauf wieder – als neue Kopie am selben Ort.
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)

-- Hier kommt Lektion 16: Ein Bot erscheint hin.

Alle Lektionen in Roblox-Spiel: Kampfarenen

Übe selbstständig: Roblox-Playground