공격하는 봇
Coddy의 Roblox 게임: 배틀그라운드 코스 레슨. 20개 중 15번째.
Settings.BotRange 범위 안에 있는 봇은 멈춰서 공격합니다. 피해량은 Settings.BotDamage이며, Settings.BotAttackCooldown마다 최대 한 번 공격하고, 베기 동작은 별도의 Animator에서 재생됩니다.
봇의 공격도 여러분의 공격처럼 hurt를 거치므로, 방어(F)로 대부분의 공격을 이미 막을 수 있습니다. 모든 공격에 하나의 함수를 사용하는 것이 여기서 유용합니다.
if time() - lastAttack >= Settings.BotAttackCooldown then
lastAttack = time()
hurt(target.Humanoid, Settings.BotDamage)
end챌린지
어려움runBot에서 local lastAttack = 0을 추가하세요. 대상이 Settings.BotRange 안에 있을 때는 가만히 서고, 마지막 공격 이후 Settings.BotAttackCooldown이 지났다면 봇의 Animator에서 베기 애니메이션을 재생하고 대상에게 Settings.BotDamage만큼 hurt를 적용하세요. 그렇지 않으면 이전처럼 추격하세요.
Play를 누른 다음 Check를 누르세요.
직접 해보기
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 bots = workspace.Bots
local lastSwing = {} -- player -> 마지막으로 검을 휘두른 시각
local combo = {} -- player -> { 연속 적중 횟수, 마지막으로 적중한 시각 }
-- 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")
-- 맞은 대상 위에 잠깐 떠 있는 숫자
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
-- 게임의 모든 공격 적중은 여기서 처리된다
local function hurt(humanoid, amount)
if humanoid.Health <= 0 then
return
end
local character = humanoid.Parent
-- 방어하면 공격 피해를 대부분 막는다
if character:GetAttribute("Blocking") then
amount *= Settings.BlockMultiplier
end
humanoid:TakeDamage(amount)
damageNumber(character, amount)
end
-- 기절하면 잠시 움직일 수 없게 된다
local function stun(humanoid)
local character = humanoid.Parent
if character:GetAttribute("Stunned") then
return
end
character:SetAttribute("Stunned", true)
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
-- 검 휘두르기: 애니메이션이 재생된 다음, 앞쪽 범위 안의 모두가 공격을 받는다
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
-- 검을 세 번 연속 휘두르면 콤보가 된다. 세 번째는 찌르기 공격이다
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()
-- 앞쪽의 판정 범위이며, 자신의 몸은 포함되지 않는다
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local box = root.CFrame * CFrame.new(0, 0, -3)
local hit = {} -- 검을 휘두를 때마다 각 humanoid는 한 번만 적중한다
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
-- 이름별 능력과 각 능력의 효과
local abilities = {}
function abilities.Dash(player, character, root)
root.AssemblyLinearVelocity = root.CFrame.LookVector * Settings.DashSpeed + Vector3.new(0, 15, 0)
end
-- 주변의 모든 대상이 공격을 받고 플레이어에게서 멀리 튕겨 나간다
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
-- 클라이언트가 이름으로 능력 사용을 요청한다. 서버는 실제로 존재하는
-- 능력인지, 플레이어가 살아 있는지, 재사용 대기시간이 끝났는지 확인한다. 다시 사용할 수 있는 시각은
-- 플레이어의 속성으로 저장되므로 화면에 표시할 수 있다.
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)
-- 모든 플레이어는 리더보드에 Kills를 받고, 부활할 때마다 검을 받는다
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()
local sword = ServerStorage.Sword:Clone()
sword.Parent = player.Backpack
sword.Activated:Connect(function()
swing(player)
end)
end)
end)
-- 훈련용 허수아비: 체력 막대가 체력에 따라 변하고, 쓰러진
-- 허수아비는 잠시 후 같은 자리에 새 복제본으로 돌아온다
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)
-- 봇이 볼 수 있는 가장 가까운 살아 있는 플레이어의 캐릭터와 그 거리
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
-- 봇의 판단: 0.25초마다 주위를 살피고 결정한다
local function runBot(bot)
local humanoid = bot.Humanoid
local root = bot.HumanoidRootPart
while bot.Parent and humanoid.Health > 0 do
local target, distance = nearestCharacter(root.Position)
if target then
humanoid:MoveTo(target.HumanoidRootPart.Position)
else
humanoid:MoveTo(root.Position)
end
task.wait(0.25)
end
end
-- 빨간 패드에 나타나는 새 봇과 그 수치 및 체력 막대
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
-- 강타의 밀쳐내기로 봇이 움직이도록 봇의 물리 연산을 서버에서 처리한다
bot.HumanoidRootPart:SetNetworkOwner(nil)
bot.Humanoid.HealthChanged:Connect(function(health)
bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
end)
task.spawn(runBot, bot)
end
spawnBot()
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.