Thanh năng lượng chiêu cuối
Bài học 24 trên 25 trong khóa học Trò chơi Roblox: Đấu trường của Coddy.
Chiêu cuối cùng là một chiêu tối thượng, và bạn cần kiếm được nó. Mỗi đòn đánh trúng sẽ cộng Settings.UltimatePerHit vào thanh đo, một thuộc tính của người chơi, tối đa là Settings.UltimateMax. Các đòn đánh được ghi nhận qua hurt, vốn đã biết ai là người tấn công.
math.min giữ tổng không vượt quá giới hạn:
health = math.min(100, health + 30) -- 90 thành 100, không phải 120Thanh Ultimate trên HUD hiển thị thanh đo: độ rộng của Fill tương ứng với phần đã được nạp đầy.
Thử thách
KhóLàm đầy thanh tuyệt chiêu khi bạn đánh trúng.
- Khi người chơi tham gia, đặt thuộc tính
Ultimatecủa họ thành 0. - Trong
hurt, khi có người tấn công, cộngSettings.UltimatePerHitvàoUltimatecủa họ, tối đa làSettings.UltimateMax. - Trong
BattleClient, đặt kích thước củahud.Ultimate.Fillthànhmeter / Settings.UltimateMaxlúc bắt đầu và mỗi khiUltimatethay đổi.
Nhấn Chơi và đánh hình nộm: thanh màu vàng đầy thêm một phần mười sau mỗi cú đánh. 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 trúng liên tiếp, thời điểm đòn cuối trúng mục tiêu }
local lastHit = {} -- humanoid -> người chơi đánh trúng lần cuối
-- 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 trên người bị đánh trong chốc lát
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 (attacker: người chơi đánh trúng, hoặc nil)
local function hurt(humanoid, amount, attacker)
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)
if attacker then
lastHit[humanoid] = attacker
end
end
-- Làm 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: 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 lần vung kiếm liên tiếp tạo thành một combo; đò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 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 trong 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], player)
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 quanh bạn 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, player)
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 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 chờ đã 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ủ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 người chơi có Kills trên bảng xếp hạng và nhận một thanh kiếm mỗi lần hồi sinh
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()
player:SetAttribute("Streak", 0)
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; khi hình nộm bị hạ,
-- một bản sao mới sẽ xuất hiện tại chỗ cũ sau một lát
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 nếu không có vật cản, hoặc
-- đi đến điểm tiếp theo trên đường vòng qua 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, nó quan sát xung quanh rồi 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ệ đỏ, với 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 để cú hất văng từ Slam có thể đẩy nó
bot.HumanoidRootPart:SetNetworkOwner(nil)
bot.Humanoid.HealthChanged:Connect(function(health)
bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
end)
bot.Humanoid.Died:Connect(function()
-- Người chơi đánh trúng bot sau cùng được tính một mạng hạ gục
local killer = lastHit[bot.Humanoid]
lastHit[bot.Humanoid] = nil
if killer and killer.Parent then
killer.leaderstats.Kills.Value += 1
killer:SetAttribute("Streak", (killer:GetAttribute("Streak") or 0) + 1)
end
task.wait(1)
bot:Destroy()
task.wait(Settings.BotRespawn)
spawnBot()
end)
task.spawn(runBot, bot)
end
for _ = 1, Settings.Bots do
spawnBot()
end
Tất cả bài học trong Trò chơi Roblox: Đấu trường
Tự luyện tập: Playground Roblox