ROBLOX — Создание Leaderboard

Автор: | 13 ноября, 2022
Поделиться...

Давно хотел написать урок по созданий доски лидеров и.. тут получил тестовое задание по его написанию. Так уж сошлись звёзды.

Когда-то давно, года так два назад, понадобилась мне данная фича и на просторах интернета я нашёл нечто подходящее. На основе этого и делал свою таблицу лидеров.

Итак постановка — хранить значения в базе данных по каждом игроку. Выводить первую сотню в порядке уменьшения позиции, т.е. от лучшего к худшему. Естественно что все разом не уместятся и потому должна быть прокрутка списка лидеров. По возможности, вывести аватарки игроков.

Добавочное условие — сама таблица лидеров должна быть не зависима от всего, что происходит в игре и пользоваться только данными из БД. Таким образом получим, что БД меняется в любом месте игры.

Подготовка стенда

Создаём новый плейс в Roblox Studio.

Ставим парт и растягиваем его до нужных размеров.

Вешаем на него SurfaceGui. (По факту этот тот-же ScreenGui, но ограниченный конкретной гранью парта на который подвешен.) Указываем ему Face = Front, просто для удобства.

На данный SurfaceGui вешаем ScrollingFrame (нам же нужна прокрутка). И обычный Frame для заголовков будущего списка. И туда же Script (серверный) для работы с БД. Выглядеть это будет примерно так: (названия не критичны)

Пока что ничего сложного. Можно сказать, что это обычная работа с GUI натянутым на part.

База данных — проблемы

Прежде чем продолжим, немного о работе БД в Roblox. Есть два варианта DataStore и OrderedDataStore. С первым мы уже знакомились и его прекрасно заменяет надстройка DataStore2, т.к. это обычное хранение данных в виде таблицы по ключу из ID и значения. А вот второе как раз нам и понадобится, т.к. у него есть два замечательных свойства: сортировка получаемых данных и их постраничное чтение. Чего, к сожалению, нет в обычном DataStore. Но и про первое не забудем, так как актуальные данные у нас будут находится в DataStore2 из-за его кэширования процедур записи значений в БД и они могут быть уже изменены. С этим может быть небольшой «косяк» но о нём и его решении позже.

Отступление. Работа с БД возможна только при включенном API в настройках игры. Но чтобы это сделать, надо, как минимум, сохранить наш плейс в Roblox.

Начинка списка

Для начала создадим то, что хотим в итоге увидеть. Для этого отредактируем наш TopBar. Что у нас должно отображаться?

  • Image — Изображение аватарки игрока
  • Place — Порядковый номер
  • PName — Его имя
  • Value — Рейтинговое число

Image — имеет тип Image, остальное TextLabel.

Можно считать, что это минимальный, комфортный набор данных для списка лидеров.

По итогу нашей работы мы получим нечто похожее:

Теперь, копируем наш TopBar в под Script и даём имя Sample. Это будет заготовка, которую мы будем помещать в ScrollingFrame для каждого игрока, после её заполнения.

В сам ScrollingFrame поместим UIListLayout. Он нужен будет нам для задания порядка отображаемых строк в списке.

Таким образом будем иметь итоговый вид:

Программный код

Настало время программирования заполнения нашего списка лидеров. Определимся с тем что и как у нас должно работать.

  • Мы должны брать данные из OrderedDataStore (ODS) и сравнивать их с текущими данными в DataStore2. Тут есть два варианта — игрок может быть в игре и его данные изменились и игрока может уже не быть в игре. К вопросу о «косяке», он как раз и будет в том, что в ODS данные будут устаревшими и нам нужно решить каким данным мы больше доверяем или обновить данные в ODS. Второй шаг более верный и тут выходит ещё одно «но», в игре может присутствовать игрок, которого ещё нет в ODS. А значит его данные мы должны туда добавить.
  • Мы должны как-то достать «аватар» игрока с сайта Roblox.
  • Сортировать список полученных данных

