Số sát thương
Bài học 15 trên 25 trong khóa học Trò chơi Roblox: Đấu trường của Coddy.
Mỗi đòn đánh nên cho biết nó gây ra bao nhiêu sát thương: một con số bật lên phía trên mục tiêu rồi biến mất sau chốc lát. Đó là một BillboardGui có TextLabel, gắn vào phần gốc của mục tiêu, còn Debris sẽ xóa nó sau 0.8 giây mà không làm gián đoạn trò chơi:
Debris:AddItem(tag, 0.8)Đặt đoạn này trong hurt, sau quy tắc chặn, để đòn đánh bị chặn vẫn hiển thị lượng sát thương gây ra. math.round giữ cho số luôn là số nguyên.
Thử thách
Trung bìnhHiển thị lượng sát thương của mỗi đòn đánh phía trên mục tiêu.
- Phía trên
hurt, viếtdamageNumber(character, amount): một BillboardGui tên làDamage, gắn với phần gốc và chứa một TextLabel hiển thị"-" .. math.round(amount). - Đặt nó làm đối tượng con của nhân vật, rồi gọi
Debris:AddItem(tag, 0.8). - Trong
hurt, gọi hàm này sauTakeDamage.
Nhấn Chơi rồi đánh hình nộm: -10 sẽ hiện lên phía trên nó. Sau đó nhấn Kiểm tra.
Tự làm thử
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
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 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")
-- Thêm bài học 15: Damage Numbers vào đây
-- 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)
end
-- Hiệu ứng 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 lần vung kiếm: chạy hoạt ảnh, rồi đánh trúng mọi mục tiêu trong vùng trước mặt 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 lần vung kiếm liên tiếp tạo thành một combo; lần thứ ba là đòn đâm
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 trước mặt 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 lần 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: 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 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 dùng 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 đã hết. Thời điểm kỹ năng sẵn sàng trở lại
-- là một thuộc tính (attribute) của người chơi, để màn hình có thể 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 người chơi nhận một thanh kiếm sau mỗi lần hồi sinh
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ạ gục,
-- 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)
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Trò chơi Roblox: Đấu trường
Tự luyện tập: Playground Roblox