Добавим движение камеры за игроком и параметры времени, чтобы игрок знал сколько он уже прожил и сколько осталось до следующего дня. Не забудьте подписаться на канал, чтобы не пропустить продолжение и погнали покорять мир геймдева! :)
Движение камеры за игроком
Сейчас наш доблестный выживальщик умеет красиво уходить из кадра по-английски. Не прощаясь. А нам, всей душой переживающим за него, хотелось бы всегда держать его на виду, чтобы точно знать, что его не ест в данный момент какой-нибудь медведь. Поэтому было бы неплохо если б наша главная камера двигалась за игроком.
Давайте создадим скрипт CameraMove и объявим в нём два поля playerTransform и cameraPosition. Первое поле это будет ссылка на компонент Transform нашего персонажа, чтобы брать оттуда координаты игрока. А второе поле это будут координаты камеры относительно игрока - фактически значения смещения вверх и назад.
Скрипт CameraMove мы повесим на нашу главную камеру (MainCamera) и будем управлять её позицией в пространстве, задавая её компоненту Transform каждый кадр в методе Update() позицию игрока с учётом смещения. Вот и весь скрипт!
Чтобы всё это заработало первым делом мы должны сохранить скрипт и повесить его на объект MainCamera в окне Иерархии, либо кликнуть на MainCamera и перетащить скрипт в окно инспектора выше или ниже имеющихся компонентов (но выше компонента Transform свой скрипт перетащить не получится).
После этого нужно обязательно не забыть перетащить в поле playerTransform объект Player, чтобы сделать ссылку именно на его координаты и задать смещение камеры в cameraPosition. Поскольку игрок у нас пока находится в нулевых координатах, то и смещение камеры будет равно её координатам.
Жмём Play и проверяем. Камера двигается вместе с игроком и теперь мы можем без проблем исследовать весь лес и даже дойти до края земли!
Добавляем в игру параметр времени
Бесконечно ходить по лесу и собирать грибы не особо интересно, поэтому чтобы создать для игрока дополнительный интерес предлагаю добавить время игрового дня. Задачей игрока становится дожить до конца дня. Учитывая, что мы делаем казуальную игру, то такая простая задача думаю вполне подойдёт.
Получается, что мы придумали еще и условие победы - прожил определенное время и ты победил. Ну или по крайней мере прожитый день пошёл тебе в рекорд. Так, наше гениальное геймдизайнерское решение готово, давайте подумаем, как это теперь считать.
Поскольку игра казуальная, то предлагаю считать не реальный световой день с 5 утра до 7 вечера условно и плюс ещё ночь, а упростить всё просто до определенного промежутка времени от 0 до 24 часов. Тогда наша задача будет просто посчитать эти 24 часа в определённом масштабе - например 1 игровой час это будет 1 реальная минута.
Давайте создадим новый скрипт TimeCounter и объявим в нём поля для счетчика времени time и счётчика дней day. Также сделаем поле timeScale для изменения масштаба времени в инспекторе. Если мы в каждом кадре будем увеличивать переменную time на Time.deltaTime, то мы будем считать реальные секунды. Умножим их на наш масштаб 60 и получится, что каждую секунду мы будем отсчитывать одну игровую минуту. Остается только теперь проверять, что наш time не перевалил за 24 * 60 = 1440 минут.
Когда игровой день закончился, то обнуляем наше время и вызываем специально обученный нами метод NewDay(), который сообщит нам в консоли, сколько дней мы прожили и увеличит счётчик дней на единицу.
В этот метод мы можем добавить всё что нам понадобиться для того, чтобы наградить игрока за прожитый день. Например, мы можем восполнить ему здоровье и сытость, сделать сохранение игры или с каждым прожитым днём добавлять какие-то игровые механики и увеличивать сложность игры. Но неплохо бы дать игроку знать сколько ему осталось прожить до заветной контрольной точки.
Наглядное отображение игровых параметров
Давайте сделаем отображение времени дня в интерфейсе. Поскольку мы не привязывались к реалистичному времени суток (утро, день, вечер, ночь), а просто упростили всё до таймера, который отсчитывает 24 часа, то мы можем легко отобразить время в виде шкалы. Заполненная шкала будет означать закончившийся день. Что-то типа полоски здоровья. Но можно пойти еще дальше и сделать заполняющийся круг.
Для этого во-первых давайте создадим ещё один Canvas внутри имеющегося и назовём его TimeCanvas. Это позволит нам оптимальнее пользоваться нашим интерфейсом, потому что все что находится на одном канвасе перерисовывается каждый раз, когда происходит изменения хотя бы в одном элементе. Т.е. каждый раз когда наш индикатор в каждом кадре будет заполняться, то все индикаторы параметров будут перерисовываться, а это повлияет на производительность игры. Отдельные же канвасы, даже вложенные друг в друга перерисовываются независимо по мере изменения. Вот мы и создадим новый вложенный канвас специально для нашего таймера.
Давайте создадим внутри TimeCanvas объект Image, кликнув в окне иерархии по объекту TimeCanvas правой кнопкой мыши и выбрав UI - Image. Созданную картинку назовём Timer и в её настройках в Инспекторе выберем в поле Source Image, кликнув по кружку выбора объектов данного типа, стандартную картинку Knob - белый круг.
И теперь в настройках Image появится новое поле Image Type, в котором нам нужно выбрать Filled, чтобы картинка стал заполняемой. А в настройке Fill Origin давайте выберем Top, чтобы индикатор заполнялся сверху, напоминая движение стрелки часов от 12 и до 12.
Создадим также текстовый объект DayText (UI - Text) и разместим их вместе с индикатором времени вверху экрана, не забыв выбрать привязку якорей к верхней границе посередине, выбрав иконку Top-Center. В настройках текстового объекта в текстовое окно впишем "День 1", чтобы прикинуть как это будет выглядеть.
В настройках нашего индикатора времени, мы можем изменять значение Fill Amount и посмотреть как индикатор будет меняться в зависимости от значения.
Было бы неплохо добавить нашему "циферблату" фон, чтобы он не выглядел как кусок пирога. Для этого давайте выберем в иерархии объект таймер и нажмём CTRL+D, чтобы продублировать его. В его настройках выберем другой цвет, например серый и типа изображения Simple. Назовём его Timer Background и наш Timer в иерархии перетащим на него. Теперь всё это выглядит интереснее.
Осталось изменять значение таймера Fill Amount из кода, в соответствии со значением нашей переменной time.
Изменение изображений из кода
Давайте в нашем скрипте UI, который управляет интерфейсом, сделаем ссылку на компонент Image нашего таймера, настройками которого нам предстоит управлять. Вот только по умолчанию наш скрипт ни про какие компоненты Image даже не слышал.
Воспользуемся подсказками VisualStudio, кликнув на "Показать возможные решения" во всплывающем окне указав курсором на подчеркнутое слово. Ну и добавим предложенную стандартную библиотеку UI или иными словами пространство имён UI. Причём наш класс никак не будет конфликтовать с этим пространством имён, не смотря на идентичные названия. Это совершенно разные сущности. Теперь мы можем пользоваться типом Image и давайте сделаем метод для его изменения.
Метод UpdateTimer() будет получать на вход дробное значение типа float и изменять параметр fillAmount у нашего таймера. А само входное значение будем вычислять в скрипте TimeCounter, в котором надо сделать ссылку на скрипт UI.
Поскольку параметр fillAmount у компонента Image изменяется от 0 до 1, то в метод UpdateTimer() будем отправлять значение времени разделённое на количество минут в дне.
Повесим скрипт TimeCounter на объект Game и проставим в нём ссылку на скрипт UI, который висит на объекте Canvas. А в скрипте UI проставим ссылку на Timer. Теперь запустим игру и проверим. Работает, но время бежит очень быстро. За 24 секунды прошел цикл дня и в консоли появилось сообщение, что мы прожили 1 "дней".
Очень удобная ошибка для тестирования получилась - мы быстро проверили, что все работает, что за первым днём отсчитывается следующий и что всё отображается. Но где-то мы явно зря умножаем на 60. За день длинною в 24 секунды вряд ли наш герой успеет хоть что-то, кроме как нажраться грибов. :)
Ошибка кроется вот в этом умножении на timeScale. Получается, что за 1 реальную секунду проходит не игровая минута, а игровой час.
Удалим это умножение на timeScale и всё заработает как нам надо. Остается только обновлять текст с номером дня. Для этого в скрипте UI сделаем на него ссылку dayText и добавим метод обновления UpdateDay().
Остаётся проставить ссылку в инспекторе на текстовый объект с номером дня и в скрипте TimeCounter в методе NewDay() добавить строчку кода.
Теперь чтобы протестировать и не ждать 24 минуты, пока игровой день закончится, давайте изменим в инспекторе значение timeScale на 1, чтобы ускорить процесс 60 раз.
Всё работает. Индикатор времени циклично отсчитывает время, а индикатор дней отображает номер текущего дня, при этом в консоли отображается сообщение о количестве прожитых дней.
Что ж мы научились перемещать один объект относительно другого, поломали голову над тем как считать и отображать время в игре, начали работать с картинками из кода, научились в C# складывать строки и числа, а также научились грамотно использовать особенности обновления канвасов.
В следующей части займёмся возобновление (респауном) грибов, увеличим их разнообразие и добавим им различные свойства. Так что, подписывайтесь на канал, чтобы не пропустить продолжение, задавайте вопросы и предлагайте идеи в комментариях! Как думаете, что еще нужно добавить в игру, чтобы играть в неё было интереснее?
Вот предыдущая часть статьи:
А вот вся подборка статей по этой теме: