Создание Minecraft на Unity3D. Часть вторая. Генерация мира

Отредактировано

33К открытий34К показов
Создание Minecraft на Unity3D. Часть вторая. Генерация мира

Это вторая часть руководства по созданию собственной Minecraft-подобной игры. В ней мы напишем генератор мира и добавим персонажа, который сможет перемещаться по миру, ставить и удалять блоки. В предыдущей части мы создали куб с текстурой.

Добавляем функциональность мыши

Прежде чем мы приступим к программированию, давайте добавим на нашу сцену направленный свет. Нам нужен источник света, чтобы лучше видеть наш 3D-мир.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 1

Вы можете поменять направление света, если хотите:

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 2

Теперь мы можем перейти к написанию нашего первого скрипта. Мы хотим реализовать функционал добавления нового экземпляра куба около стороны, на которую мы щелкаем правой кнопкой мыши, и удаления существующего блока по нажатию левой клавиши. Во вкладке Project перейдите в папку Code и создайте два C#-скрипта.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 3

Мы назовем их WorldGenerator и ClickOnFaceScript.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 4

Прежде чем мы продолжим, вам крайне рекомендуется ознакомиться со скриптингом в Unity.

Теперь откройте WorldGenerator.cs в MonoDevelop (которая уже установлена вместе с Unity) двойным щелчком по нему и введите следующий код:

			/* Andrei Jifcovici
* In2GPU.com
*/
using UnityEngine;
using System.Collections;
 
public class WorldGenerator : MonoBehaviour {
 
    // Используется для инициализации
    void Start() {
 
    }
 
    // Метод Update вызывается единожды каждый фрейм
    void Update() {
 
    }
 
    public static void CloneAndPlace(Vector3 newPosition, GameObject originalGameobject) {
        // Клон
        GameObject clone = (GameObject)Instantiate(originalGameobject,
                                                   newPosition,
                                                   Quaternion.identity);
        // Позиция
        clone.transform.position = newPosition;
        // Переименовывем
        clone.name = "Voxel@" + clone.transform.position;
    }
}
		

Мы используем статический метод, чтобы создать клон нашего куба. Unity позволяет нам задать имя и позицию клона. Больше информации о методе Instantiate вы можете получить здесь.

В Minecraft, если вы находитесь достаточно близко к кубу и кликаете по нему левой кнопкой мыши, он исчезает и появляется в вашем инвентаре. Когда вы кликаете правой клавишей на любую из сторон куба, новый экземпляр появится на той стороне, по которой вы кликнули, в случае, если у вас достаточно материала в инвентаре. В данном уроке мы не будем ставить перед собой ограничения, так что у нас будет бесконечное количество кубов и бесконечная дистанция клика.

Откройте 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!");
        }
    }
}
		

Теперь переместите скрипт на каждую из шести сторон куба на сцене.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 5

Скрипт должен появиться на каждой стороне во вкладке Inspector.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 6
Создание Minecraft на Unity3D. Часть вторая. Генерация мира 7

Давайте запустим игру. Нажмите  и проверьте, что отладочные сообщения появляются, когда вы кликаете по кубу во вкладке Game.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 8
Создание Minecraft на Unity3D. Часть вторая. Генерация мира 9

Запомните! В режиме игры любые изменения, которые вы произвели с элементами во вкладке 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 в соответствии с картинками:

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 10

Проверьте, что все работает. Запустите игру несколько раз, задавая разные позиции камере (изменяя ее Transform во вкладке Inspector), чтобы проверить, что введенные нами значения delta верны.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 11

Создаём персонажа

Если все работает, как задумано, мы можем перейти к созданию персонажа, чтобы мы могли свободно двигаться в нашей игре. К счастью для нас, Unity предоставляет готовый пакет с контроллером персонажа от первого лица, так что нам не нужно будет создавать его с нуля. Перейдите в Assets → Import Package и выберите Character Controller.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 12

В окне Importing package выберите следующее:

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 13

Во вкладке Project перейдите в Standard Assets → Character Controllers, выберите First Person Controller.prefab и перетащите его во вкладку Hierarchy.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 14

Расположите его близко к центру сцены.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 15

Мы должны защитить нашего персонажа от падения, прежде чем перейти к тестированию. Мы вернем значение обратно, когда закончим алгоритм генерации мира.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 16

Проверьте, что все работает.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 17

У 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.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 18

Мы почти закончили! Откройте скрипт WorldGenerator.cs и модифицируйте его:

			/* Andrei Jifcovici
 * In2GPU.com
 */
using UnityEngine;
using System.Collections;
 
public class WorldGenerator : MonoBehaviour 
{
    // Значения публичных полей можно поменять прямо из редактора
 
    // Перетащите сюда объект Voxel со сцены
    // Используется для создания новых экземпляров
    public GameObject Voxel;
 
    // Определяем размер мира
    public float SizeX;
    public float SizeZ;
    public float SizeY;
 
    // Используется для инициализации
    void Start() {
        // Стартуем поток генерации мира
        StartCoroutine(SimpleGenerator());
    }
  
    // Метод Update вызывается единожды каждый фрейм
    void Update() {
  
    }
  
    public static void CloneAndPlace(Vector3 newPosition, 
                                     GameObject originalGameobject) {
        // Клон
        GameObject clone = (GameObject)Instantiate(originalGameobject, 
                                                   newPosition, Quaternion.identity);
        // Позиция
        clone.transform.position = newPosition;
        // Переименовываем
        clone.name = "Cube@" + clone.transform.position;
    }
 
    IEnumerator SimpleGenerator() {
        // В этом потоке мы будем создавать 50 кубов за один фрейм
        uint numberOfInstances = 0;
        uint instancesPerFrame = 50;
   
        for(int x = 1; x <= SizeX; x++) {
            for(int z = 1; <= SizeZ; z++) {
                // Получаем случайную высоту
                float height = Random.Range(0, SizeY);
                for (int y = 0; y <= height; y++) {
                    // Расчитываем позицию для каждого блока
                    Vector3 newPosition = new Vector3(x, y, z);
                    // Вызываем метод, передавая ему новую позицию и экземпляр куба
                    CloneAndPlace(newPosition, Voxel);
                    // Инкрементируем numberOfInstances
                    numberOfInstances++;
 
                    // Если было достигнуто предельное количество экземпляров за фрейм
                    if(numberOfInstances == instancesPerFrame) {
                        // Сбрасываем numberOfInstances
                        numberOfInstances = 0;
                        // И ждем следующего фрейма
                        yield return new WaitForEndOfFrame();
                    }
                } 
            } 
        } 
    }
}
		

Скрипт будет запускаться только тогда, когда он прикреплен к какому-нибудь GameObject на сцене. Создайте пустой Empty GameObject и перетащите WorldGenerator.cs на него.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 19

Перетащите объект Voxel на соответствующее поле в скрипте. Эта версия алгоритма генерации мира хранит все блоки в памяти, так что не рекомендуется задавать большие значения полям Size X, Size Y и Size Z, иначе вам грозит низкая производительность или, что еще хуже, Unity может вылететь.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 20

Прежде чем мы увидим нашу разработку в действии, не забудьте поправить поле Gravity в скрипте, прикрепленном к Character Controller.

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 21
Создание Minecraft на Unity3D. Часть вторая. Генерация мира 22

Готово! Нажмите  и веселитесь!

Создание Minecraft на Unity3D. Часть вторая. Генерация мира 23
Следите за новыми постами
Следите за новыми постами по любимым темам
33К открытий34К показов