Давно хотел написать урок по созданий доски лидеров и.. тут получил тестовое задание по его написанию. Так уж сошлись звёзды.
Когда-то давно, года так два назад, понадобилась мне данная фича и на просторах интернета я нашёл нечто подходящее. На основе этого и делал свою таблицу лидеров.
Итак постановка — хранить значения в базе данных по каждом игроку. Выводить первую сотню в порядке уменьшения позиции, т.е. от лучшего к худшему. Естественно что все разом не уместятся и потому должна быть прокрутка списка лидеров. По возможности, вывести аватарки игроков.
Добавочное условие — сама таблица лидеров должна быть не зависима от всего, что происходит в игре и пользоваться только данными из БД. Таким образом получим, что БД меняется в любом месте игры.
Подготовка стенда
Создаём новый плейс в 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 и работу с базами данных.
-- объявляем ссылки на элементы интерфейса
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 для текущих игроков:
-- Обновление/актуализация данных по текущим игрокам
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 чтобы у нас не случилось беды, если БД будет не доступна
-- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно
dataStore:UpdateAsync(plr.UserId,function(w)
w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение
return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync
-- Обновление/актуализация данных по текущим игрокам
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:
-- Запрос данных из 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.
-- получение изображений игроков (где-то стырено)
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) -- Получить имя игрока
if not s then -- Если что-то пошло не так
warn("Error getting name for "..userid..". Error: "..e)
-- Магия получения картинки аватара игрока
local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
-- создаём образ в нашей таблице
table.insert(data,{username,points,image}) -- Добавляем в нашу таблицу.. новую таблицу
-- получение изображений игроков (где-то стырено)
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.
ui.Parent = script -- перемещаем сортировщик
sf:ClearAllChildren() -- Удаляем все оставшиеся элементы в ScrollingFrame
ui.Parent = sf -- возвращаем сортировщик
for number,d in pairs(data) do -- перебираем все данные из созданного ранее массива
local val = d[2] -- значение
local image = d[3] -- картинка
-- немного красивости на уровне кода
local color = Color3.new(1,1,1) -- Задаём цвет по умолчанию
color = Color3.new(1,1,0) -- Цвет игрока на первом месте
color = Color3.new(0.9,0.9,0.9) -- Цвет игрока на втором месте
color = Color3.fromRGB(166, 112, 0) -- Цвет игрока на третьем месте
-- Здесь формируем новый элемент 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
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)
Ну вот собственно и практически весь код. Полный код скрипта будет ниже.
Что можно или нужно ещё сделать?
- Главное — вынести критичные значения в переменные в шапку скрипта — ключи, чтобы не искать их в коде при размножении лидборда. (Сделаем это в общем коде)
- Вместо бесконечного цикла, можно повесить обновление на событие. Правда это чревато частыми обновлениями при большом числе игроков.
- Украсить ещё как-нибудь…
-- объявление переменных для простоты копирования лидборда
-- ключ значений у игрока в 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 -- наша глобальная переменная
-- Обновление/актуализация данных по текущим игрокам
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 чтобы у нас не случилось беды, если БД будет не доступна
-- возвращаемое значение нам не интересно, нам просто нужно обновить БД если это возможно
dataStore:UpdateAsync(plr.UserId,function(w)
w=MaxHeightStore:Get(0) -- для сопрограммы вновь считываем значение
return tonumber(w) -- !!! возвращаемое значение исключительно числовое, для записи в UpdateAsync
-- Запрос данных из 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) -- Получить имя игрока
if not s then -- Если что-то пошло не так
warn("Error getting name for "..userid..". Error: "..e)
-- Магия получения картинки аватара игрока
local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
-- создаём образ в нашей таблице
table.insert(data,{username,points,image}) --Добавляем в нашу таблицу.. новую таблицу
ui.Parent = script -- перемещаем сортировщик
sf:ClearAllChildren() -- Удаляем все оставшиеся элементы в ScrollingFrame
ui.Parent = sf -- возвращаем сортировщик
for number,d in pairs(data) do -- перебираем все данные из созданного ранее массива
local val = d[2] -- значение
local image = d[3] -- картинка
-- немного красивости на уровне кода
local color = Color3.new(1,1,1) -- Задаём цвет по умолчанию
color = Color3.new(1,1,0) -- Цвет игрока на первом месте
color = Color3.new(0.9,0.9,0.9) -- Цвет игрока на втором месте
color = Color3.fromRGB(166, 112, 0) -- Цвет игрока на третьем месте
-- Здесь формируем новый элемент 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
wait() -- мгновенная пауза (не помню зачем поставил)
-- Устанавливаем реальный размер внутренней области ScrollingFrame на основе всех помещённых данных
sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
wait(10) -- Ожидание до следующего такта обновления
-- объявление переменных для простоты копирования лидборда
-- ключ значений у игрока в 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)
- указываем оба значения ключей
- если надо, меняем внешний вид
Всё. С этого момента оно будет работать само по себе.
Протестировать можно здесь.