Как я делал CandyCrash на Unity3D( Часть 3)

Продолжение урока Как я делал CandyCrash на Unity3D( Часть 2)


08) Рекурсия как основа поиска

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


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

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

    Итак когда мы проверяем 1 объект игры мы хотим знать с какой стороны у нас есть соседи и мы предполагаем что они есть со всех 4 сторон.

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

    Тем самым получиться что мы будем бесконечно проверять друг друга пока игрок или пользователь программы не закроет ее и не пойдет не дай бог на улицу заниматься спортом )).

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

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

    Добавляем в скрипт chage эти 2 переменные.

Unity3D общая переменная для класса

using UnityEngine;

using System.Collections;

public class Change : MonoBehaviour {

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

public Sprite[] ImgList;

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

SpriteRenderer Sprite;

//номер выбранного элемента массива

public int IndexImg=0;

/*список найденных элементов*/

GameObject[] FindList;

/*счет количества найденных элементов*/

int FindListCounter=0;

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

    Добавляем определение длинны массива в функцию OnMouseDown;

Unity3D OnMouseDown задание размера массива

/*добавляем обработчик нажатия мышкой*/

void OnMouseDown() 

{

/*получаем список кубиков с одним цветом*/

GameObject[] O = GameObject.FindGameObjectsWithTag (tag);

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

FindList = new GameObject[O.Length];

//на всякий случай обнулим счетчик найденных объектов

FindListCounter=0;

}

    Создадим собственную функцию которая будет удалять из списка объектов тот который мы уже проверили и добавлять его в массив проверенных.

Unity3D Duble

//передаем объект который нужно извлечь из массива

GameObject[] Duble(GameObject thetObgect,GameObject[] O)

{

/*уберем текущий объект из списка*/

GameObject[] T = new GameObject[O.Length - 1];

/*индекс для подсчета элементов*/

int I = 0;

/*сравниваем все объекты ищем наш*/

foreach (GameObject F in O) 

if (thetObgect.Equals (F) != true) 

T [I++] = F;

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

FindList [FindListCounter] = thetObgect;

//и увеличиваем на 1 количество хранимых в массиве элементов

FindListCounter++;

//возвращаем массив без нашего объекта

return T;

}

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

Процесс работы функции.

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

    2) Далее получаем у объекта значения для координат и рассчитываем адреса соседей которых нужно проверить. 

    3) Следующим шагом мы перебираем объекты соседей и сверяем их координаты. 

      а) Если мы находим такого соседа то мы запускаем функцию Search и передаем в нее соседа и текущий массив. И это запускает рекурсивно нашу функцию которая переходит на шаг 1. 

    Таким образом функция не закончит свою работу пока не переберет всех соседей которые у ей достались.

Unity3d Search рекурсивный поиск

void Search(GameObject thetObgect,GameObject[] O)

{

/*Убираем из массива выбранный объект*/

O = Duble (thetObgect, O);

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

Vector3[] Sosed = GenCoordTest (thetObgect.transform.position);

//перебираем оставшиеся объекты и ищем соседей

foreach (GameObject F in O) {

Vector3 ObjectPosition=F.transform.position;

//перебираем координаты и ищем соседей

foreach(Vector3 V in Sosed) 

if(V==ObjectPosition) 

{

//нашли объект то запускаем Search снова

Search(F,O);

//и завершаем поиск

break;

}

}

}

    Мне приодеться еще не раз изменять параметр indeximg у отдельных объектов поэтому напишем функцию которая будет принимать на входе объект и выставлять параметры индекса картинки в нулевое значение. Назовем FreeImage

Unity3d FreeImage смена значения в поле компонета

void FreeImage (GameObject O)

{

/*получаем компонент*/

Change F= O.GetComponent();

/*задаем значение*/

F.IndexImg=0;

}

    Итак у после завершения поиска у нас в массиве FindList будет список смежных блоков с одинаковым цветом  и их количество в параметре FindListCounter. Простым циклом For переберем этот массив и для каждого объекта из него вызовем функцию FreeImage для скрытия его с газ долой и из сердца вон.

Unity3d FreeImage OnMouseDown() применение цикла FOR

/*добавляем обработчик нажатия мышкой*/

void OnMouseDown() 

{

/*получаем список кубиков с одним цветом*/

GameObject[] O = GameObject.FindGameObjectsWithTag (tag);

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

FindList = new GameObject[O.Length];

//на всякий случай обнулим счетчик найденных объектов

FindListCounter=0;

/*запускаем поиск соседей*/

Search (this.gameObject, O);

/*прячем все найденные элементы*/

for(int I=0; I < FindListCounter; I++) FreeImage(FindList[I]);

}

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

    Итак в результате проверки работы нашей игры появляются две ошибки 1 если игрок случайно нажмет на пустое место код как не в чем не бывало найдет все пустые мести и обнулит их. Это мы исправим указав одно условие если tag объекта на который нажал игрок будет равен "0" значит ничего не будем делать. Добавим условие в функцию OnMouseDown

