平衡团队构成

Time:5 分钟

https://developer.roblox.com/assets/blt506c733356766e41/Team-Balancing-Hero.jpg

通过将游戏中互加好友的玩家放入同一队伍并确保双方队伍的平衡,可以保证所有玩家都能享受到公平健康的游戏氛围。向游戏添加保持团队平衡的代码后,公平竞争的玩家们会逐渐发展友谊,从而在你的游戏中投入更多的时间。

如果想要实现本文中的代码示例,建议对 Lua /articles/Table|table(表) 和 Roblox 的 BindableEvent|BindableEvent 有一定了解。

本文中的脚本并不会为游戏自动添加/设置团队。如果对设置团队尚不了解,请参见/articles/Player Spawns and Teams|玩家生成和团队一文。

将好友玩家成组分配

如果想要追踪一组好友玩家,需要先在 ServerScriptService 中包括 Script。在该脚本的代码中,首先需要添加查看游戏中是否有好友的函数,然后将其连接至 Players/PlayerAdded 事件。

ServerScriptService 中的 Script ```

local friendGroups = {}

-- 将好友加入同一群组,以便其加入同一团队
local function groupFriends(player)
	local mutualGroups = {}

	-- 对好友群组进行迭代
	for groupIndex, group in ipairs(friendGroups) do
		-- 对群组中发现的好友进行迭代
		for _, user in ipairs(group) do
			-- 将共同好友放入一个群组中
			if player:IsFriendsWith(user.UserId) then
				if (mutualGroups[group] == nil) then
					table.insert(mutualGroups, groupIndex)
				end
			end
		end
	end

	if #mutualGroups > 0 then
		local groupIndex = mutualGroups[1]
 
		-- 如果玩家拥有多个正在进行游戏的好友群组,则将其合并为一个群组
		if #mutualGroups > 1 then
			groupIndex = mergeGroups(mutualGroups)
		end
 
		table.insert(friendGroups[groupIndex], player)
		assignInitialTeam(player, friendGroups[groupIndex])
	else
		table.insert(friendGroups, {player})
		assignInitialTeam(player)
	end
end

-- 将 "PlayerAdded" 事件连接至 "groupFriends()" 函数
game.Players.PlayerAdded:Connect(groupFriends)
此段代码的作用如下:

  1. 第一行的 `friendGroups` 表将会储存至少拥有一个共同好友的多个玩家列表。
  2. 在查找进入游戏的玩家是否有好友在游戏内时,将会使用 `Player/IsFriendsWith|Player:IsFriendsWith()` 对 `friendGroups` 内各个群组的玩家进行查询。包含进入游戏玩家一名以上好友的群组将会被存储在 `mutualGroups` 表内。
  3. 如果找到了多个拥有好友的群组,则首先会将这些群组通过 `mergeGroups()` 辅助函数(在下文合并好友群组章节中进行了定义),然后玩家将会被分配至该群组。如果没有发现任何好友,则玩家将会被分配至新的群组。
  4. 当玩家与其线上好友进行分组后,使用辅助函数 `assignInitialTeam()` (在下文分配玩家团队章节中进行了定义)将其分配至团队中。

### 合并好友群组

如果想要将多个含有共同好友的群组合并为一个更大的群组,请直接在 `groupFriends()` 函数上方添加下列 `mergeGroups()` 辅助函数。该函数将会查看拥有共同好友的群组,对其进行合并,移除旧的群组,然后将合并过的群组返回至 `groupFriends()` 函数。
-- 将所有拥有共同好友的群组合并至一个群组
local function mergeGroups(groups)
	-- 将其它群组的成员添加至第一个群组
	for i = 2, #groups do
		for _, user in pairs(friendGroups[groups[i]]) do
			table.insert(friendGroups[groups[1]], user)
		end
		-- 移除合并后的废弃群组
		table.remove(friendGroups, groups[i])
	end

	return groups[1]
end
### 分配玩家团队

如果玩家与好友共同游戏,开始时应该将其放入同一团队中。为了达成此目的,应当:

  1. 获取 `TeamService` 并与游戏团队交互。建议将服务放在脚本的最顶端。
local TeamsService = game:GetService("Teams")

local friendGroups = {}
  2. 在 `mergeGroups()` 函数下添加名为 `assignInitialTeam()` 的函数,其作用为接收玩家及其可能加入群组的参数。如果函数收到了群组参数且群组人数不会因过大而造成游戏不平衡,则好友群组中第一条目的 `Player/Team` 属性将会被用来分配团队。如果没有收到群组参数,则玩家将填入人数最少的团队。
-- 当玩家加入游戏时,决定其应当加入的团队
local function assignInitialTeam(player, group)
	-- 检查玩家是否属于任何群组且该群组是否并未超过公平团队人数
	if group and #group < game.Players.MaxPlayers / 2 then
		player.Team = group[1].Team
	else
		local teams = TeamsService:GetTeams()
		table.sort(teams, function(a, b) return #a:GetPlayers() < #b:GetPlayers() end)
		player.Team = teams[1]
	end
end
## 处理玩家退出

当玩家离开游戏后,需要将其从 `friendGroups` 表中移除,否则游戏将会认为好友群组比实际上的人数要多。

如果想要移除玩家的条目,需要对 `friendGroups` 进行迭代,寻找与该玩家相符的用户,并将该玩家的当前索引设为 `nil`。如果玩家之前的群组为空,则将群组也设为 `nil` 。最后需要确保将函数连接至 `Players/PlayerRemoving` 事件。
-- 当玩家离开游戏时,清除其群组
local function removeFromGroup(player)
	-- 对好友群组进行循环,找到玩家
	for groupIndex, group in ipairs(friendGroups) do
		for userIndex, user in ipairs(group) do
			if user.Name == player.Name then
				-- 将其从之前加入的群组中移除
				friendGroups[groupIndex][userIndex] = nil
				-- 如果群组为空,则将其进行移除
				if #friendGroups[groupIndex] == 0 then
					friendGroups[groupIndex] = nil
				end
			end
		end
	end
end

-- 将 "PlayerRemoving" 事件连接至 "removeFromGroup()" 函数
game.Players.PlayerRemoving:Connect(removeFromGroup)

-- 将 "PlayerAdded" 事件连接至 "groupFriends()" 函数
game.Players.PlayerAdded:Connect(groupFriends)
## 重新平衡团队

决定好友群组之后,在 `ReplicatedStorage` 内添加 `BindableEvent`,以供需要测试及重新平衡时使用。按照 Roblox 传统,将此代码置于脚本顶部。
local TeamsService = game:GetService("Teams")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local balanceTeamsEvent = Instance.new("BindableEvent")
balanceTeamsEvent.Name = "BalanceTeamsEvent"
balanceTeamsEvent.Parent = ReplicatedStorage

local friendGroups = {}
接下来,为了保证各团队分配到人数大致相当的好友群组并保持平衡,在 `removeFromGroup()` 下方添加下列 `balanceTeams()` 函数:
local function balanceTeams()
	local teams = TeamsService:GetTeams()

	-- 对群组进行排序,将更大的群组放在前面
	table.sort(friendGroups, function(a, b) return #a > #b end)

	-- 迭代好友群组(已经将其按从大到小进行排序)
	for i = 1, #friendGroups do
		-- 如果群组过大,则将其分开放入两个团队中
		if (#friendGroups[i] > game.Players.MaxPlayers / 2) then
			for _, player in pairs(friendGroups[i]) do
				table.sort(teams, function(a, b) return #a:GetPlayers() < #b:GetPlayers() end)
				player.Team = teams[1]
			end
		else
			-- 对团队列表进行排序,最小的团队放在最前
			table.sort(teams, function(a, b) return #a:GetPlayers() < #b:GetPlayers() end)
			local groupTeam = teams[1]

			-- 将群组中的所有人放入同一团队
			for _, player in pairs(friendGroups[i]) do
				player.Team = groupTeam
			end
		end
	end
end
此函数的作用如下:

  1. 为了保证各团队分配到人数大致相当的好友群组并保持团队平衡,将各 `friendGroups` 按照从大到小进行排序(第 90 行)。
  2. 排序之后,再次对 `friendGroups` 进行迭代。如果任何群组人数大于游戏 `Players/MaxPlayers` 值的一半,则会将该群组内好友分开并分配至人数更少的团队。如果人数小于该值一半,则将会对团队进行升序排序后将该群组分配至最小团队中去。

现在将 `BalanceTeamsEvent` 可绑定`BindableEvent/Event|事件`连接至 `balanceTeams()` 函数,在想要对团队进行重新平衡时`BindableEvent/Fire|触发`该事件即可。建议在两场比赛间触发事件,但也可以自行决定团队平衡的触发条件。
--  "PlayerRemoving" 事件连接至 "removeFromGroup()" 函数
game.Players.PlayerRemoving:Connect(removeFromGroup)

--  "PlayerAdded" 事件连接至 "groupFriends()" 函数
game.Players.PlayerAdded:Connect(groupFriends)

balanceTeamsEvent.Event:Connect(balanceTeams)
## 完整的脚本
local TeamsService = game:GetService("Teams")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local balanceTeamsEvent = Instance.new("BindableEvent")
balanceTeamsEvent.Name = "BalanceTeamsEvent"
balanceTeamsEvent.Parent = ReplicatedStorage

local friendGroups = {}

-- 将所有拥有共同好友的群组合并至一个群组
local function mergeGroups(groups)
	-- 将其它群组的成员添加至第一个群组
	for i = 2, #groups do
		for _, user in pairs(friendGroups[groups[i]]) do
			table.insert(friendGroups[groups[1]], user)
		end
		-- 移除合并后的废弃群组
		table.remove(friendGroups, groups[i])
	end
 
	return groups[1]
end

-- 当玩家加入游戏时,决定其应当加入的团队
local function assignInitialTeam(player, group)
	-- 检查玩家是否属于任何群组且该群组是否并未超过公平团队人数
	if group and #group < game.Players.MaxPlayers / 2 then
		player.Team = group[1].Team
	else
		local teams = TeamsService:GetTeams()
		table.sort(teams, function(a, b) return #a:GetPlayers() < #b:GetPlayers() end)
		player.Team = teams[1]
	end
end

-- 将好友加入同一群组,以便其加入同一团队
local function groupFriends(player)
	local mutualGroups = {}

	-- 对好友群组进行迭代
	for groupIndex, group in ipairs(friendGroups) do
		-- 对群组中发现的好友进行迭代
		for _, user in ipairs(group) do
			-- 将共同好友放入一个群组中
			if player:IsFriendsWith(user.UserId) then
				if (mutualGroups[group] == nil) then
					table.insert(mutualGroups, groupIndex)
				end
			end
		end
	end
 
	if #mutualGroups > 0 then
		local groupIndex = mutualGroups[1]
 
		-- 如果玩家拥有多个正在进行游戏的好友群组,则将其合并为一个群组
		if #mutualGroups > 1 then
			groupIndex = mergeGroups(mutualGroups)
		end
 
		table.insert(friendGroups[groupIndex], player)
		assignInitialTeam(player, friendGroups[groupIndex])
	else
		table.insert(friendGroups, {player})
		assignInitialTeam(player)
	end
end

-- 当玩家离开游戏时,清除其群组
local function removeFromGroup(player)
	-- 对好友群组进行循环,找到玩家
	for groupIndex, group in ipairs(friendGroups) do
		for userIndex, user in ipairs(group) do
			if user.Name == player.Name then
				-- 将其从之前加入的群组中移除
				friendGroups[groupIndex][userIndex] = nil
				-- 如果群组为空,则将其进行移除
				if #friendGroups[groupIndex] == 0 then
					friendGroups[groupIndex] = nil
				end
			end
		end
	end
end

local function balanceTeams()
	local teams = TeamsService:GetTeams()

	-- 对群组进行排序,将更大的群组放在前面
	table.sort(friendGroups, function(a, b) return #a > #b end)

	-- 迭代好友群组(已经将其按从大到小进行排序)
	for i = 1, #friendGroups do
		-- 如果群组过大,则将其分开放入两个团队中
		if (#friendGroups[i] > game.Players.MaxPlayers / 2) then
			for _, player in pairs(friendGroups[i]) do
				table.sort(teams, function(a, b) return #a:GetPlayers() < #b:GetPlayers() end)
				player.Team = teams[1]
			end
		else
			-- 对团队列表进行排序,最小的团队放在最前
			table.sort(teams, function(a, b) return #a:GetPlayers() < #b:GetPlayers() end)
			local groupTeam = teams[1]

			-- 将群组中的所有人放入同一团队
			for _, player in pairs(friendGroups[i]) do
				player.Team = groupTeam
			end
		end
	end
end

-- 将 "PlayerRemoving" 事件连接至 "removeFromGroup()" 函数
game.Players.PlayerRemoving:Connect(removeFromGroup)

-- 将 "PlayerAdded" 事件连接至 "groupFriends()" 函数
game.Players.PlayerAdded:Connect(groupFriends)

balanceTeamsEvent.Event:Connect(balanceTeams)


***__Roblox官方链接__:[平衡团队构成](https://developer.roblox.com/zh-cn/articles/team-balancing)