Приступим. Сперва объявим ссылки на GUI и работу с базами данных.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-- объявляем ссылки на элементы интерфейса
local sg = script.Parent -- Surface GUI
local sample = script:WaitForChild("Sample") -- Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") -- The scrolling frame
local ui = sf:WaitForChild("UI") -- The UI list layout
-- Объявляем работу с БД игры
local DataStore2 = require(1936396537) -- если не скачивать модуль с его установкой
-- объявляем работу с сортированной БД
-- https://developer.roblox.com/en-us/api-reference/class/DataStoreService
local dataStoreService = game:GetService("DataStoreService")
-- Задаём работу с сортированной БД
local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard1") -- тут указан ключ таблицы списка нашей игры
-- объявляем ссылки на элементы интерфейса local sg = script.Parent -- Surface GUI local sample = script:WaitForChild("Sample") -- Our Sample frame local sf = sg:WaitForChild("ScrollingFrame") -- The scrolling frame local ui = sf:WaitForChild("UI") -- The UI list layout -- Объявляем работу с БД игры local DataStore2 = require(1936396537) -- если не скачивать модуль с его установкой -- объявляем работу с сортированной БД -- https://developer.roblox.com/en-us/api-reference/class/DataStoreService local dataStoreService = game:GetService("DataStoreService") -- Задаём работу с сортированной БД local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard1") -- тут указан ключ таблицы списка нашей игры
-- объявляем ссылки на элементы интерфейса
local sg = script.Parent 						-- Surface GUI
local sample = script:WaitForChild("Sample") 	-- Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") 	-- The scrolling frame
local ui = sf:WaitForChild("UI") 				-- The UI list layout

-- Объявляем работу с БД игры
local DataStore2 = require(1936396537) -- если не скачивать модуль с его установкой

-- объявляем работу с сортированной БД 
-- https://developer.roblox.com/en-us/api-reference/class/DataStoreService
local dataStoreService = game:GetService("DataStoreService")
-- Задаём работу с сортированной БД
local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard1") -- тут указан ключ таблицы списка нашей игры

Это кусок элементарных вещей. Разве что требуется требуется пояснение последней строки. «Leaderboard1» это ключ таблицы с которой мы будем работать. Т.е. для создания новой доски нам понадобится.. только поменять название ключа.

Второй шаг. Обновим данные в нашей ODS для текущих игроков:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-- Обновление/актуализация данных по текущим игрокам
for i, plr in pairs(game.Players:GetChildren()) do-- перебор всех активных игроков в игре
if plr.UserId>0 then -- проверка на то, что игрок активен
MaxHeightStore = DataStore2("MaxHeight1", plr) -- создаём ссылку на хранилище денег и т.д. и т.п. указанного игрока
local w=MaxHeightStore:Get(0) -- получаем данные из DataStore2
if w then -- если имеется значение, запишем его в OrderedDataStore
-- оборачиваем в pcall чтобы у нас не случилось беды, если БД будет не доступна
-- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно
pcall(function()
dataStore:UpdateAsync(plr.UserId,function(w)
w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение
return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync
end)
end)
end
end
end
-- Обновление/актуализация данных по текущим игрокам for i, plr in pairs(game.Players:GetChildren()) do-- перебор всех активных игроков в игре if plr.UserId>0 then -- проверка на то, что игрок активен MaxHeightStore = DataStore2("MaxHeight1", plr) -- создаём ссылку на хранилище денег и т.д. и т.п. указанного игрока local w=MaxHeightStore:Get(0) -- получаем данные из DataStore2 if w then -- если имеется значение, запишем его в OrderedDataStore -- оборачиваем в pcall чтобы у нас не случилось беды, если БД будет не доступна -- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно pcall(function() dataStore:UpdateAsync(plr.UserId,function(w) w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync end) end) end end end
	-- Обновление/актуализация данных по текущим игрокам
	for i, plr in pairs(game.Players:GetChildren()) do-- перебор всех активных игроков в игре
		if plr.UserId>0 then -- проверка на то, что игрок активен
			MaxHeightStore = DataStore2("MaxHeight1", plr) -- создаём ссылку на хранилище денег и т.д. и т.п. указанного игрока
			local w=MaxHeightStore:Get(0) -- получаем данные из DataStore2
			if w then	-- если имеется значение, запишем его в OrderedDataStore
				-- оборачиваем в pcall чтобы у нас не случилось беды, если БД будет не доступна
				-- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно
				pcall(function()
					dataStore:UpdateAsync(plr.UserId,function(w)
						w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение
						 return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync
					end)
				end) 
			end
		end
	end

