Menu
CoddyTech

추격하는 봇

Coddy의 Roblox 게임: 배틀그라운드 코스 레슨. 20개 중 14번째.

이제 상대를 만들어 봅시다. 봇은 ServerStorage.Bot 안에 있는 휴머노이드가 포함된 모델로, 복제되어 빨간 패드 위에 놓입니다. 봇의 두뇌는 자체 스레드에서 실행되는 루프입니다. 0.25초마다 볼 수 있는 가장 가까운 살아 있는 플레이어(Settings.BotSight 범위 안)를 찾아 그 플레이어를 향해 Humanoid:MoveTo로 이동합니다.

while bot.Parent and humanoid.Health > 0 do
	local target = nearestCharacter(root.Position)
	if target then
		humanoid:MoveTo(target.HumanoidRootPart.Position)
	end
	task.wait(0.25)
end

가장 가까운 대상을 찾는 것은 전형적인 루프입니다. 처음에는 아무 대상도 선택하지 않고 시야 범위를 최적의 거리로 설정한 다음, 그보다 더 가까운 대상을 계속 선택합니다.

Roblox에서는 근처 플레이어의 클라이언트가 고정되지 않은 모델의 물리 시뮬레이션을 실행할 수 있으며, 그러면 해당 모델에 대한 ApplyImpulse 같은 서버 호출은 아무 효과도 내지 않습니다. 서버에서 내리치는 공격으로 봇을 밀쳐내려면 각 봇의 물리 시뮬레이션을 서버에서 처리하도록 하세요. 봇이 workspace에 들어간 다음 한 번 bot.HumanoidRootPart:SetNetworkOwner(nil)을 호출하면 됩니다.

challenge icon

챌린지

어려움
  1. local bots = workspace.Bots를 추가하세요.
  2. nearestCharacter(position)를 작성하세요. Settings.BotSight 안에 있는 가장 가까운 살아 있는 플레이어의 캐릭터와 그 거리(없으면 nil)를 반환합니다.
  3. runBot(bot)을 작성하세요. 봇이 살아 있는 동안 가장 가까운 캐릭터를 향해 걷거나, 대상이 없으면 가만히 서 있게 한 다음 0.25초 기다립니다.
  4. spawnBot()을 작성하세요. Bot을 workspace.BotSpawn보다 3 스터드 위에 복제하고, Settings의 MaxHealth, Health, WalkSpeed 값을 설정하고, bots에 부모로 지정하고, 서버에서 물리 연산이 처리되도록 하고, 체력 바가 체력을 표시하도록 만든 다음 runBot을 시작합니다. 봇을 하나 생성하세요.

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 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)
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Roblox 게임: 배틀그라운드의 모든 레슨