Advertisement
Guest User

MoveTetromino.cs

a guest
Sep 16th, 2019
129
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 19.25 KB | None | 0 0
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. // По умолчанию все классы создаются с наследованием от класса MonoBehaviour
  6. // Этот класс отвечает за движение объекта, к которому он прикреплен
  7. public class MoveTetromino : MonoBehaviour
  8. {
  9.     // Две обычные публичные переменные
  10.     // rotationPoint - точка поворота фигуры
  11.     // fallTime - интервал между падениями в секундах
  12.     // Такие переменные видны из редактора
  13.     // И их можно настраивать из редактора
  14.     // Также к ним можно обратиться из любого места любого другого скрипта
  15.     // При этом значение таких переменных будет приходить
  16.     // в скрипт со значением, сохраненным в редакторе
  17.     public Vector3 rotationPoint;
  18.     public float fallTime = 0.8f;
  19.  
  20.     // Обычная приватная переменная
  21.     // Ее видно только из конкретного экземпляра скрипта
  22.     // Другие скрипты и объекты ее увидеть не смогут
  23.     private float previousTime;
  24.  
  25.     // Публичная статическая переменная
  26.     // Такая переменная одна для всех экземпляров
  27.     // Ее не видно из редактора
  28.     // Но если ее поменять в одном экземпляре скрипта,
  29.     // то она поменяется во всех экземплярах всех объектов
  30.     // к которому прикреплен этот скрипт
  31.     public static int height = 20;
  32.     public static int width = 10;
  33.  
  34.     // Публичный статический массив
  35.     // В нем хранится один экземпляр поля с занятыми клетками
  36.     // Каждый скрипт (экземпляр) может обращаться
  37.     // к информации о занятых клетках
  38.     // Например, когда линяя собралась - удалять ее
  39.     // А когда фигура упирается вниз, то нужно где-то сохранить,
  40.     // какие ячейки заняты
  41.     // при старте скрипта все ячейки не просто 0, они пустые - null
  42.     // стоит посмотреть на тип Transform, это тип объекта
  43.     // это такой тип хранимых объектов, например, int хранит целые числа
  44.     // в Unity Transform фактически хранит ссылку на любой объект в сцене
  45.     // Transform есть у каждого объекта в сцене
  46.     // название Transform, а не (например) GameObject - видимо наследие из ранних версий Unity
  47.     // поэтому этот тип определят корень объекта также как и GameObject, когда мы хотим обратится к любому
  48.     // объекту в сцене, но в отличии от GameObject и к детям (в GameObject придется написать GameObject.Transform)
  49.     // в боле поздних версиях Unity сделали GameObject, но он не может существовать без Transform
  50.     // поэтому полностью функциональность ссылок на положение в сцене не стали убирать и оставили Transform
  51.     // в итоге, если расшифровать строку ниже, то на русском это будет звучать так
  52.     // приватный (видимый только объекту)
  53.     // статический (один для всех объектов MoveTetromino)
  54.     // типа Transform - хранит ссылки на любой физический объект в сцене
  55.     // [,] двухмерный массив
  56.     // с названием grid
  57.     // при первом вызове grid инициализируется (к нему будет приравнен)
  58.     // новый массив пустых ячеек (null - пустые ссылки, которые не на что не ссылаются)
  59.     // размерность массива [width, height], т.е. 10 на 20, где 10 - столбцы, 20 - строки
  60.     private static Transform[,] grid = new Transform[width, height];
  61.  
  62.     // Start вызывается до первого нарисованного кадра один раз
  63.     void Start()
  64.     {
  65.        
  66.     }
  67.  
  68.     // Update вызывается один раз за кадр
  69.     // При 60 кадрах в секунду вызовется 60 раз
  70.     void Update()
  71.     {
  72.         // Если нажата кнопка влево
  73.         if(Input.GetKeyDown(KeyCode.LeftArrow))
  74.         {
  75.             // Двигаем Объект, к которому прикреплен скрипт на значение переменной
  76.             // Vector3 - удобная организация трех пар чисел в одну переменную
  77.             transform.position += new Vector3(-1, 0, 0);
  78.             // Сдвинули и проверили, что куда сдвинули там не занято
  79.             // Идея в том, что т.к. это происходит в одном кадре,
  80.             // то пользователь не успеет увидеть, что объект подвинули
  81.             // проверили, а потом вернули назад, если фигура не влезла
  82.             if(!ValidMove())
  83.                 // Если занято - вернули назад
  84.                 transform.position -= new Vector3(-1, 0, 0);
  85.         }
  86.         // Если не нажата кнопка влево, но нажата кнопка вправо
  87.         // Тут все аналогично движению влево
  88.         else if (Input.GetKeyDown(KeyCode.RightArrow))
  89.         {
  90.             transform.position += new Vector3(1, 0, 0);
  91.             if (!ValidMove())
  92.                 transform.position -= new Vector3(1, 0, 0);
  93.         }
  94.         // Если не влево и не вправо, то если нажата вверх
  95.         else if (Input.GetKeyDown(KeyCode.UpArrow))
  96.         {
  97.             // Поворачиваем вокруг точки, которая получена из публичной переменной rotationPoint
  98.             // Поворот происходит относительно локальной начальной точки объекта + rotationPoint
  99.             transform.RotateAround(transform.TransformPoint(rotationPoint), new Vector3(0,0,1), 90);
  100.             // аналогично, если поворот приводит к пересечению - повернуть назад
  101.             if (!ValidMove())
  102.                 transform.RotateAround(transform.TransformPoint(rotationPoint), new Vector3(0, 0, 1), -90);
  103.         }
  104.  
  105.         // Тут все просто, движение фигуры вниз
  106.         // Допустим, текущее время относительно старта программы
  107.         // 22,3 секунды - это функция Time.time
  108.         // previousTime - предыдущее время, когда мы попали в тело проверки
  109.         // например, 21,8 секунды
  110.         // fallTime можно изменить из редактора, но по умолчанию это 0,8 секунды
  111.         // 22,3 - 21,5 = 0,5 секунды
  112.         // Если кнопка не нажата, то сравнение идет с 0,8 и 0,5 явно меньше 0,8
  113.         // Если кнопка нажата, то сравнение идет с 0,8 / 10 = 0,08 секунды
  114.         // тут использован тернарный оператор, это как вложенный if
  115.         // тернарный оператор <проверочное значение> ? <если проверочное значение истина> : <если проверочное значение ложь>;
  116.         if (Time.time - previousTime > (Input.GetKey(KeyCode.DownArrow) ? fallTime / 10 : fallTime))
  117.         {
  118.             // аналогично, сначала двигаем вниз
  119.             transform.position += new Vector3(0, -1, 0);
  120.             // если фигкра не поместилась
  121.             // тут логика несколько меняется
  122.             // т.к. при движении вниз нужно зафиксировать фигуру
  123.             if (!ValidMove())
  124.             {
  125.                 // сначала возвращаем ее назад, что логично, т.к. в новой позиции она не поместилась
  126.                 transform.position -= new Vector3(0, -1, 0);
  127.                 // теперь ее нужно зафиксировать фигуру в стакане
  128.                 AddToGrid();
  129.                 // теперь нужно проверить, не появились ли в стакане собранные линии
  130.                 // и удалить их, если такие линии появились
  131.                 CheckForLines();
  132.                 // теперь эта фигура больше не нужна, ее нужно отключить
  133.                 // это неправильное решение, но пока сойдет
  134.                 // каждый объект нужно удалить, а не отключить (поставить на паузу)
  135.                 // т.к. он немного, но занимает место в памяти
  136.                 // это ошибка, которую мы исправим позже
  137.                 this.enabled = false;
  138.                 // теперь находим объект, у которого есть скрипт, который создает новую фигуру
  139.                 // и выполняем его функцию создания новой фигуры
  140.                 FindObjectOfType<SpawnTetromino>().NewTetromino();
  141.             }
  142.             // Записываем текущее время относительно старта программы
  143.             previousTime = Time.time;
  144.         }
  145.     }
  146.  
  147.     // Функция проверки собранности линий
  148.     void CheckForLines()
  149.     {
  150.         // в цикле проходимся по линиям сверху вниз
  151.         // от линии 19 до 0
  152.         // стоит обратить внимание на создание переменной
  153.         // любые циклы позволяют создавать временные переменные
  154.         // которые будут действовать только внутри цикла
  155.         // в данном цикле мы создаем переменную nextLineRow
  156.         // типа int
  157.         for (int nextLineRow = height-1; nextLineRow >= 0; nextLineRow--)
  158.         {
  159.             // Проверям, если в строеке собранная линия
  160.             if(HasLine(nextLineRow))
  161.             {
  162.                 // Сначала удаляем объекты из собранной линии
  163.                 DeleteLine(nextLineRow);
  164.                 // Затем все линии выше опускаем на одну строку вниз
  165.                 RowDown(nextLineRow);
  166.             }
  167.         }
  168.     }
  169.  
  170.     // Функция проверки, что строка собралась
  171.     bool HasLine(int lineRow)
  172.     {
  173.         // проходим по каждому столбцу в строке
  174.         // от 0 до 9
  175.         for(int column = 0; column< width; column++)
  176.         {
  177.             // если наткнулись на пустую ячейку,
  178.             // значит строка точно не заполнена
  179.             if (grid[column, lineRow] == null)
  180.                 return false; // выходим из функции, говорим, сто строка не готова
  181.         }
  182.         // если дошли сюда, значит в строке нет пустых ячеек
  183.         // возвращаем истину, строка заполнена
  184.         return true;
  185.     }
  186.  
  187.     // Функция удаления линии
  188.     void DeleteLine(int deleteLineRow)
  189.     {
  190.         // По очередии от 0 до 9 удаляем объекты из массива
  191.         for (int column = 0; column < width; column++)
  192.         {
  193.             // Вызываем функцию удаления блока из массива по адресу
  194.             // j, i, где i строка, а j столбец
  195.             Destroy(grid[column, deleteLineRow].gameObject);
  196.             // И очищаем значение самой ячейки (null - это признак отсутсвия значения,
  197.             // это как бы ничто)
  198.             grid[column, deleteLineRow] = null;
  199.         }
  200.     }
  201.  
  202.     // функция смещения данных ячеек сверху вниз на одну строку
  203.     void RowDown(int clearedLineRow)
  204.     {
  205.         // вложенные циклы начинаются
  206.         // со значения строки, которая была очищена
  207.         // до самой верней, которая равна 19
  208.         for (int line = clearedLineRow; line < height; line++)
  209.         {
  210.             // теперь столбец
  211.             for (int column = 0; column < width; column++)
  212.             {
  213.                 // у нас по очереди меняется солбец
  214.                 // если в ячейке текущей обрабатываемой строки
  215.                 // значение не пустое
  216.                 if (grid[column,line] != null)
  217.                 {
  218.                     // перемещаем значение на строку вниз
  219.                     grid[column, line - 1] = grid[column, line];
  220.                     // текущее значение очищаем
  221.                     grid[column, line] = null;
  222.                     // двигаем объект (блок, это физический объект) вниз на 1
  223.                     // Т.е. в двух строчках выше мы поменяли его положение в таблице
  224.                     // как в экселе, сдинув из одной ячеки данных на ячейку ниже
  225.                     // а строчкой ниже мы двигаем картинку (спрайт) на экране, у которой есть координаты
  226.                     grid[column, line - 1].transform.position -= new Vector3(0, 1, 0);
  227.                 }
  228.             }
  229.         }
  230.     }
  231.  
  232.  
  233.     // Добавляем блоки фигуры, которая упала, в конкретные ячейки таблицы
  234.     // Которая хранит в каждой ячейке либо пустоту (null), либо
  235.     // блок, который вырван из фигуры (префаба)
  236.     void AddToGrid()
  237.     {
  238.         // Находим каждый блок в фигуре
  239.         // children по очереди станет каждым блоком фигуры
  240.         // Нам даже не нужно знать, сколько блоков в фигуре
  241.         // сколько блоков мы добавили в префаб, столько раз
  242.         // будет выполнена каждая строчка в блоке foreach
  243.         // в данном цикле мы создаем временную переменную children
  244.         // тип у переменной children Transform
  245.         foreach (Transform children in transform)
  246.         {
  247.             // Округляем и сохраняем положение блока по X
  248.             int roundedX = Mathf.RoundToInt(children.transform.position.x);
  249.             // Округляем и сохраняем положение блока по Y
  250.             int roundedY = Mathf.RoundToInt(children.transform.position.y);
  251.             // для значения блока по округленным координатам в таблице
  252.             // вместо пустоты записываем ссылку на объект (блок от фигуры)
  253.             // Ссылка это такая штука, к которой можно дописать точку
  254.             // и все что слева от точки - это ссылка на объект
  255.             // а все что справа от точки - это обращение к элементам объекта
  256.             // например grid[roundedX, roundedY].transform.position
  257.             // и children.transform.position
  258.             // после приравнивания будет действовать одинаково
  259.             // и мы сможем поменять положение позиции объекта
  260.             // т.к. ссылку на него мы храним в каждой нужной ячейке таблицы
  261.             grid[roundedX, roundedY] = children;
  262.         }
  263.     }
  264.  
  265.     // Проверяем, не попал ли объект на стену или другой объект
  266.     bool ValidMove()
  267.     {
  268.         // для каждого блока в фигуре
  269.         // кстати, у children тоже могут быть вложенные объекты
  270.         // и для них тоже мог бы быть вложенный foreach
  271.         foreach (Transform children in transform)
  272.         {
  273.             // получаем значения положения блока в таблице
  274.             int roundedX = Mathf.RoundToInt(children.transform.position.x);
  275.             int roundedY = Mathf.RoundToInt(children.transform.position.y);
  276.  
  277.             // проверяем положение блока зы выход за границы стакана
  278.             if(roundedX < 0 || roundedX >= width || roundedY < 0 ||roundedY >= height)
  279.             {
  280.                 return false; // блок вышел за границы стакана, ход неправильный
  281.             }
  282.  
  283.             if (grid[roundedX, roundedY] != null)
  284.                 return false; // в ячейке таблицы есть другой объект, ход неправильный
  285.         }
  286.  
  287.         // сюда мы попадем только в том случае, если каждый блок не пересек стакан
  288.         // и не столкнулся с записанным ранее блоком в таблице
  289.         return true;
  290.     }
  291. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement