Каждая решённая проблема и каждая исправленная ошибка - это прокачка нашего навыка разработчика! Ошибки и баги возникают всегда. Борьба с ними занимает большую часть нашего времени, но делает нас опытнее. В этой статье мы исправим некоторые внезапно возникшие баги и доработаем движение волка.
Напомню, что у нас периодически стала возникать проблема, что волк оказывается на дистанции, когда уже останавливается, но при этом ничего не делает. Его AttackTrigger всё еще не достаёт до игрока.
Как вариант, мы могли бы попытаться подобрать другие параметры компонента движения NavMesh, чтобы волк подходил не слишком близко, но и не останавливался слишком далеко.
Либо, как вариант, подобрать другие радиусы триггеров, но тогда получится, что волк будет кусать персонажа находясь за 2 метра от него. Эдакий волк-чародей кусающий телекинезом. :)
Гораздо интереснее будет сделать так, чтобы волк сам решал вопрос с дистанцией. :) Надо всё автоматизировать!
Идея в том, чтобы оставить решение с авто-остановкой НавМеша на дистанции 2 метра, т.к. при беге волк оказывается в правильном положении для атаки, но добавить метод подстройки дистанции для атаки.
Метод подстройки дистанции атаки
Если мы хотим что-то настроить, то можно выводить интересную нам переменную в консоль. Но гораздо удобнее будет наблюдать за ней в инспекторе! Нас интересует дистанция между волком и персонажем, которого он собирается покусать. Давайте для наглядности вынесем её в отдельную переменную distanceToPlayer.
Во время игры, выбрав скрипт волка, мы сможем в инспекторе наблюдать дистанцию между ним и игроком. Это нам пригодится для контроля и проверки нашего метода.
Всегда хороший вопрос - как назвать новый метод. :) В программировании хорошей практикой является называние метода в соответствии с тем, что он делает. Это как правило глагол. Есть такой глагол fix, который переводится как "исправлять/чинить". Все же помнят фиксиков? :) Главное уточнить, что именно мы будем фиксить!
Создадим новый метод FixDistance() и добавим новую булевую переменную isFixDistance. Метод будет проверять не находится ли волк дальше, чем 1.5 метра и если да, то заставлять его подойти поближе. Для этого придётся включать ему скорость и менять значение stoppingDistance компонента НавМеша, чтобы он не тормозил волка.
Метод также будет выставлять переменную isFixDistance в значение true, чтобы все знали, что волк подбирает дистанцию для атаки.
В методе FollowPlayer() сделаем проверку на isFixDistance, чтобы в момент подстройки дистанции скорость не обнулялась, как положено при дистанции меньше дистанции остановки.
Сам метод FixDistance() будет запускаться из асинхронного метода RotateToPlayer(). А он в свою очередь выполняется у нас каждые 20 миллисекунд, так как мы прописали ожидание Task.Delay(20). Однако, чтобы это выглядело более плавым и соответствовало 60 кадрам в секунду, предлагаю сделать ожидание в 16 миллисекунд.
Проверим наш метод в режиме Play. Всё более-менее работает. Из-за тормозного пути волк останавливается компонентом движения где-то за 1,1 метр, не смотря на stoppingDistance в 2 метра. Но это выглядит вполне неплохо. Постоянства тут нет - разные скорости движения и плавное замедление движения НавМеша даёт разный "тормозной путь".
Более явно проблема себя проявляла во время ходьбы, когда пытаешься от атакующего волка уйти пешком. :) Вот тогда то волк и останавливался за 2 метра не зная что делать. Теперь дистанция всегда подстраивается до расстояния меньше, чем 1.5 метра, за счёт того, что волк подходит чуть ближе благодаря нашему новому методу FixDistance(). Дело сделано! Точнее половина дела!
Если волк слишком далеко, то мы заставляем его подойти, но если волк внезапно забежал чуть ближе и стоит в притык своей мордой к выживальщику? Урон пройдёт, но смотрится не очень хорошо.
Давайте компенсируем разницу тормозного пути в зависимости от скорости. Предлагаю просто отодвигать волка назад, до расстояния например в 1,3 метра.
Сделаем это не НавМешем, а простым заданием новой позиции объекта c помощью transform.position. Из позиции волка будем вычитать вектор направления к игроку. Но так как это смещение будет происходить каждые 16 миллисекунд, т.е. фактически каждый кадр, то оно должно быть небольшое. Поэтому умножим его например на 0.01.
Вектор направления от волка к игроку мы высчитываем в методе RotateToPlayer() и можем просто его передать в FixDistance() в качестве входного параметра.
В методе RotateToPlayer() добавим параметр при вызове метода FixDistance().
Я ещё заметил, что зачем-то умножаю в асинхронном методе на Time.deltaTime. В данной ситуации это не имеет смысла, т.к. время ожидания у нас итак в реальных миллисекундах, а Time.deltaTime это время между кадрами. Поэтому его можно убрать и подобрать скорость поворота поменьше.
Теперь наш специально обученный волк, с разбегу и с дуру залетев немного дальше, довольно быстро отталкивается назад и кусает игрока уже с подходящей дистанции, что в целом выглядит весьма неплохо.
Баг: бесконечный респаун волка
Я заметил, что волк после возрождения теперь проходит несколько метров и телепортируется обратно на место возрождения. Очень короткий такой день сурка, длинною примерно в 5 секунд. Причина довольно банальна.
Поскольку мы сделали метод Death() двойного назначение - он у нас и убивает волка, и его возрождает, то некоторые значения внутри него должны зависеть от входного параметра Death(true) или Death(false).
Метод Respawn() вызывается вне зависимости от входного параметра и мы при возрождении получаем еще один вызов метода возрождения. Проблема решается очень просто.
Добавим проверку имеет ли входной параметр isDeath значение true и тогда только в этом случае будет вызываться метод Respawn().
Теперь волк возрождается на случайной точке возрождения и спокойно идёт до точки маршрута. Вот только дойдя до неё, начинает топтаться на месте.
Баг: волк не хочет гулять
Это вторая проблема. Её причина оказалась в том, что мы добавили stoppingDistance равный 2, но при этом в методе Move() проверяем не приблизился ли волк к точке маршрута ближе, чем на метр. Волк, то очень хочет подойти поближе, но компонент НавМеш ему не даёт. Меняем метр на два и всё работает.
Баг: волк забыл как гулять :)
Третья проблема самая интересная и над ней пришлось поломать голову. Если волка изначально поставить подальше от игрока, чтобы он на него сразу же не бросался, то при старте игры волк топчется на месте и никуда не идёт.
Причина этой проблемы кроется в прописанном нами в скрипте Enemy методе Start().
Оказалось, что метод Start() в скрипте Enemy, заменил собой одноименный метод Start() в скрипте AnimalMove, который Enemy наследует. В итоге метод Start() из AnimalMove просто не выполнялся и движение волка не запускалось.
Решение довольно простое для тех, кто знаком с наследованием. Делаем метод Start() в скрипте AnimalMove публичным и виртуальным, а в Enemy переопределяем его с помощью модификатора override и делаем вызов базового метода с помощью обращения base.
Почему просто не вынести задание дистанции остановки в AnimalMove? Тоже вариант, но с ним надо туда вынести и переменную stoppingDistance, так как она объявляется только в классе Enemy.
С одной стороны эта переменная важна только для атакующих существ, с другой стороны этот параметр может быть у всех и если кому-то он не нужен, то его можно будет просто обнулить. А с третьей стороны мы зато посмотрели интересные фишки наследования и переопределения методов! :)
Вот такие, возможно очевидные для профессионалов и совсем неочевидные для начинающих программистов, чудеса! :)
Если было интересно или полезно, то подписывайтесь, ставьте лайки и пишите комментарии - просто напоминаю, что это очень помогает развиваться каналу)
Предыдущая статья:
Вся подборка статей:
А вот тут про C#: