В продолжении темы RPG игры рассмотрим тему спавна ресурсов с их последующей добычей. Генерацию бесконечного мира мы уже освоили. Вот в нём и будем создавать ресурсы, которые после будем добывать.
Итак, наш алгоритм:
- создать ресурс
- определить его объём добычи
- добыча ресурса
- восстановление ресурса после его истощения
Не так чтобы и уж совсем сложно? Для примера возьмём две противоположности — дерево и камень.
Как заспавнить ресурс? Определяем координату и спавним — что тут сложного? А сложность в том, что ресурсы не должны перекрываться, по крайней мере сильно… Откуда следует что сперва мы должны генерировать «низкии» ресурсы, т.е. те, что находятся возле земли и только потом тех что высоки..
Ну что же, ограничимся парой источников ресурсов — камень и дерево. Исходя из предыдущего умозаключения — сперва будем размещать камни, а потом деревья.
Итак, алгоритм простой:
- определяемся с координатой
- с верху опускаем рейкаст
- если он попал в terrain, то создаём ресурс
Для начала выберем несколько деревьев и камушков в тулбоксе. Само собой нам нужны mesh модели. У деревьев зададим PrimaryPart на ствол, а камней — на сам mesh. Разместим всё это в своих папках, после поместим в папку Resource и по итогу перенесём её в ReplicatedStorage.
Пора бы уже и к спавну приступить, но есть ещё одна деталька. По итогу у нас должны различаться модельки в мире. Как же это сделать?
- Деревья. Поворот вокруг вертикальной оси и конечно изменение размера модели.
- Камни. Смена размера модели и поворот по любой оси?
А ещё, в идеале, нам разместить парт привязки в каждой модели, чтобы на него ориентироваться о размещении объекта в мире. В камнях это просто будет парт 1х1х1 в центре. А вот в деревьях… В деревьях этот парт должен быть где-то в центре пня. Естественно в моделях деревьев у нас нет пенька, так что просто поместим парт около низа ствола. Естественно что этот парт нужно заякорить (Anchore) и указать модели что он и является теперь PrimaryPart. Получим что-то такое:
И да, нам как-то наплевать на то, как называются наши модельки в папке ресурсов. Имена мы им будем задавать при помещении клонов в мире игры.
Подготовка закончена, теперь нужно определиться, в какой момент мы их будем «сажать» на карте. Само собой карта у нас уже должна присутствовать, т.е. только после генерации. Второе, мы можем их размещать случайно, а значит нам нужен параметр плотности. Третье, на нужно задать в каких пределах будет меняться их размер. И, конечно же нам нужно задать пределы получаемых с них ресурсов. Для этого мы создадим модуль конфигурации нашего спавна. Разместим его там же, где и сами ресурсы, чтобы у нас оставалось ощущение цельности. Да и сразу будет понятно, к чему этот конфиг относится. Почему мы вынесли настройки из скрипта? Это принципиально — таким образом нам не нужно будет лезть в скрипт при подборе значений, а значит наш будущий «оптимизатор карты» не поломает сам скрипт пока будет экспериментировать.
Обращаю внимание, имена групп в конфиге соответствуют именам папок с самими ресурсами.
Теперь то всё, подготовка закончена? В принципе, да — этого нам достаточно чтобы начать программировать.
Ну что же, приступим. Для начала добавим заготовки в скрипт генерации карты. Первое, это подключение нашего файла конфигурации:
WaitForChild() используется в начале скрипта для гарантированной прогрузки ресурсов на клиенте. И да, на сервере это не обязательно, но лучше привыкнуть что первичная подгрузка ресурсов производится через ожидание потомка, а не через поиск первого подходящего.
Внизу у нас болванка функции для будущей генерации. Вызывать мы её будем из CreateChunk(x, z), ведь именно в ней у нас гарантировано будет создана земля, на которой и надо будет будет разместиться ресурсу. Вот только присмотревшись к самой функции мы видим что.. у нас уже имеются координаты земли и чем она заполнена.
А значит медленный рейкаст нам как бы и не к чему, да? Проверим в деле как поведёт себе генератор случайных чисел…
Чего нам ещё не хватает? Нам нужен список всех доступных ресурсов с их шансами на спавн. Добавим выборку ресов перед функцией их спавна.
-- заполним массив доступных ресурсов local resource = {} -- индексный массив доступных ресурсов for name, res in pairs(config) do -- будем брать только описанные ресурсы -- print(name, res) -- его имя и описание local find = folder:FindFirstChild(name) if find:IsA("Folder") then -- найдено в ресурсах local tmp = find:GetChildren() if #tmp > 0 then -- сами ресурсы присутствуют for a, b in pairs(tmp) do if b:IsA("Model") then -- проверка что это ресурс if b.PrimaryPart ~= nil then -- имеется точка привязки local element = {} -- запоминаем новый элемент ресурса element = table.clone(res) -- скопируем данные в элемент element.name = name -- добавим название element.model = b -- добавим ссылку на саму модель -- ну наконец-то добавим сам элемент в список доступных ресурсов table.insert(resource, element) end end end end end end
Ну теперь то можем уже заспавнить наши ресурсы? По идее — да. Приступаем. Для начала добавим две строчки в начале скрипта?
math.randomseed( tick() ) -- задаём стартовое зерно local resourceFolder = workspace:WaitForChild("Resource") -- место размещения ресурсов
А теперь уже будем заниматься размещением наших моделек. Накидаем на скорую руку начинку функции и посмотрим на результат.
-- создание ресурса на карте по координатам function CreateResource(x, y, z, material) local shans = math.random() -- получаем значение от 0 до 1 local num = math.random(1, #resource) -- случайный ресурс if shans <= resource[num].Shans then -- выпал шанс для размещения print("spawn") local object = resource[num].model:Clone() -- клонируем модель object:PivotTo(CFrame.new(x * GRID, y, z * GRID)) object.Name = resource[num].name object.Parent = resourceFolder end end
Кхм… Кажется это не совсем то, что ожидалось. Во первых слишком много объектов. Во вторых у нас камни под землёй… Уменьшим шансы в конфиге.. в 10 раз и исправим вертикальное размещение.
Уже лучше, но однообразно… Добавим поворот и смену размера моделей.
-- создание ресурса на карте по координатам function CreateResource(x, y, z, material) local shans = math.random() -- получаем значение от 0 до 1 local num = math.random(1, #resource) -- случайный ресурс if shans <= resource[num].Shans then -- выпал шанс для размещения print("spawn") local object = resource[num].model:Clone() -- клонируем модель object:PivotTo(CFrame.new(x * GRID, y + GRID*2, z * GRID)) object.Name = resource[num].name -- смена размера local sizer = math.random(resource[num].SizeMin * 100, resource[num].SizeMax * 100) object:ScaleTo(sizer/100) -- поворот по Y local rotation = CFrame.Angles(0, math.rad(math.random(360)), 0) object:PivotTo(object:GetPivot() * rotation) -- размещение в мире object.Parent = resourceFolder end end
Получили что хотели, но.. как то кривовато выглядит. Заспавненное наезжает друг на друга. Это всё последствия рандома. А значит нам надо добавить проверку на то чтобы рядом ничего не было. Пойдём на хитрость…
-- создание ресурса на карте по координатам function CreateResource(x, y, z, material) local shans = math.random() -- получаем значение от 0 до 1 local num = math.random(1, #resource) -- случайный ресурс if shans <= resource[num].Shans then -- выпал шанс для размещения local object = resource[num].model:Clone() -- клонируем модель object:PivotTo(CFrame.new(x * GRID, y + GRID*2, z * GRID)) object.Name = resource[num].name -- смена размера local sizer = math.random(resource[num].SizeMin * 100, resource[num].SizeMax * 100) object:ScaleTo(sizer/100) -- поворот по Y local rotation = CFrame.Angles(0, math.rad(math.random(360)), 0) object:PivotTo(object:GetPivot() * rotation) -- добавим проверку на пересечение с другими объектами local part = Instance.new("Part") local orient, size = object:GetBoundingBox() -- получаем расположение и размер part.Size = size part.CFrame = orient local check = workspace:GetPartsInPart(part) -- возвращает nil или массив пересечений if check[1] ~= nil then -- удаление как не прошедшее проверку print(check) object:Destroy() else -- размещение в мире print("spawn") object.Parent = resourceFolder end part:Destroy() end end
Ну вот, совсем другое дело. Остаётся поиграться с конфигом и наборами элементов. Вот я, например, добавил папочку с цветочками…
Как вы заметили (заметили же?) на карте резко сократилось число деревьев и камней. Всё потому что теперь шанс спавна распределяется и на цветы, которых я добавил столько же сколько и деревьев с камнями. Этот вопрос я пока что оставлю вам на растерзание.
Не буду мучить копи-пастами (мы же не только кодом занимались), а потому — вот сохранённый плейс: