Создаем игру Змейка 3d в Unity 5 + видео

Snake3D Уроки

В этой статье я хочу рассказать вам как в игровом движке Unity 5 создать игру Змейка 3d. В игре будет реализована сама змея, которая будет двигаться в определенном направлении, поедать яблоки и расти. За поедание яблок будут начисляться очки, но если змея врежется не в яблоко а в другой предмет (препятствие или стену) игра будет окончена.

Подготовка сцен.

Для начала подготовим сцены, откройте новый проект и создайте три новых сцены, назовем их : MainMenu, Level и GameOver. Начнем со сцены MainMenu, эта сцена будет отвечать за наше игровое меню и за загрузку следующего уровня. Создадим новый С# скрипт MainMenu и перенесем его на камеру, откроем его и напишем такой код:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MainMenu : MonoBehaviour {

    public Vector2 menuSize = new Vector2(500, 300);

    // минимальная высота кнопки
    public float buttonMinHeight = 60f;

    // шрифт заголовка
    public Font captionFont;

    // шрифт кнопок
    public Font buttonFont;

    // тексты меню
    public string mainMenuText = "Menu";
    public string startButtonText = "Start Game";
    public string exitButtonText = "Exit Game";

    public void OnGUI()
    {
        // рассчитываем прямоугольник по центру экрана с заданным размером
        Rect rect = new Rect(
            Screen.width / 2f - menuSize.x / 2,
            Screen.height / 2f - menuSize.y / 2,
            menuSize.x,
            menuSize.y);

        // область меню
        GUILayout.BeginArea(rect, GUI.skin.textArea);
        {
            // создаем стиль заголовка на основе стиля label стандартного скина
            GUIStyle captionStyle = new GUIStyle(GUI.skin.label);
            // устанавливаем стилю заголовка шрифт captionFont
            captionStyle.font = captionFont;
            // Расположение текста по центру
            captionStyle.alignment = TextAnchor.MiddleCenter;

            // текст заголовка
            GUILayout.Label(mainMenuText, captionStyle);

            // создаем стиль кнопки на основе стиля button стандартного скина
            GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
            // устанавливаем стилю кнопки шрифт buttonFont
            buttonStyle.font = buttonFont;
            // отступы кнопок от краев
            buttonStyle.margin = new RectOffset(20, 20, 3, 3);




            // FlexibleSpace - автоматически рассчитанное место, необходимое для 
            // заполнения пустого пространства между элементами управления

            GUILayout.FlexibleSpace(); // динамическое пространство между заголовком и кнопкой старт

            // отрисовка кнопки Start и обработка ее нажатия
            if (GUILayout.Button(startButtonText, buttonStyle, GUILayout.MinHeight(buttonMinHeight)))
            {
                // загрузка сцены с именем Level
                SceneManager.LoadScene("Level");
            }

            GUILayout.FlexibleSpace(); // динамическое пространство между кнопками

            // отрисовка кнопки Exit и обработка ее нажатия
            if (GUILayout.Button(exitButtonText, buttonStyle, GUILayout.MinHeight(buttonMinHeight)))
            {
                // выход
                Application.Quit();
            }

            GUILayout.FlexibleSpace(); // динамическое пространство между кнопкой Exit и низом области меню

        }
        GUILayout.EndArea();

    }
}

Этот скрипт будет выводить наше меню на экран и при нажатии на кнопку Start загружать следующий уровень «Level». В редакторе можете изменить размеры меню и добавить свой шрифт в captionFont и buttonFont. В итоге должно получится примерно так…

mainmenu

Сцена Game Over.

Следующая сцена — это сцена Game Over. Эта сцена будет состоять из одно надписи и появляться тогда, когда наша змейка врежется в какой- нибудь предмет. Так же после небольшой паузы загрузится другая сцена с нашем меню.
Откроем созданную сцену и создадим еще один скрипт с названием GameOver. Переместим его так же на камеру и напишем в нем такой код:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class LoadLevel : MonoBehaviour {

    // время до загрузки уровня
    public float delay = 3;

    // имя загружаемого уровня
    public string levelName;

    public IEnumerator Start()
    {
        // задержка на заданное число секунд
        yield return new WaitForSeconds(delay);



        // загрузка уровня с указанным именем
        SceneManager.LoadScene("MainMenu");
    }
}

Скрипт готов, теперь добавим GUItext и настроим так, чтобы надпись Game Over находилась примерно по центру экрана…

gameover

Сцена Level.

Следующая сцена — сцена Level.
Здесь немного посложнее, для начала создадим ограждения. Добавьте на сцену 4 куба и расположите их на расстоянии 50 от начальной точки, изменим их размеры и добавим под них плоскость, чтобы получилось примерно так…

level1
Добавим на сцену сферу с позицией 0,0,0 ,с размерами 2,2,2 и с поворотом 0,0,90, это будет голова нашей змейки.

snake

К этой сфере добавим компонент character controller, который будет отвечать за движение.
Настроим камеру так, чтобы она следила за нашей змейкой. Поверните камеру по х на 90 градусов и расположите ее в позиции 0,30,0 и создайте новый скрипт LookAt, который будет следить за головой змейки:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LookAt : MonoBehaviour {

    // цель, на которую должен смотреть объект
    public Transform target;

    public void Update()
    {
        if (target != null)
        {
            // Смотрим всегда на цель
            transform.LookAt(target);
        }
    }
}

Перенесите его на камеру и в строке target выберете нашу сферу.

camera

Добавим еще одну сферу, это будет наша еда. Создадим еще один скрипт и назовем его food:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Food : MonoBehaviour {

    // количество очков, которое дает еда при съедании
    public int points = 10;

    public void Update()
    {
        // вращаем еду со скоростью 60 градусов в секунду
        transform.Rotate(Vector3.up, 60 * Time.deltaTime);
    }
    public void Eat()
    {
        // прибавляем очки еды к общему числу очков
        Game.points += points;
        
        // уничтожаем объект еды
        Destroy(gameObject);
        GenerateNewFood();
        
        
    }
    // функция создания новой еды
    public static void GenerateNewFood()
    {
        // создаем экземпляр еды, предварительно загружая префаб из ресурсов
        GameObject food = (GameObject)Instantiate(Resources.Load("Prefabs/Food", typeof(GameObject)));
        // цикл подбора положения еды
        while (true)
        {
            // ставим еду в рандомное место
            food.transform.position = new Vector3(Random.Range(-40, 41), 0, Random.Range(-40, 41));
            // получаем размер ее колайдера в мировых координатах
            Bounds foodBounds = food.GetComponent().bounds;

            bool intersects = false;

            // Проверяем со всеми колайдерами кроме колайдера самой еды.
            foreach (Collider objectColiider in FindObjectsOfType(typeof(Collider)))
            {
                if (objectColiider != food.GetComponent())
                {
                    // если пересекается, то завершаем цикл, досрочно
                    if (objectColiider.bounds.Intersects(foodBounds))
                    {
                        intersects = true;
                        break;
                    }
                }

            }



            // установили в нужное место, останавливаем цикл установки
            if (!intersects)
            {
                break;
            }
        }
    }

}

Этот скрипт будет размещать префаб еды при старте сцены, давать очки при съедании объекта, удалять объект со сцены и создавать новую еду в рандомном месте. Перемещаем его на модель еды и сохраняем модель в префаб в папку Resources/Prefabs под названием food.

food

На данном этапе у вас может возникнуть ошибка в строке

  Game.points += points;

Здесь подразумевается что есть скрипт под названием Game c переменной points. Давайте его создадим:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Game : MonoBehaviour {

    // материал стен
    public Material wallMaterial;

    // набранные очки
    public static int points;

    // количество стен в уровне
    public int countWals = 10;

    private string _pointsString;
    private int _lastPonts = -1;

    // генерируем уровень при загрузке сцены
    public void Awake()
    {
        // обнуляем очки
        points = 0;

        // генерируем уровень
        GenerateLevel();

        // ставим первую еду
        Food.GenerateNewFood();
    }

    public void Update()
    {
        // обновление отображаемого текста очков только при их изменении
        if (_lastPonts == points) return;



        _lastPonts = points;
        // форматируем очки в формате четырех цифр, начинающихся с нулей
        _pointsString = "Score: " + points.ToString("0000");
    }


    // отрисовка набранных очков
    public void OnGUI()
    {
        GUI.color = Color.yellow;
        GUI.Label(new Rect(20, 20, 200, 20), _pointsString ?? "");
    }

    // функция генерации уровня
    private void GenerateLevel()
    {
        for (int i = 0; i < countWals; i++)
        {
            // создаем куб
            GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
            // называем его "Wall"
            wall.name = "Wall";
            // увеличиваем его габариты
            wall.transform.localScale = new Vector3(2, 2, 2);

            // расставляем его так, чтобы координаты были не в центре игрового поля
            var pos = new Vector3(Random.Range(-40, 41), 0, Random.Range(-40, 41));
            while (Mathf.Abs(pos.x) < 5 || Mathf.Abs(pos.z) < 5)
            {
                pos = new Vector3(Random.Range(-40, 41), 0, Random.Range(-40, 41));
            }
            wall.transform.position = pos;
            // и назначаем материал
            wall.GetComponent().material = wallMaterial;
        }

    }
}

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

Создадим пустышку на сцене и перенесем на нее этот скрипт, так же можно указать материал который будет накладываться на препятствия.

wall

Можете запустить игру и проверить что мы наделали. Как видите препятствия и еда генерируется в свободных местах, но змейка наша пока не двигается, этим мы сейчас и займемся.
Создадим еще один скрипт и назовем его Player:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

[RequireComponent(typeof(CharacterController))]
public class Player : MonoBehaviour

{
    // скорость перемещения - 6 единиц в секунду по умолчанию
    // в редакторе можно поменять
    public float speed = 6;
    private bool _testing = false;
    
    public GameObject SnakeBody;
    public List BodySnake = new List { };

    // аналогично скорость вращения 60 градусов в секунду по умолчанию
    public float rotationSpeed = 60;

    // локальная переменная для хранения ссылки на компонент CharacterController
    private CharacterController _controller;
    public void AddTile() {
        Vector3 Position = transform.position;
        if (BodySnake.Count > 0) {
            Position = BodySnake[BodySnake.Count - 1].transform.position;
        }
        Position.y+=10f;
        GameObject Body = Instantiate(SnakeBody, Position, Quaternion.identity) as GameObject;
        BodySnake.Add(Body);
    }

    public void Start()
    {
        BodySnake.Clear();
        for (int i = 0; i < 5; i++) AddTile();
        _controller = GetComponent();
        }


    public void Update()
    {
         SnakeStap();
        

        // получаем значение горизонтальной оси ввода
        float horizontal = Input.GetAxis("Horizontal");



        // вращаем трансформ вокруг оси Y 
        transform.Rotate(rotationSpeed * Time.deltaTime * horizontal,0, 0);
        
        // двигаем змею постоянно
        _testing = true; // маленкий хинт, для того, чтобы не обрабатывать несколько коллизий за кадр
        _controller.Move(transform.forward * speed * Time.deltaTime);

    }
    void SnakeStap() {
        if (BodySnake.Count > 0)
        {
            BodySnake[0].transform.position = transform.position;
            for (int BodyIndex = BodySnake.Count - 1; BodyIndex > 0; BodyIndex--)
                BodySnake[BodyIndex].transform.position = BodySnake[BodyIndex - 1].transform.position;
        }
    }
    public void OnControllerColliderHit(ControllerColliderHit hit)
    {
        if (_testing)
        {
            Food food = hit.collider.GetComponent();
            if (food != null)
            {
                // врезались в еду, "съедаем" ее
                food.Eat();
                AddTile();
            }
            else
            {
                // врезались не в еду
                SceneManager.LoadScene ("GameOver");
            }
            _testing = false;
        }
    }

}

Этот скрипт будет отвечать за движение нашей змейки, создавать хвост и увеличивать значение хвоста при взятии еды или загружать сцену GameOver при столкновении с препятствием. Переместите его на голову змейки. Добавьте еще один префаб в папку resouces, простую сферу (необходимо удалить sphere collider), это будет наш хвост. В скрипте Player укажите в строке Snake Body этот префаб.

body

Ну вроде все, запустите проект, но перед этим добавьте сцены в настройки проекта file- build settings

build

game

Пишите в комментариях ваши вопросы, постараемся на них ответить. Удачных проектов!

Архив с проектом можно скачать здесь.

Скачать игру можно здесь.

Видео созданной игры после небольшой визуальной доработке и добавления звуков

Предыдущий урок

Следующий урок

Видео

Поделиться или сохранить к себе:
Технологичная помощь
Добавить комментарий

Нажимая на кнопку "Отправить комментарий", я даю согласие на обработку персональных данных, принимаю Политику конфиденциальности и условия Пользовательского соглашения.

  1. Дима

    Ссылка на архив, есть ли свежая ?

    Ответить