MaxHeightStore — объявляем вне цикла, для доступа в любом месте.

Теперь можно получить данные из ODS:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-- Запрос данных из OrderedDataStore
local smallestFirst = false -- false = 2 перед 1, true = 1 перед 2
local numberToShow = 100 -- Любое число в интервале 1-100, Большие значения замедлят работу
local minValue = 0 -- Минимальное получаемое значение
local maxValue = 10e30 -- (10^30), максимальное получаемое значение
-- запрос страницы данных из OrderedDataStore
-- объявление параметров страницы
local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
-- Получение данных
local top = pages:GetCurrentPage() -- Получаем первую страницу
-- Запрос данных из OrderedDataStore local smallestFirst = false -- false = 2 перед 1, true = 1 перед 2 local numberToShow = 100 -- Любое число в интервале 1-100, Большие значения замедлят работу local minValue = 0 -- Минимальное получаемое значение local maxValue = 10e30 -- (10^30), максимальное получаемое значение -- запрос страницы данных из OrderedDataStore -- объявление параметров страницы local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue) -- Получение данных local top = pages:GetCurrentPage() -- Получаем первую страницу
	-- Запрос данных из OrderedDataStore
	local smallestFirst = false	-- false = 2 перед 1, true = 1 перед 2
    local numberToShow = 100	-- Любое число в интервале 1-100, Большие значения замедлят работу
    local minValue = 0 			-- Минимальное получаемое значение
	local maxValue = 10e30		-- (10^30), максимальное получаемое значение
	-- запрос страницы данных из OrderedDataStore
	-- объявление параметров страницы
    local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
    -- Получение данных
    local top = pages:GetCurrentPage()	-- Получаем первую страницу

Теперь займёмся магией. Нам надо получить имя и картинку игрока из ресурсов сайта Roblox.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-- получение изображений игроков (где-то стырено)
local data = {} -- объявляем новый набор данных
for a,b in ipairs(top) do -- перебираем все записи из полученной страницы
local userid = b.key -- Получаем User id
local points = b.value -- Получаем значение для сортировки
local username = "[Failed To Load]" -- Имя по умолчанию (ошибка загрузки)
local s,e = pcall(function() -- оборачиваем в pcall т.к. это сторонний запрос
username = game.Players:GetNameFromUserIdAsync(userid) -- Получить имя игрока
end)
if not s then -- Если что-то пошло не так
warn("Error getting name for "..userid..". Error: "..e)
end
-- Магия получения картинки аватара игрока
local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
-- создаём образ в нашей таблице
table.insert(data,{username,points,image}) -- Добавляем в нашу таблицу.. новую таблицу
end
-- получение изображений игроков (где-то стырено) local data = {} -- объявляем новый набор данных for a,b in ipairs(top) do -- перебираем все записи из полученной страницы local userid = b.key -- Получаем User id local points = b.value -- Получаем значение для сортировки local username = "[Failed To Load]" -- Имя по умолчанию (ошибка загрузки) local s,e = pcall(function() -- оборачиваем в pcall т.к. это сторонний запрос username = game.Players:GetNameFromUserIdAsync(userid) -- Получить имя игрока end) if not s then -- Если что-то пошло не так warn("Error getting name for "..userid..". Error: "..e) end -- Магия получения картинки аватара игрока local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150) -- создаём образ в нашей таблице table.insert(data,{username,points,image}) -- Добавляем в нашу таблицу.. новую таблицу end
	-- получение изображений игроков (где-то стырено)
	local data = {}				-- объявляем новый набор данных
	for a,b in ipairs(top) do	-- перебираем все записи из полученной страницы
		local userid = b.key	-- Получаем User id
		local points = b.value	-- Получаем значение для сортировки
		local username = "[Failed To Load]"	-- Имя по умолчанию (ошибка загрузки)
		local s,e = pcall(function()	-- оборачиваем в pcall т.к. это сторонний запрос
			username = game.Players:GetNameFromUserIdAsync(userid)	-- Получить имя игрока
		end)
		if not s then	-- Если что-то пошло не так
			warn("Error getting name for "..userid..". Error: "..e)
		end
		-- Магия получения картинки аватара игрока
		local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
		-- создаём образ в нашей таблице
		table.insert(data,{username,points,image})	-- Добавляем в нашу таблицу.. новую таблицу
	end
	

