Статьи

Создание клона понг в Unity: интерфейс и геймплей

В первой части этого урока — « Ретро революция: создание клона понг в Unity» — мы создали клон понг с базовым искусственным интеллектом (AI) и почти без пользовательского интерфейса (UI).

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

Pong

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

Стиль игры

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

Это также означает, что нам необходимо импортировать новые спрайты в проект Unity (перетаскивая их в папку «Спрайты» на панели «Ресурсы»). Если вы читаете дальше, не стесняйтесь использовать эти примеры из демонстрационного кода.

После импорта новых спрайтов в Unity мы должны выбрать оба спрайта на панели «Ресурсы» и изменить их пиксели на единицу до 64 . Мы также должны нажать Применить, чтобы изменения вступили в силу.

Теперь, когда у нас есть наши изображения, мы можем начать обновлять наш Pong Clone. Во-первых, мы должны изменить цвет фона основной камеры на черный. Это можно сделать, нажав на цветную полосу рядом со словом « Фон» на панели инспектора основной камеры. Далее нам нужно изменить цвет весла. Мы должны выбрать игровой объект Player на панели иерархии и перетащить спрайт белого квадрата в значение атрибута Sprite в элементе Sprite Renderer. Теперь мы должны сделать то же самое для игрового объекта Enemy.

Чтобы создать красивую среднюю полосу в Понге ( см. Ниже ), нам нужно создать пустой игровой объект (щелкните правой кнопкой мыши в иерархии -> создать пустое ) и назовите его MiddleBar . Игровой объект Middle Bar должен иметь X 0 и Y 0 , чтобы он располагался в центре экрана. Теперь мы можем перетащить белый квадратный спрайт на игровой объект Middle Bar, чтобы сделать белые квадраты, которые являются потомками игрового объекта Middle Bar. Все детские шкалы Х должны быть низкими, чтобы они выглядели как тощие белые палочки.

Пример среднего бара

Наконец, нам нужно изменить спрайт шара, чтобы вместо серого круга был белый круг. Если мы перейдем к префабу ball (расположенному в папке Prefabs), мы можем выбрать его и изменить его спрайт, как мы это делали с Player и Enemy, за исключением использования спрайта белого круга.

Делать столкновение лучше

При игре в клон Понг из последнего урока мы можем наблюдать несколько ошибок со столкновениями. Иногда мяч может отскочить под странным углом, или реакция шара на удар по объекту может заставить мяч лететь прямо назад, а не под углом. Чтобы это исправить, нам нужно добавить код к шару, который позволит шару вычислить угол, под которым он падает на весло, и отскочить от него в соответствии с этим углом. Давайте откроем скрипт BallController, расположенный в папке Scripts. С открытым сценарием BallController мы должны сделать так, чтобы его метод OnCollisionEnter2D выглядел так, как OnCollisionEnter2D ниже:

 void OnCollisionEnter2D(Collision2D col) { //tag check if (col.gameObject.tag == "Enemy") { //calculate angle float y = launchAngle(transform.position, col.transform.position, col.collider.bounds.size.y); //set angle and speed Vector2 d = new Vector2(1, y).normalized; rig2D.velocity = d * speed * 1.5F; } if (col.gameObject.tag == "Player") { //calculate angle float y = launchAngle(transform.position, col.transform.position, col.collider.bounds.size.y); //set angle and speed Vector2 d = new Vector2(-1, y).normalized; rig2D.velocity = d * speed * 1.5F; } } //calculates the angle the ball hits the paddle at float launchAngle(Vector2 ball, Vector2 paddle, float paddleHeight) { return (ball.y - paddle.y) / paddleHeight; } 

Все, что нам нужно сделать сейчас, это создать тег для весла Enemy и добавить тег к веслу Player, чтобы шар мог знать, что есть что. Если мы выберем игровой объект Enemy на панели иерархии и нажмем Untagged на панели Inspector под именем Enemy, появится выпадающий список. Мы можем щелкнуть Добавить тег, чтобы просмотреть меню «Теги и слои». Нажав на символ +, мы можем добавить тег с именем Enemy в наш список тегов. Теперь давайте снова выберем игровой объект Enemy, щелкните без тега, а затем нажмите Enemy, чтобы установить для него тег Enemy. Для игрового объекта Player все, что нам нужно сделать, это выбрать игровой объект, щелкнуть немаркированную и затем щелкнуть тег Player, поскольку Unity создает тег Player для нас при первом создании проекта.

Улучшение Вражеского ИИ

Вражеский ИИ предыдущего урока был упрощенной версией ИИ, который мы будем использовать в этом уроке. Старый ИИ двигался в зависимости от положения шара Y, но имел низкую скорость, чтобы весло не подпрыгивало и не трясло. В новом AI мы все еще будем использовать позицию Y шара в качестве основы для нашего кода, но сделаем так, чтобы весло двигалось в зависимости от времени. Это позволяет веслу перемещаться быстрыми взрывами, за которыми человеческие глаза едва смогут следить. Если значения времени и скорости сделаны правильно (или даже достаточно близко, чтобы улучшить), движение вражеского весла будет выглядеть почти полностью плавным.

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

Если мы откроем скрипт EnemyController и изменим его на код, приведенный ниже, мы получим желаемые результаты:

 // Use this for initialization void Start () { //Continuously Invokes Move every x seconds (values may differ) InvokeRepeating("Move", .02F, .02F); } // Movement for the paddle void Move () { //finding the ball if(ball == null){ ball = GameObject.FindGameObjectWithTag("Ball").transform; } //setting the ball's rigidbody to a variable ballRig2D = ball.GetComponent<Rigidbody2D>(); //checking x direction of the ball if(ballRig2D.velocity.x < 0){ //checking y direction of ball if(ball.position.y < this.transform.position.y-.5F){ //move ball down if lower than paddle transform.Translate(Vector3.down*speed*Time.deltaTime); } else if(ball.position.y > this.transform.position.y+.5F){ //move ball up if higher than paddle transform.Translate(Vector3.up*speed*Time.deltaTime); } } //set bounds of enemy if(transform.position.y > topBound){ transform.position = new Vector3(transform.position.x, topBound, 0); } else if(transform.position.y < bottomBound){ transform.position = new Vector3(transform.position.x, bottomBound, 0); } } 

Примечание. Увеличение времени метода InvokeRepeating замедляет движение, но добавляет эффект дрожания. Смешивание скорости весла между 1 и 4 (числа с плавающей запятой) и время InvokeRepeating между .1 и .3 работали лучше всего из моего тестирования.

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

 //enemy transform public Transform enemy; void OnTriggerEnter2D(Collider2D other){ if(other.gameObject.tag == "Ball"){ //Destroys other object Destroy(other.gameObject); //sets enemy's position back to original enemy.position = new Vector3(-6,0,0); //pauses game Time.timeScale = 0; } } 

Мы также должны убедиться, что перетаскиваем весло Enemy из иерархии в значение преобразования Enemy на обоих ограничивающих объектах, иначе мы получим исключение Null Reference.

Создание меню паузы

Функциональность паузы для этого клона Pong будет немного отличаться от функциональности в моем уроке по Dodger . Поскольку в этой игре есть текст, который будет отображаться, когда мяч уничтожен, и когда игрок приостанавливает игру, мы должны специально нацелиться на этот текст, используя переменную Unity gameObject.name чтобы установить его активное состояние. Мы также добавим в игру собственный шрифт из Font Squirrel, чтобы придать ему более ретро-вид.

Создание ретро-меню паузы

Чтобы добавить собственный шрифт в Unity, нам нужно загрузить его на наш компьютер, а затем импортировать в Unity с помощью перетаскивания. Сначала нам нужно скачать SilkScreen ( не ссылка на скачивание ), а затем распаковать загруженный файл. После распаковки файла мы видим, что существует несколько разных типов SilkScreen. Для этого урока мы будем использовать slkscreb.ttf . Нам не нужно устанавливать шрифт на наш компьютер. Вместо этого давайте создадим папку на панели «Ресурсы» с именем «Шрифты». Теперь мы можем открыть папку Fonts и перетащить файл slkscreb.ttf на панель Assets.

С SilkScreen, добавленным в Unity, теперь мы можем начать работать с нашим меню паузы. Наше меню будет выровнено по верху (см. Ниже) и будет содержать две кнопки и текст из последней статьи:

Меню паузы

Теперь мы должны создать игровой объект-кнопку с именем ResetButton. Чтобы придерживаться традиционной темы, которую мы для себя установили, мы должны заменить атрибут «Исходное изображение» кнопки (расположенный в Инспекторе под элементом «Изображение») на белый квадратный спрайт. Мы можем сделать это, перетащив белый квадратный спрайт из папки Sprites в значение атрибута Source Image. Теперь мы должны увидеть, что кнопка представляет собой белый прямоугольник без закругленных углов.

Далее нам нужно изменить шрифт на кнопке на более точечный шрифт SilkScreen. Если мы выберем дочерний текстовый объект из иерархии, мы можем сделать свойство текста текстового объекта похожим на значения ниже:

Текстовые значения ResetButton

Примечание. Чтобы изменить шрифт текста, мы можем просто перетащить файл slkscreb.ttf из панели «Ресурсы» в значение атрибута «Шрифт». Также обратите внимание, что размер шрифта может отличаться для всех.

Чтобы создать кнопку «Главное меню», мы можем выбрать кнопку «Сброс» в иерархии и дублировать ее ( команда / Ctrl + D или щелкните правой кнопкой мыши -> дублировать ). Теперь все, что нам нужно сделать, это назвать дублированную кнопку MainMenuButton. Мы должны выровнять каждую кнопку в соответствующей области экрана (сброс в верхнем левом углу, главное меню в правом верхнем углу).

Затем мы можем стилизовать текст, созданный из последнего урока. Если текст уже не называется PauseText, мы должны изменить его на PauseText. Мы также должны изменить атрибут Font с Arial на наш шрифт SilkScreen. Давайте удостоверимся, что текст белый и по центру. Наконец, нам нужно выровнять его по середине экрана, чуть выше центра.

Добавление функциональности в меню паузы

Мы создали меню паузы в стиле ретро, ​​но сейчас оно постоянно отображается на экране во время игры, и кнопки ничего не делают при нажатии. Чтобы добавить функциональность в наше меню, мы должны создать пустой игровой объект с именем UIManager. Теперь давайте создадим скрипт с именем UIManager и откроем его внутри нашей IDE. Для полной функциональности мы можем добавить приведенный ниже код в наш скрипт:

 GameObject[] pauseObjects; // Use this for initialization void Start () { pauseObjects = GameObject.FindGameObjectsWithTag("ShowOnPause"); } // Update is called once per frame void Update () { //uses the p button to pause and unpause the game if(Input.GetKeyDown(KeyCode.P)) { if(Time.timeScale == 1) { Time.timeScale = 0; showPaused(); } else if (Time.timeScale == 0){ Time.timeScale = 1; hidePaused(); } } if(Time.timeScale == 0){ //searches through pauseObjects for PauseText foreach(GameObject g in pauseObjects){ if(g.name == "PauseText") //makes PauseText to Active g.SetActive(true); } } else { //searches through pauseObjects for PauseText foreach(GameObject g in pauseObjects){ if(g.name == "PauseText") //makes PauseText to Inactive g.SetActive(false); } } } //Reloads the Level public void Reload(){ Application.LoadLevel(Application.loadedLevel); } //controls the pausing of the scene public void pauseControl(){ if(Time.timeScale == 1) { Time.timeScale = 0; showPaused(); } else if (Time.timeScale == 0){ Time.timeScale = 1; hidePaused(); } } //shows objects with ShowOnPause tag public void showPaused(){ foreach(GameObject g in pauseObjects){ g.SetActive(true); } } //hides objects with ShowOnPause tag public void hidePaused(){ foreach(GameObject g in pauseObjects){ g.SetActive(false); } } //loads inputted level public void LoadLevel(string level){ Application.LoadLevel(level); } 

Этот UIManager очень похож на UIManager из серии учебных пособий по Dodger. Основное отличие заключается в методе Update() . Мы добавили код, который просматривает массив pauseObjects для текста паузы, используя цикл foreach для поиска объекта с именем PauseText. После обнаружения он устанавливает активное состояние текста паузы в зависимости от масштаба игры.

Теперь, когда мы написали наш скрипт, давайте добавим его к игровому объекту UIManager внутри Unity, перетащив его на игровой объект. Чтобы скрыть наше меню, когда экран приостановлен, мы должны сделать кнопки и приостановить текстовые теги ShowOnPause. Это означает, что нам нужно создать новый тег ShowOnPause для каждого из объектов.

Добавив теги и прикрепив скрипт UIManager к игровому объекту UIManager, теперь мы можем приостановить игру, чтобы просмотреть наше меню. Мы также видим, что текст паузы появляется, когда игра приостановлена ​​или когда мяч разрушен.

Чтобы завершить меню, нам нужно добавить наши методы к кнопкам. Мы можем выбрать кнопку «Сброс» и нажать символ «+» в меню OnClick (), расположенном в Инспекторе под элементом «Кнопка». Теперь мы можем перетащить игровой объект UIManager из панели Иерархия в первое поле ввода. Если щелкнуть второе раскрывающееся окно и выбрать UIManager -> Обновить , к кнопке будет добавлена ​​функция « Reload() . Меню OnClick () должно выглядеть так:

Сбросить меню OnClick ()

Для кнопки «Главное меню» мы можем сделать то же, что и выше, но вместо этого добавить метод LoadLevel() . С добавленным методом LoadLevel() мы увидим окно, которое позволит нам добавить наш строковый параметр. Внутри поля мы можем ввести MainMenu, чтобы наша сцена главного меню загружалась при нажатии кнопки. Меню OnClick () кнопки Main Menu должно выглядеть так:

Главное меню OnClick () меню

Исправление функциональности паузы

Теперь, когда мы добавили наше меню и UIManager, мы исправили некоторые проблемы и добавили проблему. Если мы пытаемся поставить игру на паузу, мы можем заметить (это может зависеть от версии Unity), что пауза не работает. Это потому, что у нас есть два разных скрипта, устанавливающих шкалу времени экрана, что заставляет их взаимно отменять друг друга. Чтобы это исправить, мы можем открыть скрипт PlayerController и удалить приведенный ниже код:

 //pauses or plays game when player hits p if(Input.GetKeyDown(KeyCode.P) && Time.timeScale == 0){ Time.timeScale = 1; } else if(Input.GetKeyDown(KeyCode.P) && Time.timeScale == 1){ Time.timeScale = 0; } 

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

Создание сцены

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

Сохранив сцену, мы теперь можем выбрать ее на панели «Активы» и продублировать ( command / ctrl + d ). Теперь давайте переименуем дубликат в MainMenu и откроем его (двойным щелчком).

Внутри сцены главного меню мы должны отключить / удалить игровой объект BallSpawner, игровой объект MiddleBar, игровой объект LeftBound, игровой объект RightBound и игровые объекты меню паузы из иерархии. Мы удаляем их, потому что они не нужны, и могут помешать пользовательскому интерфейсу, который мы добавим.

Добавление пользовательского интерфейса

Интерфейс главного меню будет оставаться минималистичным. У нас будет большой текст заголовка, который отображает слово Pong и кнопку с надписью play .

Мы можем начать с создания текстового объекта с именем TitleText. Текстовый элемент текста заголовка должен иметь значения атрибута, как показано ниже:

Атрибуты текста TitleText

Далее мы можем создать объект кнопки и назвать его PlayButton. Мы должны изменить исходное изображение кнопки воспроизведения на белый квадратный спрайт. Давайте также добавим метод LoadLevel() к кнопке воспроизведения , а строковым параметром будет имя нашей сцены воспроизведения. Мы можем проверить имя нашей сцены воспроизведения, найдя его на панели «Ресурсы» или перейдя в « Файл» -> «Настройки сборки» (сцена должна быть единственной в списке; если нет, то это самая верхняя сцена). Свойства дочернего текстового объекта должны совпадать со свойствами текста заголовка, за исключением того, что текст должен оставаться серым и иметь другой размер шрифта.

Наконец, нам нужно выровнять элементы пользовательского интерфейса в нужное нам место, например ниже:

Понг Главное меню

Добавление симулированного геймплея

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

Во-первых, давайте добавим шарик в сцену, перейдя в папку Prefabs на панели Assets и добавив prefab шарика. Мы должны убедиться, что его позиция преобразования равна X:0 Y:0 Z:0 чтобы она была в центре экрана.

Внутри папки Scripts на панели Assets нам нужно создать новый скрипт с именем AutoPlayer, а затем открыть его в нашей IDE. Скрипт AutoPlayer должен содержать следующий код:

 //Speed of the AI public float speed = 2.75F; //the ball Transform ball; //the ball's rigidbody 2D Rigidbody2D ballRig2D; //bounds of AI public float topBound = 4.5F; public float bottomBound = -4.5F; // Use this for initialization void Start () { //Continuously Invokes Move every x seconds (values may differ) InvokeRepeating("Move", .02F, .02F); } // Movement for the paddle void Move () { //finding the ball if(ball == null){ ball = GameObject.FindGameObjectWithTag("Ball").transform; } //setting the ball's rigidbody to a variable ballRig2D = ball.GetComponent<Rigidbody2D>(); //checking x direction of the ball if(ballRig2D.velocity.x > 0){ //checking y direction of ball if(ball.position.y < this.transform.position.y-.3F){ //move ball down if lower than paddle transform.Translate(Vector3.down*speed*Time.deltaTime); } else if(ball.position.y > this.transform.position.y+.3F){ //move ball up if higher than paddle transform.Translate(Vector3.up*speed*Time.deltaTime); } } //set bounds of AI if(transform.position.y > topBound){ transform.position = new Vector3(transform.position.x, topBound, 0); } else if(transform.position.y < bottomBound){ transform.position = new Vector3(transform.position.x, bottomBound, 0); } } 

Теперь мы можем удалить скрипт PlayerController из игрового объекта Player в иерархии и добавить скрипт AutoPlayer.

Внутри нашей папки Scripts давайте создадим другой скрипт с именем AutoEnemy и откроем его в нашей IDE. Сценарий AutoEnemy должен содержать следующий код:

 /Speed of the AI public float speed = 2.75F; //the ball Transform ball; //the ball's rigidbody 2D Rigidbody2D ballRig2D; //bounds of AI public float topBound = 4.5F; public float bottomBound = -4.5F; // Use this for initialization void Start () { //Continuously Invokes Move every x seconds (values may differ) InvokeRepeating("Move", .02F, .02F); } // Movement for the paddle void Move () { //finding the ball if(ball == null){ ball = GameObject.FindGameObjectWithTag("Ball").transform; } //setting the ball's rigidbody to a variable ballRig2D = ball.GetComponent<Rigidbody2D>(); //checking x direction of the ball if(ballRig2D.velocity.x < 0){ //checking y direction of ball if(ball.position.y < this.transform.position.y-.3F){ //move ball down if lower than paddle transform.Translate(Vector3.down*speed*Time.deltaTime); } else if(ball.position.y > this.transform.position.y+.3F){ //move ball up if higher than paddle transform.Translate(Vector3.up*speed*Time.deltaTime); } } //set bounds of AI if(transform.position.y > topBound){ transform.position = new Vector3(transform.position.x, topBound, 0); } else if(transform.position.y < bottomBound){ transform.position = new Vector3(transform.position.x, bottomBound, 0); } } 

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

Если мы нажмем на игру, то увидим, что фон имитировал игровой процесс. Однако, если мы нажмем кнопку воспроизведения , мы заметим, что она не работает. Это потому, что мы должны добавить сцену в настройки сборки. Мы можем сделать это, щелкнув File -> Build Settings и перетащив сцену из панели Assets в меню или нажав кнопку Add Current . После добавления сцены главного меню в настройки сборки нам нужно перетащить ее в верхнюю часть списка сцен.

Добавление пользовательского интерфейса счета игрока

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

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

Свойства ScoreText

Мы должны выровнять счет в верхней части экрана в центре. Теперь давайте откроем скрипт BoundController и сделаем так, как показано ниже:

 //enemy transform public Transform enemy; public int enemyScore; public int playerScore; void Start(){ enemyScore = 0; playerScore = 0; } void OnTriggerEnter2D(Collider2D other){ if(other.gameObject.tag == "Ball"){ if(other.gameObject.GetComponent<Rigidbody2D>().velocity.x > 0){ enemyScore++; } else { playerScore++; } //Destroys other object Destroy(other.gameObject); //sets enemy's position back to original enemy.position = new Vector3(-6,0,0); //pauses game Time.timeScale = 0; } } 

Нам нужно перетащить игровой объект Enemy из иерархии в свойство Bound Controller Enemy как для объектов с левой, так и с правой стороны границ. Преобразование противника будет использовано для сброса позиции противника после уничтожения мяча. После добавления игрового объекта Enemy в границы мы можем создать новый скрипт с именем PointCounter и открыть его в нашей IDE. Мы должны сделать скрипт PointCounter похожим на приведенный ниже код:

 using UnityEngine; using UnityEngine.UI; using System.Collections; public class PointCounter : MonoBehaviour { public GameObject rightBound; public GameObject leftBound; Text text; // Use this for initialization void Start () { text = GetComponent<Text>(); text.text = rightBound.GetComponent<BoundController>().enemyScore + "\t\t" + leftBound.GetComponent<BoundController>().playerScore; } // Update is called once per frame void Update () { text.text = rightBound.GetComponent<BoundController>().enemyScore + "\t\t" + leftBound.GetComponent<BoundController>().playerScore; } } 

Мы должны прикрепить скрипт PointCounter к тексту партитуры. Нам также необходимо перетащить игровые объекты с левой и правой границей в соответствующие места в качестве значений переменных RightBound и LeftBound для скрипта PointCounter.

Завершение игры

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

Создание меню

Давайте начнем с создания нового тега с именем ShowOnFinish. После создания этого тега мы можем создать текстовый объект с именем GameOverText. Мы можем установить игру поверх текстового тега на ShowOnFinish. Мы можем установить текстовые свойства игры поверх текста так же, как на изображении ниже:

Свойства текста GameOverText

Теперь мы можем выровнять игру по тексту по центру экрана.

Вместо того, чтобы создавать все новые кнопки для состояния «игра закончена», мы можем вместо этого дублировать главное меню и сбросить кнопки и установить их теги на ShowOnFinish. Мы можем назвать новую кнопку главного меню FinishMainMenuButton и новую кнопку сброса FinishResetButton. Меню игры закончено, но мы не добавили в него никакой функциональности.

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

Давайте откроем скрипт UIManager и сделаем код таким же, как показано ниже:

 //arrays for pause and game over objects GameObject[] pauseObjects, finishObjects; //variables for the bounds public BoundController rightBound; public BoundController leftBound; //game over variables public bool isFinished; public bool playerWon, enemyWon; // Use this for initialization void Start () { pauseObjects = GameObject.FindGameObjectsWithTag("ShowOnPause"); finishObjects = GameObject.FindGameObjectsWithTag("ShowOnFinish"); hideFinished(); } // Update is called once per frame void Update () { //checks to make sure the current level is play level if(Application.loadedLevel == 1){ if(rightBound.enemyScore >= 7 && !isFinished){ isFinished = true; enemyWon = true; playerWon = false; } else if (leftBound.playerScore >= 7 && !isFinished){ isFinished = true; enemyWon = false; playerWon = true; } if(isFinished){ showFinished(); } } //uses the p button to pause and unpause the game if(Input.GetKeyDown(KeyCode.P) && !isFinished) { pauseControl(); } if(Time.timeScale == 0 && !isFinished){ //searches through pauseObjects for PauseText foreach(GameObject g in pauseObjects){ if(g.name == "PauseText") //makes PauseText to Active g.SetActive(true); } } else { //searches through pauseObjects for PauseText foreach(GameObject g in pauseObjects){ if(g.name == "PauseText") //makes PauseText to Inactive g.SetActive(false); } } } //Reloads the Level public void Reload(){ Application.LoadLevel(Application.loadedLevel); } //controls the pausing of the scene public void pauseControl(){ if(Time.timeScale == 1) { Time.timeScale = 0; showPaused(); } else if (Time.timeScale == 0){ Time.timeScale = 1; hidePaused(); } } //shows objects with ShowOnPause tag public void showPaused(){ foreach(GameObject g in pauseObjects){ g.SetActive(true); } } //hides objects with ShowOnPause tag public void hidePaused(){ foreach(GameObject g in pauseObjects){ g.SetActive(false); } } //shows objects with ShowOnFinish tag public void showFinished(){ foreach(GameObject g in finishObjects){ g.SetActive(true); } } //hides objects with ShowOnFinish tag public void hideFinished(){ foreach(GameObject g in finishObjects){ g.SetActive(false); } } //loads inputted level public void LoadLevel(string level){ Application.LoadLevel(level); } 

Теперь мы можем добавить игровые объекты с левой и правой границ к соответствующим переменным для скрипта UIManager, прикрепленного к игровому объекту UIManager. Нам также нужно создать новый скрипт с именем GameOver. Давайте откроем его и сделаем код похожим на это:

 using UnityEngine; using UnityEngine.UI; using System.Collections; public class GameOver : MonoBehaviour { public UIManager uiManager; private Text text; // Use this for initialization void Start () { text = GetComponent<Text>(); } // Update is called once per frame void Update () { if(uiManager.playerWon){ text.text = "GAME OVER!\nPLAYER WON!"; } else if(uiManager.enemyWon){ text.text = "GAME OVER!\nENEMY WON!"; } } } 

Теперь все, что нам нужно сделать, это добавить скрипт в объект игры поверх текстовой игры и перетащить игровой объект UIManager в переменную UIManager. Если мы играем в игру и проигрываем нарочно, мы должны увидеть, как слова меняются, а кнопки проигрывают.

Примечание: если слова не появляются или обрезаются, это означает, что ширина и высота Rect Transform в GameOverText должны быть больше. Кроме того, если меню паузы не работает правильно, попробуйте воссоздать кнопки.

Вывод

Поздравляем, мы официально сделали полный беззвучный клон Понг!

Это заняло некоторое время, но, надеюсь, это руководство было достаточно доступно, чтобы вы могли начать экспериментировать с вашими базовыми 2D физическими играми в Unity. Дайте нам знать, что вы придумали!

Помните, что вы можете скачать полный проект на GitHub .