Lebih Banyak Bot
Pelajaran 21 dari 25 dalam kursus Game Roblox: Battlegrounds di Coddy.
Satu bot berarti satu duel. Arena memiliki Settings.Bots bot, dan bot yang kalah akan kembali.
Saat bot mengalami Died, bot itu tetap berada di sana selama satu detik (agar ayunan atau bantingan yang masih melewatinya selesai), lalu pergi, dan beberapa detik kemudian sesuai Settings.BotRespawn, bot baru menggantikannya. spawnBot dapat memanggil dirinya sendiri untuk itu, seperti yang dilakukan setupDummy:
for _ = 1, 3 do
spawnBot()
endTantangan
SulitPenuhi arena, dan hadirkan kembali bot yang kalah.
- Di dalam
spawnBot, saat botDied: tunggu 1 second, hancurkan bot, tungguSettings.BotRespawn, lalu panggilspawnBot(). - Alih-alih satu
spawnBot(), munculkanSettings.Botsbot dengan perulangan for.
Tekan tombol “Main”: dua bot akan mendatangimu, dan bot yang kalah akan kembali 4 seconds kemudian. Lalu tekan tombol “Periksa”.
Coba sendiri
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 -> waktu terakhir kali mereka mengayunkan pedang
local combo = {} -- player -> { jumlah serangan beruntun, waktu serangan terakhir mengenai sasaran }
-- Animasi pedang bawaan Roblox
local slash = Instance.new("Animation")
slash.AnimationId = "rbxassetid://522635514"
local lunge = Instance.new("Animation")
lunge.AnimationId = "rbxassetid://522638767"
print("Swords deal " .. Settings.SwordDamage .. " damage")
-- Angka yang melayang sesaat di atas siapa pun yang terkena serangan
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
-- Setiap serangan dalam game diproses di sini
local function hurt(humanoid, amount)
if humanoid.Health <= 0 then
return
end
local character = humanoid.Parent
-- Tangkisan menahan sebagian besar serangan
if character:GetAttribute("Blocking") then
amount *= Settings.BlockMultiplier
end
humanoid:TakeDamage(amount)
damageNumber(character, amount)
end
-- Efek stun membuat seseorang tidak bisa bergerak sesaat
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
-- Ayunan pedang: animasi dimainkan, lalu semua yang berada di dalam kotak di depanmu terkena serangan
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
-- Tiga ayunan berturut-turut membentuk kombo; ayunan ketiga adalah tusukan
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()
-- Kotak di depanmu, tidak pernah tubuhmu sendiri
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local box = root.CFrame * CFrame.new(0, 0, -3)
local hit = {} -- Setiap humanoid hanya sekali per ayunan
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
-- Kemampuan berdasarkan nama: apa yang dilakukan masing-masing
local abilities = {}
-- Perangkat pemain menggerakkan karakternya, jadi dash terjadi di perangkat itu
function abilities.Dash(player)
remotes.Ability:FireClient(player, "Dash")
end
-- Semua yang ada di sekitarmu terkena serangan dan terpental menjauh darimu
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
-- Client meminta kemampuan berdasarkan namanya. Server memeriksa apakah itu kemampuan yang valid,
-- pemainnya masih hidup, dan masa tunggunya sudah selesai. Waktu kemampuan itu siap lagi
-- disimpan sebagai atribut pemain agar bisa ditampilkan di layarnya.
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)
-- Setiap pemain mendapat pedang setiap kali hidup
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)
-- Boneka latihan: bilah kesehatannya mengikuti kesehatannya, dan boneka yang kalah
-- muncul kembali sesaat kemudian sebagai salinan baru di tempat yang sama
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)
-- Karakter pemain hidup terdekat yang dapat dilihat bot, beserta jaraknya
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
-- Berjalan menuju karakter: langsung ke arahnya jika tidak ada penghalang, atau
-- ke titik berikutnya pada jalur yang memutar melewati penghalang
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
-- Cara bot berpikir: setiap seperempat detik, ia melihat sekeliling dan mengambil keputusan
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
-- Bot baru di landasan merah, lengkap dengan statistik dan bilah kesehatannya
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
-- Biarkan fisika bot ditangani server agar dorongan dari slam menggerakkannya
bot.HumanoidRootPart:SetNetworkOwner(nil)
bot.Humanoid.HealthChanged:Connect(function(health)
bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
end)
-- Pelajaran 21: More Bots ditempatkan di sini
task.spawn(runBot, bot)
end
spawnBot()
Semua pelajaran di Game Roblox: Battlegrounds
Latihan sendiri: Playground Roblox