Я честно попытался объяснить всё что происходит в данном фрагменте кода. По крайней мере как я это понял.

В следующем куске выводим полученные данные в нужном виде на наш ScrollingFrame.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-- отображение данных
ui.Parent = script -- перемещаем сортировщик
sf:ClearAllChildren() -- Удаляем все оставшиеся элементы в ScrollingFrame
ui.Parent = sf -- возвращаем сортировщик
for number,d in pairs(data) do -- перебираем все данные из созданного ранее массива
local name = d[1] -- имя
local val = d[2] -- значение
local image = d[3] -- картинка
-- немного красивости на уровне кода
local color = Color3.new(1,1,1) -- Задаём цвет по умолчанию
if number == 1 then
color = Color3.new(1,1,0) -- Цвет игрока на первом месте
elseif number == 2 then
color = Color3.new(0.9,0.9,0.9) -- Цвет игрока на втором месте
elseif number == 3 then
color = Color3.fromRGB(166, 112, 0) -- Цвет игрока на третьем месте
end
-- Здесь формируем новый элемент ScrollingFrame
local new = sample:Clone() -- Создаём клон из нашего образца
new.Name = name -- Устанавливаем имя
new.LayoutOrder = number -- UIListLayout изспользует это число для сортировки элементов
new.Image.Image = image -- Устанавливаем изображение
new.Image.Place.Text = number -- Добавляем порядковый номер
new.Image.Place.TextColor3 = color -- Задаём ранее расчитанный цвет
new.PName.Text = name -- Устанавливаем имя
new.Value.Text = val -- Устанавливаем значение из БД
new.Value.TextColor3 = color -- Задаём ранее расчитанный цвет
new.PName.TextColor3 = color -- Задаём ранее расчитанный цвет
new.Parent = sf -- Помещаем подготовленный клон в ScrollingFrame
end
wait() -- мгновенная пауза (не помню зачем поставил)
-- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных
sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
-- отображение данных ui.Parent = script -- перемещаем сортировщик sf:ClearAllChildren() -- Удаляем все оставшиеся элементы в ScrollingFrame ui.Parent = sf -- возвращаем сортировщик for number,d in pairs(data) do -- перебираем все данные из созданного ранее массива local name = d[1] -- имя local val = d[2] -- значение local image = d[3] -- картинка -- немного красивости на уровне кода local color = Color3.new(1,1,1) -- Задаём цвет по умолчанию if number == 1 then color = Color3.new(1,1,0) -- Цвет игрока на первом месте elseif number == 2 then color = Color3.new(0.9,0.9,0.9) -- Цвет игрока на втором месте elseif number == 3 then color = Color3.fromRGB(166, 112, 0) -- Цвет игрока на третьем месте end -- Здесь формируем новый элемент ScrollingFrame local new = sample:Clone() -- Создаём клон из нашего образца new.Name = name -- Устанавливаем имя new.LayoutOrder = number -- UIListLayout изспользует это число для сортировки элементов new.Image.Image = image -- Устанавливаем изображение new.Image.Place.Text = number -- Добавляем порядковый номер new.Image.Place.TextColor3 = color -- Задаём ранее расчитанный цвет new.PName.Text = name -- Устанавливаем имя new.Value.Text = val -- Устанавливаем значение из БД new.Value.TextColor3 = color -- Задаём ранее расчитанный цвет new.PName.TextColor3 = color -- Задаём ранее расчитанный цвет new.Parent = sf -- Помещаем подготовленный клон в ScrollingFrame end wait() -- мгновенная пауза (не помню зачем поставил) -- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
	-- отображение данных
	ui.Parent = script		-- перемещаем сортировщик
	sf:ClearAllChildren()	-- Удаляем все оставшиеся элементы в ScrollingFrame 
	ui.Parent = sf			-- возвращаем сортировщик
	for number,d in pairs(data) do	-- перебираем все данные из созданного ранее массива
		local name = d[1]	-- имя
		local val = d[2]	-- значение
		local image = d[3]	-- картинка
		-- немного красивости на уровне кода
		local color = Color3.new(1,1,1)			-- Задаём цвет по умолчанию
		if number == 1 then
			color = Color3.new(1,1,0)			-- Цвет игрока на первом месте
		elseif number == 2 then
			color = Color3.new(0.9,0.9,0.9)		-- Цвет игрока на втором месте
		elseif number == 3 then
			color = Color3.fromRGB(166, 112, 0)	-- Цвет игрока на третьем месте
		end
		-- Здесь формируем новый элемент ScrollingFrame
		local new = sample:Clone()			-- Создаём клон из нашего образца
		new.Name = name						-- Устанавливаем имя
        new.LayoutOrder = number			-- UIListLayout изспользует это число для сортировки элементов
		new.Image.Image = image				-- Устанавливаем изображение
		new.Image.Place.Text = number		-- Добавляем порядковый номер
		new.Image.Place.TextColor3 = color	-- Задаём ранее расчитанный цвет
		new.PName.Text = name				-- Устанавливаем имя
		new.Value.Text = val				-- Устанавливаем значение из БД
		new.Value.TextColor3 = color		-- Задаём ранее расчитанный цвет
		new.PName.TextColor3 = color		-- Задаём ранее расчитанный цвет
		new.Parent = sf						-- Помещаем подготовленный клон в ScrollingFrame
	end
	wait()	-- мгновенная пауза (не помню зачем поставил)
	-- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных
	sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)	 

