Thêm bot
Bài học 21 trên 25 trong khóa học Trò chơi Roblox: Đấu trường của Coddy.
Một bot là một trận đấu tay đôi. Đấu trường có Settings.Bots bot, và bot bị đánh bại sẽ quay lại.
Khi bot Died, nó ở lại một giây (để cú vung hoặc cú đập đang xuyên qua nó kết thúc), rồi rời đi. Sau Settings.BotRespawn giây, một bot mới sẽ thay thế nó. Bạn có thể để spawnBot tự gọi lại để làm việc đó, giống như setupDummy đã làm:
for _ = 1, 3 do
spawnBot()
endThử thách
KhóLấp đầy đấu trường và cho các bot bị đánh bại quay lại.
- Trong
spawnBot, tại sự kiệnDiedcủa bot: chờ 1 giây, hủy bot, chờSettings.BotRespawnrồi gọispawnBot(). - Thay vì gọi
spawnBot()một lần, hãy tạoSettings.Botsbot bằng vòng lặp for.
Nhấn Chơi: hai bot sẽ tiến về phía bạn, và bot bị đánh bại sẽ quay lại sau 4 giây. Sau đó nhấn Kiểm tra.
Tự làm thử
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 = {} -- người chơi -> thời điểm vung kiếm gần nhất
local combo = {} -- người chơi -> { số đòn đánh liên tiếp, thời điểm đòn gần nhất trúng mục tiêu }
-- Hoạt ảnh vung kiếm có sẵn của 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")
-- Con số hiện thoáng qua phía trên người bị đánh trúng
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
-- Mọi đòn đánh trong trò chơi đều đi qua đây
local function hurt(humanoid, amount)
if humanoid.Health <= 0 then
return
end
local character = humanoid.Parent
-- Đỡ đòn giúp giảm phần lớn sát thương
if character:GetAttribute("Blocking") then
amount *= Settings.BlockMultiplier
end
humanoid:TakeDamage(amount)
damageNumber(character, amount)
end
-- Choáng khiến mục tiêu đứng yên trong chốc lát
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
-- Một cú vung kiếm: phát hoạt ảnh, rồi đánh trúng mọi người trong vùng phía trước bạn
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
-- Ba cú vung kiếm liên tiếp tạo thành combo; cú thứ ba là đâm tới
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()
-- Vùng phía trước bạn, không bao gồm cơ thể bạn
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local box = root.CFrame * CFrame.new(0, 0, -3)
local hit = {} -- Mỗi humanoid chỉ trúng đòn một lần cho mỗi cú vung kiếm
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
-- Các kỹ năng theo tên và tác dụng của từng kỹ năng
local abilities = {}
-- Thiết bị của người chơi điều khiển nhân vật, nên thao tác lướt diễn ra trên thiết bị đó
function abilities.Dash(player)
remotes.Ability:FireClient(player, "Dash")
end
-- Mọi người xung quanh bạn đều trúng đòn và bị hất ra xa
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
-- Máy khách yêu cầu một kỹ năng theo tên. Máy chủ kiểm tra: kỹ năng có tồn tại,
-- người chơi còn sống và thời gian hồi chiêu đã kết thúc. Thời điểm kỹ năng sẵn sàng trở lại
-- là một thuộc tính của người chơi, để màn hình của họ hiển thị thời điểm đó.
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)
-- Mỗi lần hồi sinh, người chơi nhận một thanh kiếm
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)
-- Hình nộm tập luyện: thanh máu thay đổi theo lượng máu, và khi bị hạ,
-- hình nộm sẽ xuất hiện lại sau một lát dưới dạng bản sao mới ở cùng vị trí
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)
-- Nhân vật còn sống gần nhất mà bot nhìn thấy và khoảng cách đến nhân vật đó
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
-- Đi về phía nhân vật: đi thẳng tới đó nếu không có vật cản, hoặc
-- đi đến điểm tiếp theo trên đường vòng tránh vật cản
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
-- Cách bot suy nghĩ: cứ mỗi phần tư giây, bot quan sát xung quanh và quyết định
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
-- Một bot mới trên bệ màu đỏ, có các chỉ số và thanh máu
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
-- Để máy chủ xử lý vật lý của bot, nhờ đó lực đẩy lùi từ Slam có thể đẩy bot đi
bot.HumanoidRootPart:SetNetworkOwner(nil)
bot.Humanoid.HealthChanged:Connect(function(health)
bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
end)
-- Đặt bài học 21: More Bots ở đây
task.spawn(runBot, bot)
end
spawnBot()
Tất cả bài học trong Trò chơi Roblox: Đấu trường
Tự luyện tập: Playground Roblox