Menu
CoddyTech

궁극기 게이지

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

마지막 능력은 궁극기이며, 사용하려면 충전해야 합니다. 적중시킬 때마다 플레이어의 속성인 게이지에 Settings.UltimatePerHit만큼 더해지며, Settings.UltimateMax를 넘지 않습니다. 적중은 hurt를 통해 처리되며, 이 함수는 이미 공격자를 알고 있습니다.

math.min은 합계가 상한을 넘지 않도록 합니다.

health = math.min(100, health + 30) -- 90은 120이 아니라 100이 됩니다

HUD의 Ultimate 막대는 게이지를 표시합니다. 막대가 채워진 비율만큼 Fill의 너비가 넓어집니다.

challenge icon

챌린지

어려움

적중할 때마다 궁극기 게이지를 채우세요.

  1. 플레이어가 참가하면 해당 플레이어의 Ultimate 특성을 0으로 설정하세요.
  2. hurt에서 공격자가 있으면 공격자의 Ultimate에 Settings.UltimatePerHit을 더하되, 최대값은 Settings.UltimateMax로 제한하세요.
  3. BattleClient에서 시작할 때와 Ultimate가 변경될 때마다 hud.Ultimate.Fill의 크기를 meter / Settings.UltimateMax로 설정하세요.

‘플레이’를 누르고 훈련용 더미를 공격하세요. 노란색 막대가 적중할 때마다 10분의 1씩 채워집니다. 그런 다음 ‘확인’을 누르세요.

직접 해보기

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 = {} -- player -> 마지막으로 검을 휘두른 시점
local combo = {} -- player -> { 연속 적중 횟수, 마지막 공격이 적중한 시점 }
local lastHit = {} -- humanoid -> 마지막으로 공격한 플레이어

-- 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

-- 게임의 모든 공격은 이곳을 거칩니다 (attacker: 공격한 플레이어 또는 nil)
local function hurt(humanoid, amount, attacker)
	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)
	if attacker then
		lastHit[humanoid] = attacker
	end
end

-- 기절하면 잠시 움직일 수 없습니다
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

-- 검을 휘두르면 애니메이션이 재생된 뒤, 앞쪽 판정 범위 안의 모든 대상이 공격받습니다
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], player)
			if finisher then
				stun(humanoid)
			end
		end
	end
end

-- 능력의 이름과 각각의 효과
local abilities = {}

-- 플레이어의 캐릭터는 해당 플레이어의 기기에서 움직이므로 돌진도 그곳에서 처리한다
function abilities.Dash(player)
	remotes.Ability:FireClient(player, "Dash")
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, 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

-- 클라이언트가 이름으로 능력 사용을 요청한다. 서버는 실제로 존재하는
-- 능력인지, 플레이어가 살아 있는지, 재사용 대기시간이 끝났는지 확인한다. 다시 사용할 수 있는 시각은
-- 플레이어의 속성에 저장되어 화면에 표시할 수 있다.
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()
		player:SetAttribute("Streak", 0)
		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

-- 캐릭터를 향해 걷기: 가로막는 것이 없으면 곧장 다가가고, 그렇지 않으면
-- 장애물을 돌아가는 경로의 다음 지점으로 이동한다
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

-- 봇의 판단: 0.25초마다 주변을 살피고 행동을 결정한다
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

-- 빨간 패드에 새 봇을 생성하고 능력치와 체력 바를 설정한다
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)
	bot.Humanoid.Died:Connect(function()
		-- 마지막으로 봇을 공격한 플레이어가 처치 기록을 얻는다
		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

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

직접 연습해 보세요: Roblox 플레이그라운드