Ну вот собственно и практически весь код. Полный код скрипта будет ниже.

Что можно или нужно ещё сделать?

  • Главное — вынести критичные значения в переменные в шапку скрипта — ключи, чтобы не искать их в коде при размножении лидборда. (Сделаем это в общем коде)
  • Вместо бесконечного цикла, можно повесить обновление на событие. Правда это чревато частыми обновлениями при большом числе игроков.
  • Украсить ещё как-нибудь…

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
-- объявление переменных для простоты копирования лидборда
-- ключ значений у игрока в DataStore2
local DS2key = "MaxHeight1"
-- ключ значений у игрока в OrderedDataStore
local OrderKey = "Leaderboard1"
-- Могут быть и одинаковыми, но лучше использовать так: какой ключ игрока, в каком лидборде
-- объявляем ссылки на элементы интерфейса
local sg = script.Parent -- Surface GUI
local sample = script:WaitForChild("Sample") -- Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") -- The scrolling frame
local ui = sf:WaitForChild("UI") -- The UI list layout
-- Объявляем работу с БД игры
local DataStore2 = require(1936396537) -- если не скачивать модуль с его установкой
-- объявляем работу с сортированной БД
-- https://developer.roblox.com/en-us/api-reference/class/DataStoreService
local dataStoreService = game:GetService("DataStoreService")
-- The data store service
local dataStore = dataStoreService:GetOrderedDataStore(OrderKey)
local MaxHeightStore -- наша глобальная переменная
while true do
-- Обновление/актуализация данных по текущим игрокам
for i, plr in pairs(game.Players:GetChildren()) do-- перебор всех активных игроков в игре
if plr.UserId>0 then -- проверка на то, что игрок активен
MaxHeightStore = DataStore2(DS2key, plr) -- создаём ссылку на хранилище денег и т.д. и т.п. указанного игрока
local w=MaxHeightStore:Get(0) -- получаем данные из DataStore2
if w then -- если имеется значение, запишем его в OrderedDataStore
-- оборачиваем в pcall чтобы у нас не случилось беды, если БД будет не доступна
-- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно
pcall(function()
dataStore:UpdateAsync(plr.UserId,function(w)
w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение
return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync
end)
end)
end
end
end
-- Запрос данных из OrderedDataStore
local smallestFirst = false -- false = 2 перед 1, true = 1 перед 2
local numberToShow = 100 -- Любое число в интервале 1-100, Большие значения замедлят работу
local minValue = 0 -- Минимальное получаемое значение
local maxValue = 10e30 -- (10^30), максимальное получаемое значение
-- запрос страницы данных из OrderedDataStore
-- объявление параметров страницы
local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
-- Получение данных
local top = pages:GetCurrentPage() -- Получаем первую страницу
-- получение изображений игроков (где-то стырено)
local data = {} -- объявляем новый набор данных
for a,b in ipairs(top) do -- перебираем все записи из полученной страницы
local userid = b.key -- Получаем User id
local points = b.value -- Получаем значение для сортировки
local username = "[Failed To Load]" -- Имя по умолчанию (ошибка загрузки)
local s,e = pcall(function() -- оборачиваем в pcall т.к. это сторонний запрос
username = game.Players:GetNameFromUserIdAsync(userid) -- Получить имя игрока
end)
if not s then -- Если что-то пошло не так
warn("Error getting name for "..userid..". Error: "..e)
end
-- Магия получения картинки аватара игрока
local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
-- создаём образ в нашей таблице
table.insert(data,{username,points,image}) --Добавляем в нашу таблицу.. новую таблицу
end
-- отображение данных
ui.Parent = script -- перемещаем сортировщик
sf:ClearAllChildren() -- Удаляем все оставшиеся элементы в ScrollingFrame
ui.Parent = sf -- возвращаем сортировщик
for number,d in pairs(data) do -- перебираем все данные из созданного ранее массива
local name = d[1] -- имя
local val = d[2] -- значение
local image = d[3] -- картинка
-- немного красивости на уровне кода
local color = Color3.new(1,1,1) -- Задаём цвет по умолчанию
if number == 1 then
color = Color3.new(1,1,0) -- Цвет игрока на первом месте
elseif number == 2 then
color = Color3.new(0.9,0.9,0.9) -- Цвет игрока на втором месте
elseif number == 3 then
color = Color3.fromRGB(166, 112, 0) -- Цвет игрока на третьем месте
end
-- Здесь формируем новый элемент ScrollingFrame
local new = sample:Clone() -- Создаём клон из нашего образца
new.Name = name -- Устанавливаем имя
new.LayoutOrder = number -- UIListLayout изспользует это число для сортировки элементов
new.Image.Image = image -- Устанавливаем изображение
new.Image.Place.Text = number -- Добавляем порядковый номер
new.Image.Place.TextColor3 = color -- Задаём ранее расчитанный цвет
new.PName.Text = name -- Устанавливаем имя
new.Value.Text = val -- Устанавливаем значение из БД
new.Value.TextColor3 = color -- Задаём ранее расчитанный цвет
new.PName.TextColor3 = color -- Задаём ранее расчитанный цвет
new.Parent = sf -- Помещаем подготовленный клон в ScrollingFrame
end
wait() -- мгновенная пауза (не помню зачем поставил)
-- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных
sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
wait(10) -- Ожидание до следующего такта обновления
end
-- объявление переменных для простоты копирования лидборда -- ключ значений у игрока в DataStore2 local DS2key = "MaxHeight1" -- ключ значений у игрока в OrderedDataStore local OrderKey = "Leaderboard1" -- Могут быть и одинаковыми, но лучше использовать так: какой ключ игрока, в каком лидборде -- объявляем ссылки на элементы интерфейса local sg = script.Parent -- Surface GUI local sample = script:WaitForChild("Sample") -- Our Sample frame local sf = sg:WaitForChild("ScrollingFrame") -- The scrolling frame local ui = sf:WaitForChild("UI") -- The UI list layout -- Объявляем работу с БД игры local DataStore2 = require(1936396537) -- если не скачивать модуль с его установкой -- объявляем работу с сортированной БД -- https://developer.roblox.com/en-us/api-reference/class/DataStoreService local dataStoreService = game:GetService("DataStoreService") -- The data store service local dataStore = dataStoreService:GetOrderedDataStore(OrderKey) local MaxHeightStore -- наша глобальная переменная while true do -- Обновление/актуализация данных по текущим игрокам for i, plr in pairs(game.Players:GetChildren()) do-- перебор всех активных игроков в игре if plr.UserId>0 then -- проверка на то, что игрок активен MaxHeightStore = DataStore2(DS2key, plr) -- создаём ссылку на хранилище денег и т.д. и т.п. указанного игрока local w=MaxHeightStore:Get(0) -- получаем данные из DataStore2 if w then -- если имеется значение, запишем его в OrderedDataStore -- оборачиваем в pcall чтобы у нас не случилось беды, если БД будет не доступна -- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно pcall(function() dataStore:UpdateAsync(plr.UserId,function(w) w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync end) end) end end end -- Запрос данных из OrderedDataStore local smallestFirst = false -- false = 2 перед 1, true = 1 перед 2 local numberToShow = 100 -- Любое число в интервале 1-100, Большие значения замедлят работу local minValue = 0 -- Минимальное получаемое значение local maxValue = 10e30 -- (10^30), максимальное получаемое значение -- запрос страницы данных из OrderedDataStore -- объявление параметров страницы local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue) -- Получение данных local top = pages:GetCurrentPage() -- Получаем первую страницу -- получение изображений игроков (где-то стырено) local data = {} -- объявляем новый набор данных for a,b in ipairs(top) do -- перебираем все записи из полученной страницы local userid = b.key -- Получаем User id local points = b.value -- Получаем значение для сортировки local username = "[Failed To Load]" -- Имя по умолчанию (ошибка загрузки) local s,e = pcall(function() -- оборачиваем в pcall т.к. это сторонний запрос username = game.Players:GetNameFromUserIdAsync(userid) -- Получить имя игрока end) if not s then -- Если что-то пошло не так warn("Error getting name for "..userid..". Error: "..e) end -- Магия получения картинки аватара игрока local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150) -- создаём образ в нашей таблице table.insert(data,{username,points,image}) --Добавляем в нашу таблицу.. новую таблицу end -- отображение данных ui.Parent = script -- перемещаем сортировщик sf:ClearAllChildren() -- Удаляем все оставшиеся элементы в ScrollingFrame ui.Parent = sf -- возвращаем сортировщик for number,d in pairs(data) do -- перебираем все данные из созданного ранее массива local name = d[1] -- имя local val = d[2] -- значение local image = d[3] -- картинка -- немного красивости на уровне кода local color = Color3.new(1,1,1) -- Задаём цвет по умолчанию if number == 1 then color = Color3.new(1,1,0) -- Цвет игрока на первом месте elseif number == 2 then color = Color3.new(0.9,0.9,0.9) -- Цвет игрока на втором месте elseif number == 3 then color = Color3.fromRGB(166, 112, 0) -- Цвет игрока на третьем месте end -- Здесь формируем новый элемент ScrollingFrame local new = sample:Clone() -- Создаём клон из нашего образца new.Name = name -- Устанавливаем имя new.LayoutOrder = number -- UIListLayout изспользует это число для сортировки элементов new.Image.Image = image -- Устанавливаем изображение new.Image.Place.Text = number -- Добавляем порядковый номер new.Image.Place.TextColor3 = color -- Задаём ранее расчитанный цвет new.PName.Text = name -- Устанавливаем имя new.Value.Text = val -- Устанавливаем значение из БД new.Value.TextColor3 = color -- Задаём ранее расчитанный цвет new.PName.TextColor3 = color -- Задаём ранее расчитанный цвет new.Parent = sf -- Помещаем подготовленный клон в ScrollingFrame end wait() -- мгновенная пауза (не помню зачем поставил) -- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y) wait(10) -- Ожидание до следующего такта обновления end
-- объявление переменных для простоты копирования лидборда
-- ключ значений у игрока в DataStore2
local DS2key = "MaxHeight1"
-- ключ значений у игрока в OrderedDataStore
local OrderKey = "Leaderboard1"
-- Могут быть и одинаковыми, но лучше использовать так: какой ключ игрока, в каком лидборде

