Друзья, делюсь своими работами! В студии 3D-печати Чулкова в Миассе недавно прошли испытания один из моих проектов — адаптер-соединитель румпеля для двигателя моторной лодки. Использовал углепластичный пластик для повышения прочности и надежности.
Также предлагаю услуги контрактной 3D-печати — быстрое выполнение заказов и индивидуальный подход к каждому проекту. Если нужны прототипы, детали или что-то уникальное — обращайтесь, сделаю качественно и в срок!
Друзья, делюсь с вами новостью! В студии 3D-печати Чулкова в Миассе мы осваиваем новую нишу — изготовление форм и мастер-моделей для рыболовных снастей. Используем экологичный, не токсичный PET-пластик для 3D-печати, что отлично подходит для создания гипсовых форм для литья силикона и олова. Теперь можем быстро и качественно делать кастомы, формы и прототипы для рыболовов и не только. Предлагаем услуги контрактной 3D-печати с индивидуальным подходом и короткими сроками. Если нужны уникальные решения — обращайтесь! Буду рад показать свои работы и поделиться опытом. 🎣🔧🚀
С этой троицей демонов всё и началось — это мои самые первые работы, которые я напечатала на фотополимерном принтере. Тогда я только осваивала эту технологию, училась подбирать смолу, настраивать поддержи и привыкать к совсем другому подходу после обычной FDM-печати. Поэтому именно к этим трём у меня особенное отношение: с них начался мой путь в смоляной печати и в какой-то степени — новая глава в моём творчестве.
Эта фигурка — одна из трёх. Все трое демонесс готовы, но выкладывать их решила постепенно, начиная с этой — самой, пожалуй, деликатной по цвету.
📏 Размеры: Высота — 18 см Ширина (по нижней части платья) — около 10 см 🪄 Подставки у неё нет — фигурка стоит, опираясь на складки платья.
Покрашена вручную, в основном аэрографом и красками Mr. Hobby, но с проработкой деталей кистью. Особенно долго возилась с лицом — для меня это всегда центральный элемент, даже если он почти скрыт под волосами. Фотополимерная печать, Anycubic Tough Resin 2.0, печатала на Elegoo Saturn 4 Ultra.
На фото — готовый результат. Остальные демонессы — скоро! :)
Долго красил миниатюры по принципу "ну вроде похоже на броню", "тень? ну пусть будет потемнее", пока не наткнулся на книгу Джеймса Гарни "Цвет и свет". И тут как будто лампочка включилась — буквально, потому что теперь я понимаю, как свет вообще работает.
Оказалось, что красить — это не просто взял потемнее, потом посветлее. Это целая наука ! И Гарни объясняет её так, что даже после рабочего дня мозг не умирает от перегрузки.
Теперь, когда я крашу, у меня в голове голос:
— А где источник света ?
— Почему тут холодный оттенок в тени ?
— Ты вообще видел, как кожа выглядит при закате ?
Результат — миниатюры стали выглядеть так, будто их кто-то нормальный покрасил. Советую всем, кто хочет, чтобы их покраска выглядела не как "я старался", а как "я кажется понял как это должно работать". Понятно что до совершенства еще далеко, но базу книга очень хорошо разжёвывает.
Доброго времени суток! Если вы вдруг захотели сделать игру со случайной генерацией мира (В моем случае это приключенческий выживач-боссобойка) но у вас как и у меня нет либо навыков, либо желания делать сложную процедурную генерацию полноценных структур, то, возможно, вам подойдет мой вариант, который я постараюсь расписать ниже.
Сам принцип:
Играли в Морской Бой? В нем на двухмерной матрице 10x10 на нужно расставить опр. количество маленьких, средних и больших кораблей.
Вот у нас в ядре своем, мир тоже будет двухмерной матрицей (только нужного вам размера), в котором случайные ячейки мы помечаем как будущие: малые острова (синие), средние острова (зеленые), и крупные острова (красные)
Потом, используя данные этой карты, мы в координатах игрового мира будем размещать случайные ландшафты островов из заранее созданного пресета (об это далее). И схематично, на выходе мы получим что-то вот такое:
Само собой это можно будет конфигурировать под ваши нужны, например если бы вам хотелось чтобы крупных островов было больше, а маленькие, к примеру, могли бы прилегать к островам побольше. Да в принципе и понятие "большой" можно наверное превратить и в целые материки, не 2x2 ячейки, а скажем 6x6.
Штош, хватит картинок и буковок, займемся кодом! (Ага, теперь буковки даже без картинок пойдут...)
2. Пишем код для генерации 2d матрицы с данными о расположении островов
Для начала определимся что карта островов будет int двумерным массивом, где: 0 - пустое пространство 1 - маленький остров 2 - средний остров 3 - большой остров
И так, создаём скрипт "IslandMapGenerator", и первым делом задаём перечисление (enum) с нашими островами (заодно видны настройки класса, он не статический, и не Monobehavior)
Далее, я хочу подвязать генерацию карты с seed, вы видели это наверное во всех играх с процедурной генерацией мира. Если нет, то это необходимо чтобы создать эдакий "ключ генерации", при котором мир будет генерироваться каждый раз одинаково.
Конструктором мы будем определять экземпляр класса, задавая ему при инициализации seed
Метод GenerateIslandsMap()
Далее пишем основной метод, который и будет возвращать 2d массив с островами, она же наша карта островов
Метод принимает следующие параметры:
worldSize - размер мира, то есть размер нашего 2d массива b|m|s_islandCount - количество островов каждого типа, которые необходимо генерировать.
Далее метод использует другой метод PlaceIslands() (который вы еще не написали, да) для каждого вида островов, начинаем с больших, и заканчиваем маленькими.
Возвращает метод int[,] islandsMap, то бишь необходимый нам результат в виде 2д массива с будущими островами, пока что это просто цифорки)
Метод PlaceIslands()
Теперь, собственно, перейдем к методу PlaceIslands() :
Метод принимает 2d массив карты (1), который мы определили методом ранее, размер мира (2), количество островов (3) и тип острова (4).
Он что делает... Он выполняет цикл while пока : А Не будет создано необходимое кол-во островов. Б количество попыток/итераций превысит лимит.
В каждой попытке он через наш _random определяет случайную координату для нашего массива, и... И вызывает еще один метод (Обожаю, сука, методы в методах!) TryPlaceIsland(), и если этот метод успешен (вернул true), то мы увеличиваем значение созданных островов на 1 (сама пометка ячейки под цифру острова происходит "глубже")
Метод TryPlaceIsland()
Принимает в параметрах этот метод следующее: 1. 2d массив карты 2. размер мира 3 и 4. координаты в которых будем генерироваться (пытатся) остров, ну и вид острова.
Он через Switch определяет вид острова, и в зависимости от вида, вызывает ЕЩЕ ОДИН ВЛОЖЕННЫЙ МЕТОД (Эти последние, правда! (нет)), для каждого вида острова свой, зощем? Да потому что у каждого вида острова своя логика размещения.
(3) Большие острова - это 4 ячейки вместе (2х2), и соотв. за раз метод красит 4 ячейки и проверяет отсутствие соседей вокруг этих 4 ячеек, а не вокруг одной.
(2) Средние острова могут быть горизонтальными или же вертикальными, и состоят из 2х соседних ячеек
(1) Ну а с мелкими всё и так понятно
И наконец последние 3 метода, которые и выполняют всю логику пометок ячеек массива соотв. числом.
Все следующие методы будут принимать один и тот же набор параметров: 1. 2d массив карты 2. размер мира 3. позицию генерации х (внутри массива) и позицию y
Метод для большого острова TryPlaceBigIsland()
1) Первым делом мы проверяем что наша координата массива (startX и startY) не находятся в правом крайнем, или нижнем ряду массива, ведь тогда при попытке создать 4 ячейки острова он просто выпадет в ошибку (выход за границы массива)
2)
Далее мы циклом перебираем все 4 ячейки будущего большого острова (красной точкой я отметил нашу startX | startY ячейку), внутри которых выполняем метод (этот точно последний, простите) IsCellValidForIsland(), метод выполняет проверку всех соседей вокруг ячейки, и возвращает true если соседей нет. (Код этого метода напишу в конце)
3) Если все 4 ячейки успешно прошли проверку этим методом, тогда мы переходим к третьему циклу, в котором мы точно так же проходимся по всем 4м нашим ячейкам, и передаем им значение 3 ((int)IslandSize.Big это 3)
Метод для среднего острова TryPlaceMediumIsland()
Так как средний остров может быть либо вертикальным, либо горизонтальным, тут 2 поведения, в зависимости от значения horizontal, которое мы определяем через простенькое выражение (1) _random.Next(0, 2), генерирует случайное число в заданном диапазоне, и если == 0, то горизонтальная ориентация, иначе вертикальная.
2) Делаем в принципе аналогичные действия что и для большого острова, только для 2х ячеек а не для 4х, и со смещением на одну вправо по x
3) Аналогично, но смещаемся на одну ячейку вниз по y
Метод для маленького острова TryPlaceSmallIsland()
Ну тут все максимально просто, проверили соседей, влепили ячейке 1
Ну сам метод проверки отсутствия соседей IsCellValidForIsland()
Как я уже написал ранее, этот метод принимает в параметрах координаты ячейки, проверяет все ячейки вокруг (от x -1 y -1 до x+1 y+1) и возвращает истину если ни одна из этих ячеек не занята, и не выходит за границы массива
На этом всё! Код который будет нам генерировать шаблон размещения островов по сиду в принципе готов. Надо только теперь его проверить как-то, хотя бы схематично и для отладки
Давайте создадим еще скрипт (В этот раз стандартный Monobehavior ), и назовём его WorldGenerator, и повесим его на новый объект в сцене, и назовем его World
И напишите в скрипте следующий код:
DebugPrintIslandsMap() выведет в консоль игры текстовый вариант карты, которую мы в параметре ему передаем
А в Start объявляется в переменную генератор наш IslandMapGenerator, с сидом 123, создается маленькая карта через генератор, 10 ячеек, 2 больших острова, 3 средних и четыре маленьких, и выполняется метод вызова в консоль.
У меня при заданном сиде 505 получился вот такой результат, немного повело из-за разницы в размере символ, но в целом читается.
На этом первую часть думаю стоит завершить, слишком много текста получилось. Пока что довольно непрезентабельно и примитивно, но я сам вызвался писать поэтапно) А это по сути просто массив-заготовка. Всё еще впереди)