Читать нас в Telegram

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

Рубрика: Переводы
,
24616

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

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

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

Да будет свет!

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

Позиционируем источник света

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

Добавляем в проект C#-скрипт

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

Используемые скрипты

Прежде чем мы продолжим, вам крайне рекомендуется ознакомиться со скриптингом в 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!");
        }
    }
}

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

Применяем скрипт к GameObject

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

Компонент скрипта

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

Тестируем нажатия кнопок мыши

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

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

Для простоты мы назовем центр куба, по которому кликают, буквой C, а центр блока, который должен появиться — N. Мы рассматриваем эти центры как позиции в 3D-пространстве.

Мы можем обобщить сказанное выше в простую формулу: 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 и модифицируйте его:

/* 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 на него.

Перетаскиваем WorldGenerator.cs на новый GameObject

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

Размеры больше указанных выставлять не стоит

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

И всё-таки гравитация важна

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

Хинт для программистов: если зарегистрируетесь на соревнования Huawei Honor Cup, бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.

Перейти к регистрации

Перевод статьи «Build Minecraft in Unity»

, full-stack ньюсрайтер