Думаю, почти все читатели Хабра слышали про майнкрафт, кто-то играл в сингле, кто-то на одном из многочисленных серверов, был даже небольшой сервер у кого-то из хабраюзеров. После двух месяцев игры я задумался — а реально ли написать свой генератор карты? Как оказалось, это вполне возможно сделать за несколько дней неторопливого гугления и кодинга.
Немного технической части
- 4096 байт, содержащих оффсеты чанков (так называются блоки 16x16x128) и их размер в блоках по 4кб, округляя вверх, 3 байта оффсет, 1 — размер
- 4096 байт timestamp’ов чанков, по 4 байта на каждый
- Оставшееся место до конца файла — собственно, данные чанков, сжатые Zlib. 4 байта — размер сжатых данных, 1 — способ сжатия (по умолчанию 2, Zlib (RFC1950)), размер-1 запакованная злибом NBT-структура, т.е сам контейнер кубов
Если упакованные данные занимают меньше целого числа секторов по 4 кб, то остаток сектора заполняется нулями, т.к каждый чанк должен начинаться с оффсета, выраженного целым числом секторов по 4096 байт
Выбор языка
Реализовать такую структуру можно на любом языке, я остановился Delphi 7. Во-первых, это пока единственный язык, который я знаю, во-вторых, именно на 7 версии года 4 назад я начинал писать блокноты по мануалам из Игромании.
Я создал ИДЕАЛЬНУЮ ГЕНЕРАЦИЮ в Minecraft / Майнкрафте
Код
Так как данные хранятся в сжатом виде, нам необходим модуль zlib.
Я использовал ZlibEx
Для начала создадим класс чанка, в который будем впоследствии писать данные
Tchunk = class(TObject) private public Data: tmemorystream; c_data: tmemorystream; c_stream: tzcompressionstream; constructor Create; procedure writeblock(x, y, z, block: integer); overload; procedure writeblock(x, y, z, block, color: integer); overload; procedure compress; end;
Код этого класса:
constructor tchunk.Create; begin Data := TMemoryStream.Create; Data.size := 82360; Data.LoadFromFile(‘data.bin’); c_data := TMemoryStream.Create; c_stream := tzcompressionstream.Create(c_data, zcdefault, 15, 8, zsdefault); end; procedure tchunk.writeblock(x, y, z, block: integer); begin Data.Seek(form1.getoffset(x, y, z) + 16487, 0); Data.Write(block, 1); end; procedure tchunk.compress; var buffer: array [0..82360] of byte; begin c_data.Position := 0; Data.Position := 0; Data.Read(buffer, 82360); c_stream.writebuffer(buffer, 82360); c_stream.Free; c_data.SaveToFile(‘file’ + IntToStr(n)); end;
Функция getoffset выдает нужое смещение по формуле y + ( z *128 + ( x * 128 * 16 ) )
function tform1.getoffset(x, y, z: integer): integer; begin Result := y + (z * 128 + (x * 128 * 16)); end;
Добавим в var пару переменных:
chunks:array[0..32] of array[0..32] of tchunk; n: integer=0;
Процедура для сборки всех чанков в готовый файл:
procedure tform1.SwapEndiannessOfBytes(var Value: cardinal); var tmp: cardinal; i: integer; begin tmp := 0; for i := 0 to sizeof(Value) — 1 do Inc(tmp, ((Value shr (8 * i)) and $FF) shl (8 * (sizeof(Value) — i — 1))); Value := tmp; end; procedure tform1.generatefile; var fileoffset: integer; time, compressiontype, counter: integer; filename: string; regionfile: tfilestream; tmp: cardinal; size: integer; n_x, n_z: integer; bu: array[0..99999] of byte; n: integer; roundedsize: integer; neededsize: integer; d: byte; begin fileoffset := 2; time := $d8de2f4e; compressiontype := $02; filename := GetVar(‘Appdata’) + ‘.minecraftsavesNewWorldregionr.0.0.mcr’; regionfile := tfilestream.Create(filename, fmcreate); n := 0; for n_x := 0 to 31 do for n_z := 0 to 31 do begin chunks[n_x][n_z].compress; roundedsize := ((chunks[n_x][n_z].c_data.Size) div 4096); if (((chunks[n_x][n_z].c_data.Size) mod 4096) > 0) then Inc(roundedsize); regionfile.seek((4 * ((n_x mod 32) + (n_z mod 32) * 32)), 0); tmp := fileoffset; SwapEndiannessOfBytes(tmp); tmp := tmp shr 8; regionfile.Write(tmp, 4); regionfile.seek(4 * ((n_x mod 32) + (n_z mod 32) * 32) + 3, 0); regionfile.Write(roundedsize, 1); size := chunks[n_x][n_z].c_data.Size + 1; regionfile.seek(fileoffset * 4096, 0); tmp := size; SwapEndiannessOfBytes(tmp); regionfile.Write(tmp, 4); regionfile.Write(compressiontype, 1); chunks[n_x][n_z].c_data.Position := 0; chunks[n_x][n_z].c_data.readbuffer(bu, chunks[n_x][n_z].c_data.size); regionfile.Writebuffer(bu, chunks[n_x][n_z].c_data.size); regionfile.seek((n) * 4 + 4096, 0); regionfile.Write(time, 4); fileoffset := fileoffset + ((chunks[n_x][n_z].c_data.Size) div 4096); if (((chunks[n_x][n_z].c_data.Size) mod 4096) > 0) then fileoffset := fileoffset + 1; Inc(n); end; neededsize := 4096 * fileoffset — regionfile.Size — 1; regionfile.Seek(regionfile.Size, 0); d := 00; for n := 0 to neededsize do regionfile.Write(d, 1); regionfile.Free; end;
Всё, теперь мы имеем метод записи любого блока по любой координате, в пределах региона. При желании, несложно повторить то же для остальных регионов, надо строк 10 кода.
ОСНОВЫ WorldPainter. КАК СОЗДАВАТЬ СВОЮ ГЕНЕРАЦИЮ МИРА?
Обертка для writeblock:
procedure tform1.writeworld(x, y, z, block: integer); var xw, zw: integer; begin xw := (x div 16); zw := (z div 16); chunks[xw][zw].writeblock(x mod 16, y, z mod 16, block); end;
Генерация мира, его сжатие и сохранение.
procedure TForm1.Button4Click(Sender: TObject); var x, y, z: integer; xx, zz: integer; image: tbitmap; begin for xx := 0 to 31 do for zz := 0 to 31 do begin chunks[xx][zz] := tchunk.Create; end; image := tbitmap.Create; image.LoadFromFile(‘image.bmp’); for x:=0 to 127 do for y:=0 to 116 do begin if image.Canvas.Pixels[x,y]=clblack then form1.writeworld(x,117-y,0,49); if image.Canvas.Pixels[x,117-y]=clwhite then form1.writeworld(x,y,0,80); end; form1.generatefile;
Результат:
Можно генерировать не только пиксельарт, но произвольные фигуры, все, что можно задать какой-либо формулой. Например, пол в виде синусоиды:
Проект можно скачать тут.
- Невозможно сохранять изменения в сгенерированном регионе (возможно, из-за того, что пишется одинаковый timestamp, который не совпадает с временем последнего сохранения в level.dat, как разберусь с форматом последнего — попробую реализовать)
- Спавн лучше переставить с помощью McEdit, т.к вполне возможно, что после генерации он окажется в сотне блоков над землей, что чревато летальным исходом(тоже можно менять в level.dat)
- Нет генерации света, вместо этого освещены все блоки, даже под землей(Рассчет освещения — отдельная серьёзная задача, пока не готов ее решать)
- Починить сохранение, т.к без этого теряется половина смысла
- Сделать поддержку записи дополнительной инфы(цвет шерсти, листвы, ориентация печек, etc) // частично готово
- Какое-то подобие ландшафта(холмы/дома/озера)
- Доработал generatefile , сделал нормальный разворот
- Форматирование кода
- Наброски a dditional block data , см. в проекте, ссылка обновлена
- Описание формата карт
- ID блоков для функции writeblock/writeworld
- Zlib для Delphi
Источник: habr.com
Создание Minecraft на Unity3D. Часть вторая. Генерация мира
Это вторая часть руководства по созданию собственной Minecraft-подобной игры. В ней мы напишем генератор мира и добавим персонажа, который сможет перемещаться по миру, ставить и удалять блоки. В предыдущей части мы создали куб с текстурой.
Добавляем функциональность мыши
Прежде чем мы приступим к программированию, давайте добавим на нашу сцену направленный свет. Нам нужен источник света, чтобы лучше видеть наш 3D-мир.
Вы можете поменять направление света, если хотите:
Позиционируем источник света
Теперь мы можем перейти к написанию нашего первого скрипта. Мы хотим реализовать функционал добавления нового экземпляра куба около стороны, на которую мы щелкаем правой кнопкой мыши, и удаления существующего блока по нажатию левой клавиши. Во вкладке Project перейдите в папку Code и создайте два C#-скрипта.
Добавляем в проект C#-скрипт
Мы назовем их WorldGenerator и ClickOnFaceScript.
Прежде чем мы продолжим, вам крайне рекомендуется ознакомиться со скриптингом в Unity.
Теперь откройте WorldGenerator.cs в MonoDevelop (которая уже установлена вместе с Unity) двойным щелчком по нему и введите следующий код:
Мы используем статический метод, чтобы создать клон нашего куба. Unity позволяет нам задать имя и позицию клона. Больше информации о методе Instantiate вы можете получить здесь.
В Minecraft, если вы находитесь достаточно близко к кубу и кликаете по нему левой кнопкой мыши, он исчезает и появляется в вашем инвентаре. Когда вы кликаете правой клавишей на любую из сторон куба, новый экземпляр появится на той стороне, по которой вы кликнули, в случае, если у вас достаточно материала в инвентаре. В данном уроке мы не будем ставить перед собой ограничения, так что у нас будет бесконечное количество кубов и бесконечная дистанция клика.
Обучение ручному тестированию с оплатой после трудоустройства , онлайн, Беcплатно
Откройте ClickOnFaceScript.cs и введите туда этот код:
/* Andrei Jifcovici * In2GPU.com */ using UnityEngine; using System.Collections; public class ClickOnFaceScript : MonoBehaviour < // Эта функция вызывается, когда курсор находится над GameObject, на котором этот скрипт расположен void OnMouseOver() < // Если нажата левая клавиша мыши if (Input.GetMouseButtonDown(0)) < // Выводим сообщение в консоль Debug.Log(«Left click!»); >// Если правая клавиша нажата if (Input.GetMouseButtonDown(1)) < // Выводим сообщение Debug.Log(«Right click!»); >> >
Теперь переместите скрипт на каждую из шести сторон куба на сцене.
Применяем скрипт к GameObject
Скрипт должен появиться на каждой стороне во вкладке Inspector.
Давайте запустим игру. Нажмите и проверьте, что отладочные сообщения появляются, когда вы кликаете по кубу во вкладке Game.
Тестируем нажатия кнопок мыши
Запомните! В режиме игры любые изменения, которые вы произвели с элементами во вкладке Scene, будут отменены. Не меняйте ничего, пока игра запущена. Нажмите еще раз, чтобы остановить игру.
Теперь у нас есть все что нужно, чтобы создавать новые кубы по клику правой клавишей мыши. Мы должны определить точную позицию нового GameObject, который будет появляться при клике. На нашей сцене расстояние между центрами двух соседних блоков равно единице.
Для простоты мы назовем центр куба, по которому кликают, буквой C, а центр блока, который должен появиться — N. Мы рассматриваем эти центры как позиции в 3D-пространстве.
- Если мы кликаем на верхнюю грань: N = C + (0, 1, 0);
- На нижнюю: N = C + (0, -1, 0);
- На правую: N = C + (1, 0, 0);
- На левую: N = C + (-1, 0, 0);
- На переднюю: N = C + (0, 0, 1);
- На заднюю: N = C + (0, 0, -1).
Мы можем обобщить сказанное выше в простую формулу: N = C + delta, где delta — это смещение, требуемое для расчета центра нового блока. Каждая из шести сторон содержит свой экземпляр ClickOnFaceScript и разное значение delta.
Мы должны изменить ClickOnFaceScript.cs, чтобы реализовать функционал, описанный выше. Откройте скрипт и измените файл таким образом:
/* Andrei Jifcovici * In2GPU.com */ using UnityEngine; using System.Collections; public class ClickOnFaceScript : MonoBehaviour < // Значения публичных полей можно поменять прямо из редактора // Хранит смещение, требуемое для расчета позиции нового объекта public Vector3 delta; // Эта функция вызывается, когда курсор находится над GameObject, на котором этот скрипт расположен void OnMouseOver() < // Если нажата левая клавиша мыши if (Input.GetMouseButtonDown(0)) < // Выводим сообщение в консоль Debug.Log(«Left click!»); // Уничтожаем блок, по которому кликнули Destroy(this.transform.parent.gameObject); >// Если правая клавиша нажата if (Input.GetMouseButtonDown(1)) < // Выводим сообщение в консоль Debug.Log(«Right click!»); // Вызываем метод из класса WorldGenerator WorldGenerator.CloneAndPlace(this.transform.parent.transform.position + delta, // N = C + delta this.transform.parent.gameObject); // Родительский GameObject >> >
Вернитесь в редактор и поменяйте значения delta в соответствии с картинками:
Вся необходимая информация обведена
Проверьте, что все работает. Запустите игру несколько раз, задавая разные позиции камере (изменяя ее Transform во вкладке Inspector), чтобы проверить, что введенные нами значения delta верны.
Настраиваем позицию камеры и нажимаем на стороны кубов
Создаём персонажа
Если все работает, как задумано, мы можем перейти к созданию персонажа, чтобы мы могли свободно двигаться в нашей игре. К счастью для нас, Unity предоставляет готовый пакет с контроллером персонажа от первого лица, так что нам не нужно будет создавать его с нуля. Перейдите в Assets → Import Package и выберите Character Controller.
Импортируем пакет Character Controller
В окне Importing package выберите следующее:
Во вкладке Project перейдите в Standard Assets → Character Controllers, выберите First Person Controller.prefab и перетащите его во вкладку Hierarchy.
Заготовка First Person Character Controller
Расположите его близко к центру сцены.
Настраиваем местоположение заготовки
Мы должны защитить нашего персонажа от падения, прежде чем перейти к тестированию. Мы вернем значение обратно, когда закончим алгоритм генерации мира.
Гравитация не нужна!
Проверьте, что все работает.
У First Person Controller на нашей сцене есть Camera, прикрепленная, как дочерний GameObject. Когда мы запустим игру, эта камера станет главной на сцене, так что нам больше не нужна другая. Каждая камера содержит компонент AudioListener. Это и есть причина, по которой сообщение «There are 2 audio listeners in the scene.
Please ensure there is always exactly one audio listener in the scene.» появляется во вкладке Console. Отключите объект Main Camera во вкладке Hierarchy.
Отключаем главную камеру
Мы почти закончили! Откройте скрипт WorldGenerator.cs и модифицируйте его:
Скрипт будет запускаться только тогда, когда он прикреплен к какому-нибудь GameObject на сцене. Создайте пустой Empty GameObject и перетащите WorldGenerator.cs на него.
Перетаскиваем WorldGenerator.cs на новый GameObject
Перетащите объект Voxel на соответствующее поле в скрипте. Эта версия алгоритма генерации мира хранит все блоки в памяти, так что не рекомендуется задавать большие значения полям Size X, Size Y и Size Z, иначе вам грозит низкая производительность или, что еще хуже, Unity может вылететь.
Размеры больше указанных выставлять не стоит
Прежде чем мы увидим нашу разработку в действии, не забудьте поправить поле Gravity в скрипте, прикрепленном к Character Controller.
И всё-таки гравитация важна
Готово! Нажмите и веселитесь!
Источник: tproger.ru
Minecraft: Опции создания нового мира
Первым шагом игрока в Minecraft, когда он начинает игру, является создание нового мира в меню. На первый взгляд, это довольно простая функция, но когда игроки немного углубляются в опции, которые Mojang добавляла в программу запуска на протяжении многих лет, все становится намного сложнее. Оказывается, у игроков есть масса возможностей, когда дело доходит до создания своего мира и установки его правил.
К счастью, для тех, кому не важны изыски, опции по умолчанию всегда выбираются автоматически при первом входе игрока на экран, так что не нужно возиться. Однако игроки, которые хотят создать особый мир с конкретной картой или набором стартовых правил, могут захотеть ознакомиться с опциями экрана создания нового мира в Minecraft.
Опции создания нового мира в Minecraft
При входе на экран создания нового мира из главного меню Minecraft, экран разделен на две страницы. Первая страница содержит самые базовые настройки, и большинство игроков будут в порядке, настроив только их. Однако вторая страница содержит несколько более интересных функций, которые стоит проверить.
Основные параметры
Это первый экран, который увидят игроки, позволяющий изменить имя файла сохранения или название мира, сложность, режим игры и так далее. Ниже приводится описание всех настроек и того, чего игроки могут добиться с их помощью.
Это название будет отображаться в файле сохранения мира, а также в меню, когда игроки загружают свои миры. Оно будет отображаться в списке всех существующих миров. Его можно оставить как Новый мир, если у игроков нет названия, так как его можно отредактировать и изменить позже. Его основное назначение — внешний вид и идентификация.
В Minecraft есть несколько игровых режимов, некоторые из которых можно открыть в окне создания нового мира на пусковой установке. На этом экране игроки могут выбирать между режимом выживания, творческим режимом и хардкорным режимом. Другие режимы игры, такие как режим зрителя и режим приключений, доступны только через команды, когда игроки находятся внутри мира и имеют доступ к читам.
Творческий режим дает игрокам возможность летать и использовать любые предметы и блоки по своему усмотрению. Выживание — это хлеб и масло Minecraft, который заставляет игроков сражаться с врагами, собирать ресурсы и управлять своим голодом и здоровьем. Хардкор — это усовершенстWowанная, более сложная версия выживания, где смерть является окончательной.
Этот параметр определяет, насколько сложна игра, и влияет только на режим выживания. В режиме «Хардкор» игроки не могут выбрать сложность, поскольку она уже установлена, а в творческом режиме сложность не имеет никакого значения.
Игроки могут выбирать между мирным, легким, нормальным и трудным режимами. В «Мирном» не появляются враждебные мобы, а «Легкий», «Нормальный» и «Тяжелый» — это «обычные» версии Minecraft, с разным количеством здоровья и наносимого урона.
Находясь в мире, игроки могут вводить чат и даже использовать команды, известные как читы. Однако для того, чтобы иметь такую возможность, игроки должны сначала включить читы в меню создания мира. Опции могут быть как ВКЛ, так и ВЫКЛ.
Также можно включить читы в однопользовательском мире после создания мира. Игроки могут открыть мир для LAN, что позволит им выбрать, включены или выключены читы, на небольшом экране настроек перед открытием мира.
Пакеты данных — это тип модифицированного контента в игре, который может изменить функционирование игры. Сообщество создало тонну таких пакетов, поэтому игрокам следует изучить их возможности. Обычно это отличные изменения качества жизни, но некоторые добавляют в игру более интересные функции. Чтобы добавить пакет данных, нажмите на кнопку и переместите все желаемые пакеты данных слева направо, чтобы включить их.
Этот инструмент дает игрокам доступ к длинному списку правил, которые они могут подстроить под конкретный мир. Некоторые правила касаются мобов и спавна, в то время как другие более специфичны для игрока. Просмотрите список и отметьте то, что кажется интересным, так как это легко позволяет игрокам адаптировать мир по своему вкусу.
Дополнительные параметры
В нижней правой части первого экрана рядом с «Правилами игры» находится кнопка «Дополнительные параметры мира». Нажмите на нее, чтобы получить доступ к дополнительным опциям, которые позволяют игрокам изменять настройки мира.
В этой строке игроки могут вводить семена. Семена обычно представляют собой набор цифр, или это может быть слово или буквы, которые будут преобразованы в цифры, определяющие, какой именно мир и карту загружает игра. Семена отлично подходят для того, чтобы поделиться найденным игроками миром с другими.
Или, если они ищут определенный тип мира, они могут найти в Интернете интересные семена с крутыми возможностями и играть через эту конкретную карту. В Chunkbase есть карта семян, которая генерирует карты различных семян, которые затем могут быть загружены в игру, когда игроки копируют номер семени.
Структуры — это различные здания, которые случайным образом генерируются в мире Minecraft. Подземелья, аванпосты мародеров, иглу, деревни, океанские памятники и так далее — все это игра считает структурами. Если игроки по какой-то причине не хотят играть с включенными структурами, они могут отключить их, нажав эту кнопку.
World Type — это интересный модификатор, который немного изменит способ генерации мира Minecraft. Вариант Default просто генерирует обычный мир, как обычно, с нормальной генерацией биомов. Superflat сделает мир трехслойным, с бесконечной плоской пустошью, созданной из определенного блока. Расположенная под ним кнопка Customize позволяет игрокам выбрать, из чего будет состоять каждый слой.
Большие биомы будут имитировать генерацию мира по умолчанию, но размер биомов был значительно изменен, в результате чего на карте появятся массивные биомы. Игрокам не стоит ожидать большого разнообразия от этого типа мира. Наконец, в Amplified все характеристики генерации мира доведены до предела, с массивными горами и пиками. Как говорится в игре, это может оказаться непосильной ношей для слабых ПК, поэтому будьте осторожны при выборе этого варианта.
Игроки, которые не хотят начинать игру с пустыми запасами, могут в самом начале получить бонусный сундук. Этот сундук появится рядом с игроком, когда он появится в мире, и в нем будут различные предметы первой необходимости, которые помогут ему начать игру. Обычно там есть немного еды, деревянные доски или бревна, деревянные или каменные инструменты и тому подобное, чтобы помочь игрокам получить преимущество. Сундук также окружен четырьмя факелами.
Если у игроков есть предыдущий мир с настройками, которые они с удовольствием используют, но не хотят вручную вводить их, они могут импортировать настройки в новый мир с помощью этого инструмента.
Также обязательно почитайте, как найти жемчуг Эндера и узнайте все про красивые шейдеры в Майнкрафт.
Последнее обновление 05.01.2023
Источник: game-zoom.ru