Текстовое поле

 void OnMouseDown()

    {

        /*если tag не равен 0 то выполняем поиск*/

       if (tag != "0")

        {

            /*получаем список кубиков с одним цветом*/

            GameObject[] O = GameObject.FindGameObjectsWithTag(tag);

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

            FindList = new GameObject[O.Length];

            //на всякий случай обнулим счетчик найденных объектов

            FindListCounter = 0;

            /*запускаем поиск соседей*/

            Search(this.gameObject, O);

            /*прячем все найденные элементы*/

            for (int I = 0; I < FindListCounter; I++) FreeImage(FindList[I]);

       }

    }

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

Unity3D объявляем публичную переменную

public class Change : MonoBehaviour {

    /*Объявим параметр проверки количества соседей */

    public int R = 1;

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

    public Sprite[] ImgList;

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

    SpriteRenderer Sprite;

    //номер выбранного элемента массива

    public int IndexImg = 0;

    /*список найденных элементов*/

    GameObject[] FindList;

    /*счет количества найденных элементов*/

    int FindListCounter = 0;

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

Unity3D вводим ограничения для уничтожения объектов

 /*добавляем обработчик нажатия мышкой*/

    void OnMouseDown()

    {

        /*если tag не равен 0 то выполняем поиск*/

        if (tag != "0")

        {

            /*получаем список кубиков с одним цветом*/

            GameObject[] O = GameObject.FindGameObjectsWithTag(tag);

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

            FindList = new GameObject[O.Length];

            //на всякий случай обнулим счетчик найденных объектов

            FindListCounter = 0;

            /*запускаем поиск соседей*/

            Search(this.gameObject, O);

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

            if (FindListCounter >= R) //проверяем параметр

            /*прячем все найденные элементы*/

            for (int I = 0; I < FindListCounter; I++) FreeImage(FindList[I]);

        }

    }

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

Код скрипта Chage

using UnityEngine;

using System.Collections;

public class Change : MonoBehaviour {

    /*Обьявим параметр проверки количества соседей */

    public int R = 1;

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

    public Sprite[] ImgList;

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

    SpriteRenderer Sprite;

    //номер выбранного элемента массива

    public int IndexImg = 0;

    /*список найденных элементов*/

    GameObject[] FindList;

    /*счет количества найденных эдементов*/

    int FindListCounter = 0;

    // Функция срабатывает  1 раз при создании объекта

    void Start()

    {

        /*получаем случайное число от 0 до 8*/

        IndexImg = Random.Range(1, 8);

        //получаем доступ к компоненту

        Sprite = GetComponent();

    }

    // Функция срабатывает  каждый раз при обновлении экрана

    void Update()

    {

        /*при обновлении экрана задаем картинку по номеру из списка*/

        Sprite.sprite = ImgList[IndexImg];

        /*задаем уникальный tag для данного типа картинки*/

        tag = IndexImg.ToString();

    }

    /*функция создания массива координат*/

    Vector3[] GenCoordTest(Vector3 T)

    {

        /*Объявим массив из 4 точек*/

        Vector3[] S = new Vector3[4];

        /*задаем координаты для точек*/

        S[0] = T; S[0].x++; //правая точка

        S[1] = T; S[1].x--; //левая точка

        S[2] = T; S[2].y++; //верхняя точка

        S[3] = T; S[3].y--; //нижнаяя точка

        /*возвращаем позиции для проверки*/

        return S;

    }

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

    GameObject[] Duble(GameObject thetObgect, GameObject[] O)

    {

        /*уберем текущий обьект из списка*/

        GameObject[] T = new GameObject[O.Length - 1];

        /*индекс для посчета элементов*/

        int I = 0;

        /*сравниваем все обьекты ищем наш*/

        foreach (GameObject F in O)

            if (thetObgect.Equals(F) != true)

                T[I++] = F;

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

        FindList[FindListCounter] = thetObgect;

        //и увеличиваем на 1 количество хранимых в массиве элементов

        FindListCounter++;

        //возвращаем массив без нашего обьекта

        return T;

    }

    void Search(GameObject thetObgect, GameObject[] O)

    {

        /*Убираем из массива выбранный объект*/

        O = Duble(thetObgect, O);

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

        Vector3[] Sosed = GenCoordTest(thetObgect.transform.position);

        //перебираем оставщиеся объекты и ищем соседей

        foreach (GameObject F in O)

        {

            Vector3 ObjectPosition = F.transform.position;

            //перебираем координаты и ищем соседей

            foreach (Vector3 V in Sosed)

                if (V == ObjectPosition)

                {

                    //нашли обьект то запускаем Search снова

                    Search(F, O);

                    //и завершаем поиск

                    break;

                }

        }

    }

    void FreeImage(GameObject O)

    {

        /*получаем компонент*/

        Change F = O.GetComponent();

        /*задаем значение*/

        F.IndexImg = 0;

    }

    /*добавляем обработчик нажатия мышкой*/

    void OnMouseDown()

    {

        /*если tag не равен 0 то выполняем поиск*/

        if (tag != "0")

        {

            /*получаем список кубиков с одним цветом*/

            GameObject[] O = GameObject.FindGameObjectsWithTag(tag);

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

            FindList = new GameObject[O.Length];

            //на всякий случай обнулим счетчик найденных объектов

            FindListCounter = 0;

            /*запускаем поиск соседей*/

            Search(this.gameObject, O);

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

            if (FindListCounter >= R) //проверяем параметр

            /*прячем все найденные элементы*/

            for (int I = 0; I < FindListCounter; I++) FreeImage(FindList[I]);

        }

    }

}

09) Добавляем физику к блокам

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

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

    Для этого опишем функцию MoveDown в скрипте Change.

1) получаем список всех пустых блоков  GameObject.FindGameObjectsWithTag("0");.

2) получаем координаты текущего блока и далее рассчитываем координату под ним просто вычитая из позиции по Y единицу

3) Если наши координаты совпали с пустым блоком то записываем в него значения из текущего блока а текущий обнуляем.

   Реализуем логику работы функции MoveDown.

Unity3D описание функции движения блока вниз MoveDown()

 void MoveDown()

    { 

        /*получаем список пустых блоков*/

        GameObject[] f = GameObject.FindGameObjectsWithTag("0");

        /*получаем текущие координаты блока*/

        Vector3 Coord = this.transform.position;

        /*рассчитываем смещение по Y для проверки координат*/

        Coord.y--;

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

         перебираем все пустые блоки

         */

        foreach (GameObject FreeBlok in f) 

        {

            /*получаем координаты объекта и сравниваем их*/

            Vector3 V = FreeBlok.transform.position;

            if (Coord == V) 

            {

                /*нашли блок, тогда записываемся в него*/

                /*получаем компонент*/

                Change F = FreeBlok.GetComponent();

                /*задаем значение*/

                F.IndexImg = IndexImg;

                /*очищаем значение текущего блока*/

                IndexImg = 0;

                /*дальше нет смысла искать останавливаем цикл*/

                break;

            }

        }

    }

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

Unity3D проверка движения блока в низ при каждом кадре в функции Update()

 void Update()

    {

        /*при обновлении экрана задаем картинку по номеру из списка*/

        Sprite.sprite = ImgList[IndexImg];

        /*задаем уникальный tag для данного типа картинки*/

        tag = IndexImg.ToString();

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

        MoveDown();

    }

    Теперь когда мы будем удалять элементы блоки будут самостоятельно проверять есть ли в низу место и если есть то перемещаться туда

10) делаем игру бесконечной

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

    В статье 9) Добавляем физику к блокам мы проверяли что там под блоком и перемещали его в низ. А теперь мы посмотрим что там над блоком если там пустой блок то просто запишем в него случайный цвет, используя генератор случайных чисел.

    Назовем функцию RandBox() которая будет выполнять следующие действия:

1) получаем все элементы с 0 значением т.е. пустые

2) далее получаем координаты текущего элемента.

3) рассчитываем координаты блока над выбранным

4) Если нашли в массиве пустых блоков совпадение по координатам то настраиваем его случайным образом. 

Unity3D создаем случайные блоки функцией RandBox()

void RandBox()

    {

            /*получаем список пустых блоков*/

            GameObject[] f = GameObject.FindGameObjectsWithTag("0");

            /*получаем текущие координаты блока*/

            Vector3 Coord = this.transform.position;

            /*расчитваем смещение по Y для проверки координат*/

            Coord.y++;

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

             перебираем все пустые блоки

             */

            foreach (GameObject FreeBlok in f)

            {

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

                Vector3 V = FreeBlok.transform.position;

                if (Coord == V)

                {

                    /*нашли блок, тогда записываемся в него*/

                    /*получаем компонент*/

                    Change F = FreeBlok.GetComponent();

                    /*задаем значение*/

                    F.IndexImg = Random.Range(1, 8);

                    /*очищаем значение текущего блока*/

                    /*дальше нет смысла искать останавливаем цикл*/

                    break;

                }

            }

     

    }

    Итак в функции Update будем проверять какое состояние у блока. Если он пуст будем вызывать функцию RandBox() если нет то будем вызывать функцию MoveDown();

    Добавляем код в функцию Update()

Unity3D обновляем функцию Update()

  void Update()

    {

        /*при обновлении экрана задаем картинку по номеру из списка*/

        Sprite.sprite = ImgList[IndexImg];

        /*задаем уникальный tag для данного типа картинки*/

        tag = IndexImg.ToString();

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

       if(tag!="0") MoveDown(); else RandBox();

    }

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

    Все игра готова. Ура.



298 0 850 2
0
RENDER.RU