-- объявляем ссылки на элементы интерфейса
local sg = script.Parent 						-- Surface GUI
local sample = script:WaitForChild("Sample") 	-- Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") 	-- The scrolling frame
local ui = sf:WaitForChild("UI") 				-- The UI list layout

-- Объявляем работу с БД игры
local DataStore2 = require(1936396537) -- если не скачивать модуль с его установкой

-- объявляем работу с сортированной БД 
-- https://developer.roblox.com/en-us/api-reference/class/DataStoreService
local dataStoreService = game:GetService("DataStoreService")
-- The data store service
local dataStore = dataStoreService:GetOrderedDataStore(OrderKey)

local MaxHeightStore -- наша глобальная переменная

while true do
	-- Обновление/актуализация данных по текущим игрокам
	for i, plr in pairs(game.Players:GetChildren()) do-- перебор всех активных игроков в игре
		if plr.UserId>0 then -- проверка на то, что игрок активен
			MaxHeightStore = DataStore2(DS2key, plr) -- создаём ссылку на хранилище денег и т.д. и т.п. указанного игрока
			local w=MaxHeightStore:Get(0) -- получаем данные из DataStore2
			if w then	-- если имеется значение, запишем его в OrderedDataStore
				-- оборачиваем в pcall чтобы у нас не случилось беды, если БД будет не доступна
				-- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно
				pcall(function()
					dataStore:UpdateAsync(plr.UserId,function(w)
						w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение
						 return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync
					end)
				end) 
			end
		end
	end
	
	-- Запрос данных из OrderedDataStore
	local smallestFirst = false	-- false = 2 перед 1, true = 1 перед 2
    local numberToShow = 100	-- Любое число в интервале 1-100, Большие значения замедлят работу
    local minValue = 0 			-- Минимальное получаемое значение
	local maxValue = 10e30		-- (10^30), максимальное получаемое значение
	-- запрос страницы данных из OrderedDataStore
	-- объявление параметров страницы
    local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
    -- Получение данных
    local top = pages:GetCurrentPage()	-- Получаем первую страницу

	-- получение изображений игроков (где-то стырено)
	local data = {}				-- объявляем новый набор данных
	for a,b in ipairs(top) do	-- перебираем все записи из полученной страницы
		local userid = b.key	-- Получаем User id
		local points = b.value	-- Получаем значение для сортировки
		local username = "[Failed To Load]"	-- Имя по умолчанию (ошибка загрузки)
		local s,e = pcall(function()	-- оборачиваем в pcall т.к. это сторонний запрос
			username = game.Players:GetNameFromUserIdAsync(userid)	-- Получить имя игрока
		end)
		if not s then	-- Если что-то пошло не так
			warn("Error getting name for "..userid..". Error: "..e)
		end
		-- Магия получения картинки аватара игрока
		local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
		-- создаём образ в нашей таблице
		table.insert(data,{username,points,image})	--Добавляем в нашу таблицу.. новую таблицу
	end
	
	-- отображение данных
	ui.Parent = script		-- перемещаем сортировщик
	sf:ClearAllChildren()	-- Удаляем все оставшиеся элементы в ScrollingFrame 
	ui.Parent = sf			-- возвращаем сортировщик
	for number,d in pairs(data) do	-- перебираем все данные из созданного ранее массива
		local name = d[1]	-- имя
		local val = d[2]	-- значение
		local image = d[3]	-- картинка
		-- немного красивости на уровне кода
		local color = Color3.new(1,1,1)			-- Задаём цвет по умолчанию
		if number == 1 then
			color = Color3.new(1,1,0)			-- Цвет игрока на первом месте
		elseif number == 2 then
			color = Color3.new(0.9,0.9,0.9)		-- Цвет игрока на втором месте
		elseif number == 3 then
			color = Color3.fromRGB(166, 112, 0)	-- Цвет игрока на третьем месте
		end
		-- Здесь формируем новый элемент ScrollingFrame
		local new = sample:Clone()			-- Создаём клон из нашего образца
		new.Name = name						-- Устанавливаем имя
        new.LayoutOrder = number			-- UIListLayout изспользует это число для сортировки элементов
		new.Image.Image = image				-- Устанавливаем изображение
		new.Image.Place.Text = number		-- Добавляем порядковый номер
		new.Image.Place.TextColor3 = color	-- Задаём ранее расчитанный цвет
		new.PName.Text = name				-- Устанавливаем имя
		new.Value.Text = val				-- Устанавливаем значение из БД
		new.Value.TextColor3 = color		-- Задаём ранее расчитанный цвет
		new.PName.TextColor3 = color		-- Задаём ранее расчитанный цвет
		new.Parent = sf						-- Помещаем подготовленный клон в ScrollingFrame
	end
	wait()	-- мгновенная пауза (не помню зачем поставил)
	-- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных
	sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)	 
	wait(10)	-- Ожидание до следующего такта обновления
end

Ну вот, собственно у нас готова заготовка лидерборда для чего угодно.

Использование

Подошли к самому главному. Как использовать сиё чудо?

  • копируем в любое место workspace (можем просто повесить на любую существующую стенку копию SurfaceGui)
  • указываем оба значения ключей
  • если надо, меняем внешний вид

Всё. С этого момента оно будет работать само по себе.

Протестировать можно здесь.


Поделиться...

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *