Как я делал змейку на Unity3D( Часть 2)

3) Движение персонажа

 Итак теперь нужно объявить 2 переменные на основе которых мы будем решать в какую сторону будет двигаться наша змея они будут изменяться от -1 до +1 т.е. если в переменной 0 то мы по этой оси не перемещаемся а если в переменной 1 то мы движемся в одну сторону, если -1 то в обратную.


Unity3D Объявляем переменные для указания направления движения

//Объявляем переменные для указания направления движения змеи

    int Dx = 0, Dy = 0;

    Плюсы редактора игр Unity3D в том что большинство вещей он умеет делать сам итак игрок может по разному управлять движением змеи во первых он может изменять направление с помощью клавиш стрелок на клавиатуре а может с помощью клавиш WASD как привыкли большинство игроков по всему миру. Это учли разработчики и позволили создать группу кнопок которые выполняют одно действие данные группы называются Axis. Чтобы увидеть какие кнопки были заданы для тех или иных комбинаций достаточно открыть в редакторе пункт Edit >> Project Settings >> Input

    Нас будут интересовать два значения это управление стрелками и управление клавишами SAWD. Значит получение значений при нажатии кнопок по горизонтали называется Horizontal а по вертикали называется Vertical. 

    Итак опишем функцию которая будет получать значение от данных параметров и задавать значение направление нашей змейке. Во время нажатия стрелок по одной оси движение по другой мы будем останавливать чтобы наша змейка перемещалась по диагонали хотя ничего ей это не запрещает делать и возможно игра от этого бы даже выиграла. Но в оригинале такого не было поэтому сделаем как в оригинале.

Unity3D обработка нажатия клавишь InputKey()

 //Функция обработки нажатия клавиш

    void InputKey()

    {

        //получаем что нажал игрок по горизонтали по Х

        float X = Input.GetAxis("Horizontal");

        //если X больше 0 значит задаем DX будет равен 1

        if (X > 0)

        {

            Dx = 1;     Dy = 0;

        }

        //если X меньше 0 значит задаем DX будет равен -1

        else if (X < 0)

        {

            Dx = -1;    Dy = 0;

        }

        //получаем что нажал игрок по вертикали по У

        float Y = Input.GetAxis("Vertical");

        //если Y больше 0 значит задаем DY будет равен -1

        if (Y > 0)

        {

            Dx = 0;     Dy = -1;

        }

        //если Y меньше 0 значит задаем DY будет равен 1

        else if (Y < 0)

        {

            Dx = 0;     Dy = 1;

        }

    }

    Данная функция не будет срабатывать при нажатии на клавиши пока ее не добавить в процедуру обновления игрового процесса. Так как она не является встроенной и зарегистрированной в система хотя по мне это странно. Итак не суть добавляем ее вызов в функцию Update

Unity3D Функция обновления окна игры

// Функция обновления окна игры

void Update () {

   //перерисовка окна

        DrawSnake(); 

        //обработка нажатия клавиш

        InputKey();

}

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

Unity3D описываем функцию двигаем персонажа

 //двигаем персонажа

    void movesnake()

    {

        //если указано направление для движения то двигаемся

        if ((Dx != 0) || (Dy != 0))

        {

            //смещаем змею с хвоста в сторону головы

            for (int I = SizeSnake - 1; I > 0; I--) Snake[I] = Snake[I - 1];

            //двигаем голову в новую точку

            Snake[0].IndexX += Dx;

            Snake[0].IndexY += Dy;

        }

    }

    Для того чтобы функция срабатывала при каждом обновлении экрана нужно ее добавить в функцию Update(); 

Unity3d Двигаем персонажа функцией Update

// Функция обновления окна игры

void Update () {

   //перерисовка окна

        DrawSnake(); 

        //обработка нажатия клавиш

        InputKey();

        //Двигаем персонажа если возможно 

        movesnake();

}

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

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

Unity3D проверяем выход персонажа за границы карты

 //двигаем персонажа

    void movesnake()

    {

        //если указано направление для движения то двигаемся

        if ((Dx != 0) || (Dy != 0))

        {

            //смещаем змею с хвоста в сторону головы

            for (int I = SizeSnake - 1; I > 0; I--) Snake[I] = Snake[I - 1];

            //двигаем голову в новую точку

            Snake[0].IndexX += Dx;

            Snake[0].IndexY -= Dy;

            //если мы вылезли на граници поля то 2 варианат 1 мы вылезаем с другой стороны либо умераем

            //Проверяем координаты по оси X

            if (Snake[0].IndexX > 29) Snake[0].IndexX = 0;

            if (Snake[0].IndexX < 0) Snake[0].IndexX = 29;

            //проверяем по оси Y

            if (Snake[0].IndexY > 19) Snake[0].IndexY = 0;

            if (Snake[0].IndexY < 0) Snake[0].IndexY = 19;

        }

    }

   Как можно вспомнить когда мы генерировали карту мы ее делали размерами по X=30 а по Y=20 именно проверку на эти границы мы и добавили.

    После запуска игры и указании нашей змейки направления  с помощью клавиш мы увидим как змейка улетает в один угол экрана но не упирается в стену а выходит с другой стороны и продолжает свой путь. 

    Змейка летает в нужном нам направлении  с невероятной скоростью. И это мы сейчас исправим. Дело в том что перерисовка окна происходит максимально быстро силами видео карты. Но нам нужно чтобы движения были более медленными. Для этого функцию движения персонажа будем запускать с задержкой в определенное количество миллисекунд. Это сделать довольно просто зададим переменную в которую поместим значение времени которое было во время движения змейки. Далее зададим параметр скорость который будет означать то количество времени которое должно пройти до следующего момента движения. И далее будем считывать при каждом кадре текущее время. Вычитая из текущего время тот момент когда была перерисована змейка в один прекрасный момент мы получим что разница во времени чуть больше чем скорость в этот момент мы запустим процесс движения снова запомнив время когда это движение было выполнено и так по кругу. Таким образом наша скорость будет привязана к  секундам прошедшим в реальном времени а не к мощности вашего устройства. 

    Объявим глобальную переменную в начале кода которая будет означать скорость игры и сделаем ее публичной. Возможно захочется во время работы проекта поэкспериментировать с этим параметром что публикация его нам позволит реализовать.  И еще две переменные которые будут нужны для расчета времени.

Unity3D Объявляем параметр скорости персонажа

 //Объявляем параметр скорости персонажа

    public float speedSnake = 0.6f;

    //объявляем 2 переменные для хранения 

    float   RenderTime,  //время перерисовки

           CurrentTime; //Текущее время

   Опишем в функции Update задержку перед движением персонажа.

Update добавляем управления скорости движения персонажа

// Функция обновления окна игры

void Update () {

        //перерисовка окна

        DrawSnake(); 

        //обработка нажатия клавиш

        InputKey();

        //получаем текущее время

        CurrentTime = Time.time;

        //получаем разницу между текущим временем и когда был сделан ход змейки

        float alpha = CurrentTime - RenderTime;

        if (alpha > speedSnake)

        {

            //Двигаем персонажа если возможно 

            movesnake();

            //запоминаем время когда двинули персонажа

            RenderTime = Time.time;

        }

}

    Код будет выглядеть так 

    Теперь персонаж будет перемещаться медленнее и плавнее.

    Следующая неприятность которую мы устраним это то что след от хвоста остается в тех местах где прошла змейка. Если бы мы были следопытом и выслеживали ее по пути следования думаю было бы интересно но сейчас это выглядит не очень хорошо. Итак что нужно сделать. В   функции которая отвечает за движение змейки мы рисовали голову у первого блока рассчитав его позицию. По аналогии последний элемент нужно просто закрасить. Так как последний элемент смещается на одну клетку а в той клетке где он располагался уже ничего нет. Добавим данное действие в функцию.

Unity3D movesnake()

  //двигаем персонажа

    void movesnake()

    {

        //если указано направление для движения то двигаемся

        if ((Dx != 0) || (Dy != 0))

        {

//*******************************************************************

            //затираем прозрачным цветом последнюю часть хвоста

            int L = SizeSnake - 1;

            //если змейка существует 

            if (L > 0) 

                //закрашиваем цвет

                SetColor(MapArea[Snake[L].IndexX, Snake[L].IndexY], 0);

//*******************************************************************

            //смещаем змею с хвоста в сторону головы

            for (int I = SizeSnake - 1; I > 0; I--) Snake[I] = Snake[I - 1];

            //двигаем голову в новую точку

            Snake[0].IndexX += Dx;

            Snake[0].IndexY -= Dy;

            //если мы вылезли на граници поля то 2 варианат 1 мы вылезаем с другой стороны либо умераем

            //Проверяем координаты по оси X

            if (Snake[0].IndexX > 29) Snake[0].IndexX = 0;

            if (Snake[0].IndexX < 0) Snake[0].IndexX = 29;

            //проверяем по оси Y

            if (Snake[0].IndexY > 19) Snake[0].IndexY = 0;

            if (Snake[0].IndexY < 0) Snake[0].IndexY = 19;

        }

    }

    Теперь наш игрок обрел скорость и не оставляет следов за собой при движении персонажа по полю.


4) Расстановка бонусов на поле

    Итак наша змейка умеет перемещаться по уровню, умеет заходить с разных сторон на поле но пока не умеет расти. Чтобы запускать процесс роста нужно разбросать на поле яблоки которые она будет есть. Хотя логичнее для змеи разбросать на поле зайцев но это уже 18+ и живодерство. Чтобы нарисовать на поле яблоко достаточно блоку присвоить номер картинки 3. Функция будет делать следующие действия выбирать на поле одну свободную ячейку случайным образом и рисовать одно яблоко. Далее при старте игры мы запустим ее столько раз сколько мы хотим увидеть яблок на экране.
Опишем данную функцию в виде кода все в том же скрипте MainGameScript и создадим процедуру CreateEat().

Unity3D функция создания бонусов

  //создаем функцию случайного размещения яблок на поле

    void CreateEat()

    {

        //флаг смогли ли установить яблоко

        bool Insert_Apple = true;

        //параметры яблока

        int

            CoordX, //координата бонуса по оси Х

            CoordY; //координата бонуса по оси У

       //выполняем действие пока яблоко не найдет себе место

        while (Insert_Apple)

        {

            //получаем случайные 2 координаты

            CoordX = Random.Range(0, 29);

            CoordY = Random.Range(0, 19);

            Debug.Log("X "+CoordX+" Y "+CoordY);

            //если полученные координаты не выходят за границы массива

           

           //смотрим какой цвет у части карты 

                int cvet = GetColor(MapArea[CoordX, CoordY]);

                //если в блоке картинка с номером 0 

                if (cvet == 0)

                {

                //задаем цвет картинки 

                    SetColor(MapArea[CoordX, CoordY], 3);

                //и останавливаем цикл установки яблока

                    Insert_Apple = false;

                }

         }

    }

    Данную функцию вызовем например 10 раз при старте игры.

Unity3D функция добавления еды в случайные места

// Настройка параметров при старте игры

void Start () {

//Задаем координаты для камеры

Vector3 CameraPose = new Vector3 (14.5f, 9.5f, -10);

//так как скрипт находиться на том же объекте то задаем позицию так

this.transform.position = CameraPose;

//перед заданием параметра Size есго нужно получить из камеры

Camera C = this.GetComponent ();

//задаем параметр

C.orthographicSize = 10;

        //Генерация карты игры

GenMap ();

        //Добавляем 2 блока к игроку

        for (int I = 0; I < 2; I++) 

            //вызываем функцию добавления к змейке части

            GrowSnake();

        //добавляем еду змейке

        for (int I = 0; I < 10; I++) 

             CreateEat();

}

   При запуске игры мы увидим как на поле разбросанные яблоки.

    Но если мы наедем на данный бонус нашем героем то ничего не произойдет это потому что мы не проверяем при движении героя куда он наступил. Итак давайте добавим реакцию игрока на прикосновение к бонусу. Для этого создадим новую функцию и назовем ее TestStep(); проверка шага.

Unity3D добавление функции проверки куда наступил игрок TestStep()

 //функция проверки куда наступил персонаж

    void TestStep() 

    {

        //получаем место куда должна переместится голова

        int HeadX = Snake[0].IndexX,

            HeadY = Snake[0].IndexY;

        //смотрим что находилось в данной точке

        int Step = GetColor(MapArea[HeadX, HeadY]);

        //проверяем куда наступил персонаж

        switch (Step)

        {

                //если наступили на яблоко то запускаем рост змеи

            case 3:

                GrowSnake();

                //помещаем яблоко в другом месте

                CreateEat();

                break;

        }

    }

    Далее во время движения персонажа в функцию movesnake() добавляем проверку куда наступил игрок. 

Unity3D двигаем персонажа

    //двигаем персонажа

    void movesnake()

    {

        //если указано направление для движения то двигаемся

        if ((Dx != 0) || (Dy != 0))

        {

            //затираем прозрачным цветом последнюю часть хвоста

            int L = SizeSnake - 1;

            //если змейка существует 

            if (L > 0) 

                //закрашиваем цвет

                SetColor(MapArea[Snake[L].IndexX, Snake[L].IndexY], 0);

            //смещаем змею с хвоста в сторону головы

            for (int I = SizeSnake - 1; I > 0; I--) Snake[I] = Snake[I - 1];

            //двигаем голову в новую точку

            Snake[0].IndexX += Dx;

            Snake[0].IndexY -= Dy;

            //если мы вылезли на граници поля то 2 варианат 1 мы вылезаем с другой стороны либо умераем

            //Проверяем координаты по оси X

            if (Snake[0].IndexX > 29) Snake[0].IndexX = 0;

            if (Snake[0].IndexX < 0) Snake[0].IndexX = 29;

            //проверяем по оси Y

            if (Snake[0].IndexY > 19) Snake[0].IndexY = 0;

            if (Snake[0].IndexY < 0) Snake[0].IndexY = 19;

            //добавляем проверку куда наступил игрок 

            TestStep();

        }

    }

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

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

Unity 3D код игрового процесса в файле MainGameScript()

using UnityEngine;

using System.Collections;

public class MainGameScript : MonoBehaviour {

    //Объявляем параметр скорости персонажа

    public float speedSnake = 0.6f;

    //обьявляем 2 переменные для хранения 

    float RenderTime,  //время перерисовки

          CurrentTime; //Текущее время

//переменная для связи с блоком карты из которых она будет строиться

public GameObject Block;

//карта игры 20 на 20 точек

GameObject[,] MapArea =new GameObject[30,20];

//структура описания одной части тела змеи

struct Telo 

{

public int IndexX, IndexY;

}

//обьявим тело змеи длинной в 600 элементов

Telo[] Snake = new Telo[600];

//Длинна змеи

int SizeSnake=0;

    //Объявляем переменные для указания направления движения змеи

    int Dx = 0, Dy = 0;

    //создаем функцию случайного размещения яблок на поле

    void CreateEat()

    {

        //флаг смогли ли установить яблоко

        bool Insert_Apple = true;

        //параметры яблока

        int

            CoordX, //координата бонуса по оси Х

            CoordY; //координата бонуса по оси У

       //выполняем действие пока яблоко не найдет себе место

        while (Insert_Apple)

        {

            //получаем случайные  2 координаты

            CoordX = Random.Range(0, 29);

            CoordY = Random.Range(0, 19);

            Debug.Log("X "+CoordX+" Y "+CoordY);

            //если полученные координаты не выходят за границы массива

           

           //смотрим какой цвет у части карты 

                int cvet = GetColor(MapArea[CoordX, CoordY]);

                //если в блоке картинка с номером 0 

                if (cvet == 0)

                {

                //задаем цвет картинки 

                    SetColor(MapArea[CoordX, CoordY], 3);

                    //и останавливаем цикл установки яблока

                    Insert_Apple = false;

                }

         }

    }

    //функция проверки куда наступил персонаж

    void TestStep() 

    {

        //получаем место куда должна переместится голова

        int HeadX = Snake[0].IndexX,

            HeadY = Snake[0].IndexY;

        //смотрим что находилось в данной точке

        int Step = GetColor(MapArea[HeadX, HeadY]);

        //проверяем куда наступил персонаж

        switch (Step)

        {

                //если наступили на яблоко то запускаем рост змеи

            case 3:

                GrowSnake();

                //помещаем яблоко в другом месте

                CreateEat();

                break;

        }

    }

    //Функция отображения змейки 

    void DrawSnake()

    {

        //перебираем весь хвост пока не дойдем до его максимальной длинны

        for (int I = 1; I < SizeSnake; I++)

            //задаем картинку блоку вида 0 картинки

            SetColor(MapArea[Snake[I].IndexX, Snake[I].IndexY], 2);

           

        //если змейка существует и ее размер >0

        if (SizeSnake > 0) 

            //Для 0 блока змейки задаем что это голова

            SetColor(MapArea[Snake[0].IndexX, Snake[0].IndexY], 1);

    }

//функция получения цвета у объекта

int GetColor(GameObject O){

//читаем нужный нам компонент из объекта

MyMap ComonentObject=O.GetComponent();

//получаем номер используемой картинки

int Imgcolor = ComonentObject.IndexImg;

//возвращаем цвет выбранного блока

return Imgcolor;

}

void SetColor(GameObject O, int Set_color)

{

//читаем нужный нам компонент из объекта

MyMap ComonentObject=O.GetComponent();

//Задаем номер используемой картинки

ComonentObject.IndexImg = Set_color;

}

    //функция роста змеиного хвоста

    void GrowSnake()

    {

        //смотрим какой длинны хвост

        if (SizeSnake == 0)

        {

            //стартовая точка будет 10 10 примерно центр экрана

            Snake[SizeSnake].IndexX = 10; 

            Snake[SizeSnake].IndexY = 10;

        }

        else 

        { 

            //если хвост есть то добавляем его к последней части

            Snake[SizeSnake].IndexX = Snake[SizeSnake-1].IndexX;

            Snake[SizeSnake].IndexY = Snake[SizeSnake-1].IndexY;

        }

        //увеличиваем длину

        SizeSnake++;

    }

    //Функция обработки нажатия клавиш

    void InputKey()

    {

        //получаем что нажал игрок по горизонтали по Х

        float X = Input.GetAxis("Horizontal");

        //если X больше 0 значит задаем DX будет равен 1

        if (X > 0)

        {

            Dx = 1;     Dy = 0;

        }

        //если X меньше 0 значит задаем DX будет равен -1

        else if (X < 0)

        {

            Dx = -1;    Dy = 0;

        }

        //получаем что нажал игрок по вертикали по У

        float Y = Input.GetAxis("Vertical");

        //если Y больше 0 значит задаем DY будет равен -1

        if (Y > 0)

        {

            Dx = 0;     Dy = -1;

        }

        //если Y меньше 0 значит задаем DY будет равен 1

        else if (Y < 0)

        {

            Dx = 0;     Dy = 1;

        }

    }

void GenMap(){

//точка отсчета

Vector3 PointStart=new Vector3(0,0,0);

//запускаем цикл расстановки по Y

for (int Y=0; Y<20; Y++) {

//запускаем цикл расстановки по X

for (int X=0; X<30; X++) {

//сдвигаем точку 

PointStart=new Vector3(X,Y,0);

//создаем блок в указанном месте

MapArea [X, Y] = (GameObject)Instantiate (Block, PointStart, Quaternion.identity);

}

}

}

// Настройка параметров при старте игры

void Start () {

//Задаем координаты для камеры

Vector3 CameraPose = new Vector3 (14.5f, 9.5f, -10);

//так как скрипт находиться на том же объекте то задаем позицию так

this.transform.position = CameraPose;

//перед заданием параметра Size есго нужно получить из камеры

Camera C = this.GetComponent ();

//задаем параметр

C.orthographicSize = 10;

        //Генерация карты игры

GenMap ();

        //Добавляем 2 блока к игроку

        for (int I = 0; I < 2; I++) 

            //вызываем функцию добавления к змейке части

            GrowSnake();

        //добавляем еду змейке

        for (int I = 0; I < 10; I++) 

             CreateEat();

}

   

    //двигаем персонажа

    void movesnake()

    {

        //если указано направление для движения то двигаемся

        if ((Dx != 0) || (Dy != 0))

        {

            //затираем прозрачным цветом последнюю часть хвоста

            int L = SizeSnake - 1;

            //если змейка существует 

            if (L > 0) 

                //закрашиваем цвет

                SetColor(MapArea[Snake[L].IndexX, Snake[L].IndexY], 0);

            //смещаем змею с хвоста в сторону головы

            for (int I = SizeSnake - 1; I > 0; I--) Snake[I] = Snake[I - 1];

            //двигаем голову в новую точку

            Snake[0].IndexX += Dx;

            Snake[0].IndexY -= Dy;

            //если мы вылезли на граници поля то 2 варианат 1 мы вылезаем с другой стороны либо умераем

            //Проверяем координаты по оси X

            if (Snake[0].IndexX > 29) Snake[0].IndexX = 0;

            if (Snake[0].IndexX < 0) Snake[0].IndexX = 29;

            //проверяем по оси Y

            if (Snake[0].IndexY > 19) Snake[0].IndexY = 0;

            if (Snake[0].IndexY < 0) Snake[0].IndexY = 19;

            //добавляем проверку куда наступил игрок 

            TestStep();

        }

    }

   

// Функция обновления окна игры

void Update () {

        //перерисовка окна

        DrawSnake(); 

        //обработка нажатия клавиш

        InputKey();

        //получаем текущее время

        CurrentTime = Time.time;

        //получаем разницу между текущим временем и когда был сделан ход змейки

        float alpha = CurrentTime - RenderTime;

        if (alpha > speedSnake)

        {

            //Двигаем персонажа если возможно 

            movesnake();

            //запоминаем время когда двинули персонажа

            RenderTime = Time.time;

        }

}

}


252 0 850 1
0
RENDER.RU