Урок хороший, но как мне кажется совсем для новичков, а для новичков куча реализаций. Хотелось бы наверное более углубиться в раскрытие темы, т.к. на базе State machine строят архитектуры приложений. Поговорить о вариациях Transitions, возможно зацепить реализации с async, может быть построить какой-нибудь WindowManager. Построение простой архитектуры через StateMachine. Связывание например StateMachine и View. Может быть StateMachine и Zenject рассмотреть. Просто паттерн очень часто используется, а качественных уроков по его применению не особо много, кроме условного состояния персонажа. А так спасибо за видос!
@@samserious5483 zenject используют повсеместно. и неспроста - это лучший di контейнер. единственный минус заключается в его массивности и, наверное, в сложности освоения. для масштабных проектов - идеальный фреймворк. однако, для небольших лучше взять просто сервис локатор и не парится
Хорошее видео. Не посмотрел раньше из-за сессии + сейчас в проекте я бомбанул и переписываю машину состоянии))0) Ещё приятно слышать твой радостный голос в видео))
неплохо вышло, но не могу не заметить пару замечаний 1) метод AddState лучше сделать генерик, как и SetState, тогда не придется вызывать метод GetType, который выполняется в рантайме, а заменить на typeof(T), выполняющийся на этапе компиляции 2) можно кэшировать Type (который также будет браться через typeof) последнего установленного стейта и в проверке на одинаковый тип тоже можно убрать GetType, а оставить что то типа: if (currentStateType == typeof(T)) return жду новых видосов)
А ничего и не надо понимать. Напридумывали всякой ерунды (паттернов), мало было GOF - так вот получите 100+ паттернов. И реализуют эту выдумку разными способами. И паттерны и гибкая методология и фреймворки - легаси код на выходе ) А есть ещё и антипаттерны... Паттерн должен был решать типовую задачу, но когда их стало более ста, когда у каждого стало несколько реализаций, когда базовый mvc трактуют как тонкий-толстый контроллер и устраивают холивары (и еще какие и на любых уровнях от джуна до синьора)...есть над чем подумать.
@@ВладимирАсланов-э3ш паттерны не работают и не решают задачи бизнеса. Да, по сути они не нужны в силу невозможности их правильной реализации. Хотели как лучше, получилось как всегда. Изучать их нужно для формального прохождения собеседования. Далее, в 90% случаев вы будете писать код с минимальным использованием паттернов. Никто не даст вам проектировать архитектуру бизнес приложения, где ошибка это потерянные миллионы. Но вы можете всегда поиграться, реализовав паттерн в вашем маленьком пет проекте. Или снять ролик, где расскажете как использовать паттерн, чтобы сказать на собеседовании, что вы не только знаете паттерны, а ещё обучаете других людей.
@@NewUser78654 Полную околесицу несешь. Здесь канал о геймдеве и конкретно юнити. Какие бизнес-приложения, какие потерянные миллионы. Попробуй напиши игру без игрового цикла (тоже, внезапно, паттерн) или управление сложного персонажа без машины состояний. Ты поехавший.
понятно, но зачем вообще это использовать? какие то плюсы для серверной части? лучше при контроле античитом? лучше контроль за игроками в мультиплеере? почему нельзя просто получать или собирать состояния обычной стейтмашин каждого экземляра? зачем этот навес, интересно
Паттерн - это почти модно. Мода на них прошла, лет пять назад (Но это не точно (с)). Паттерн - это фишка на собеседовании, показать а знаешь ли ты GOF, банду четырех, Дядю Боба - модных и распиаренных чуваков из айти. Реального толку от паттерна мало. Мы планируем, выбираем паттерн, реализуем, работает - модифицируем, изменяем под новые требования бизнеса/заказчика, получаем легаси код. ) Анализируем, выбираем паттерн, переписываем всё с нуля (о да - типовое в вакансиях, мы крутые ты нам нужен чтобы переписать легаси код) - модифицируем...снова легаси. Паттерн? Не сработал. Кто вообще сказал, что код должен подчиняться паттерну, быть написан по паттерну? Заиграл технический долг в одном месте? Прихожу к бизнес аналитику - говорю какие у вас паттерны? Смотрит удивленно - у нас тут бизнес, а не игрушки. Вот система, автоматически генерирующая архитектуру под изменяющиеся бизнес задачи - мы "рисуем" логику, она создает архитектуру. А программисты наполняют всё это кодом, без изменения архитектуры - заполнить готовый класс, метод. Никакого создания своего класса, объекта и прочих вещей.
@@NewUser78654 Паттерны это просто общий "язык", способ формализации проблем и решений, которые и так есть в любом коде. Даже когда ты не применяешь паттерны, ты их применяешь, как минимум потому, что в любом современном высокоуровневом языке (типа C# ) или движке (типа Unity) часть из них уже вшита и используется, а часть ты "переизобретаешь" пока пишешь свою программу. В C# вшиты нативные события, коллекции, итераторы и так далее, в Unity из коробки есть те же события, компонент (в любом монобехе), прототип (в перфабах), машина состояний (в аниматоре), апдейт, игровой цикл, а в плагинах и того больше. Ты не можешь написать сколько-нибудь сложную программу и особенно игру без использования паттернов. Банальный пул объектов это тоже паттерн. Просто ты, видимо, не знаешь о том, что та или иная проблема давно уже считается типовой, решается стандартно и у нее есть общее название, которое понимают другие программисты.
@@NewUser78654 По комментам в других постах я уже понял, что ты вообще никакого отношения к C# и Unity не имеешь, а может и геймдеву, только ходишь и теоретизируешь, пуляя глупости невпопад.
@@NewUser78654 паттерн - это не реализация, это общая идея решения задачи, "шаблон". Суть архитектуры не мазюкать грязными ручками тяп-ляпы, а использовать структурный подход, а любой структурный подход по сути является шаблонным, т.е. принадлежит к какому то паттерну
Спасибо за урок! У меня практический вопрос к автору канала и опытным разработчикам. Вот если мы применяем компонент Animator к некому персонажу или объекту, то какая практика считается хорошей, использовать машину состояний этого аниматора или дублировать ее своей и синхронизировать со стейтами аниматора?
Чем меньше код привязан к движку, тем лучше. Теоретический перенос механики в другой проект в дальнейшем намного упростится не завися от каких-то компонентов.
Я не понял. Сначала FsmStateWalk наследовался от FsmState, но потом там появились transform и speed, получается, теперь FsmStateWalk наследуется от FsmStateMovement? А как же SetState тогда, где T должно быть FsmState? И вообще, что это за тип данных Type? У меня на него движок ругается, из-за этого ничего не работает.
Type это специальный тип данных для хранения названия типа данных. Например, можно написать так: Type t = typeof(String); Здесь он для того, что бы в список состояний нельзя было добавить два состояния одного типа. При попытке добавить повторно такой ключ уже будет.
Если есть деньги, то конечно эффективнее сразу PlayMaker ставить. Пиратить тоже можно, но от этого могут появляться разного рода проблемы, не рекомендую
Здравствуй, хочу реализовать реалистичное поведение персонажа, с учетом костей, с учетом физики... Движение ног влево, вправо, вперед, назад... Приятное поведение модели персонажа, лучше IK и анимации создать в блендере, а потом настраивать в юнити? Или лучше в блендере создать риггинг и через код в юнити создать IK, я видел там по поводу этого есть методы... У одного ютубера видел SHIMORO у него написано через IK... Размышляю по этому поводу, надеюсь у кого-то есть соображение, что лучше и как это реализовать. Было приятно если есть статья или видео на ютуб по этому поводу... Заранее спасибо...
Лучше взять готовое хорошо сделанное и разобрать по полочкам, пробовать собрать тоже самое с нуля рядышком. Самому с нуля разбираться очень долго и на это уйдут недели и месяцы труда. Материалов не подскажу, т.к. не использую такой подход, увы
Базово урок хороший, но мне кажется, что вы не очень явно выделили абстракцию. У вас у одного класса состояния можно быть только один экземпляр. Но как нам действовать, если у одного состояния могут быть разные параметры? Например, состояние стана, которое может длиться разное время время в зависимости от полученного урона.
да все хорошо, конечно, но как обращаться к скриптам которые на сцене, например если ты бежишь то изменить фокус на камере? Да хоть что так как монобех мы как я понимаю из состояний убераем, то и со сценой ни каких взаимодействий не будет. Например при хотьбе или беге что бы включались звуки шагов. И как проверить какое текущее состояние? Например если идле то отключить звук шелеста плаща?
Привет, никак у тебя на канале не могу найти создание каких-то конкретных игр.(Возможно просто не нашёл) Очень хотелось бы посмотреть на применение всех фишек из твоих видосов на деле.
Привет! Подскажите, пожалуйста, если я пишу простенький ИИ на основе FSM и мне необходимо, чтобы внутри какого-то состояния прокидывался рейкаст или в целом использовались какие-то монобеховские штуки, будет ли правильно наследовать базовый FsmState от монобеха?
Или лучше сделать метод, который принимает RayCastHit на уровне абстрактного класса FsmState и вызывать его в апдейте класса FsmExample по типу _currentState?.GetRaycast(hit);
@@styleoflazyphotography лучше вызывать метод FixedUpdate на FsmExample, соответственно в абстрактном классе FsmState добавить метод virtual FixedUpdate, и реализовать только в тех состояниях, где это надо
Это интуитивная фигня. Типа видишь, что у тебя в скрипте куча if'ов или switch'ей, которые вроде как постоянно проверяют состояние объекта, чтобы выполнить те или иные действия. И тогда запиливаешь стейт-машину и раскидываешь эти действия по отдельным состояниям. Плюсы этого паттерна в том, что: 1) не запутаешься в куче однотипных условных операторов и не будешь ловить баги из-за того, что забыл добавить какое-то условие в 100500-й строчке своего скрипта; 2) можешь легко добавлять и изменять эти состояния, не рискуя поломать основной скрипт. Как-то так.
Состояние - это идея о том, что у объекта есть состояния и их можно переключать (абстрактная идея). FSM - идея о том, что объект обладает конечным количеством конкретных состояний, заключённых в одну сущность
Контекст в паттерне Состояние - это и есть машина состояний, просто в него добавлена логика хранения всех состояний, чтобы при каждой смене состояния не создавать новое через new. А FSM - это контейнер компонентов, где компоненты как бы состояния, а контейнер как бы машина состояний. FSM не имеет к паттерну Состояние, как и к машине состояний, никакого отношения.
Как-то некрасиво передавать ссылку на fsm в конструкторе состояний. Проще сразу делать это внутри метода AddState. Сделать метод SetFsm для класса fsmState или как-то так. Ведь раньше чем состояние добавится нам эта ссылка всё равно нужна не будет. Все методы состояния вызываются только после его добавления через смену состояний.
зачем придумывают на каждую элементарную часть программирования отдельные названия и постоянно используют слова типа "паттерн"? чтобы видео записывать на ютубе и курсы продавать? скоро будут видимо курсы по паттерну нажать энтер или паттерн использования переменных, паттерн написания пробелов...
В целом ты прав, паттерны привносят больше конструктивы, т.е. это именнованные способы решения задач, и в дискуссии достаточно укзать название а не описывать принцип работы.
Все супер, но есть момент. Если перемещение основано на физике (через компонент Rigidbody), то возникает сложность. Поясню, инпут хорошо читается в методе жизненного цикла Update, а всю работу с физикой хорошо делать в методе FixedUpdate. Как тут быть? Есть ли какое-то элегантное решение без добавления костылей в данный паттерн? UPD: для себя решил в FsmState добавить виртуальный метод PhysicsUpdate, тем самым разделив обработку инпута и обновление физики.
Урок хороший, но как мне кажется совсем для новичков, а для новичков куча реализаций. Хотелось бы наверное более углубиться в раскрытие темы, т.к. на базе State machine строят архитектуры приложений. Поговорить о вариациях Transitions, возможно зацепить реализации с async, может быть построить какой-нибудь WindowManager. Построение простой архитектуры через StateMachine. Связывание например StateMachine и View. Может быть StateMachine и Zenject рассмотреть. Просто паттерн очень часто используется, а качественных уроков по его применению не особо много, кроме условного состояния персонажа. А так спасибо за видос!
Не, Zenject не надо, эту какаху вообще не надо
@@samserious5483 а какая лучшая альтернатива?
@@almightiey0.0 написать то, что лучше всего подходит проекту конуретному
@@samserious5483 zenject используют повсеместно. и неспроста - это лучший di контейнер. единственный минус заключается в его массивности и, наверное, в сложности освоения. для масштабных проектов - идеальный фреймворк. однако, для небольших лучше взять просто сервис локатор и не парится
Очень круто объяснил даже я понял
Отличный урок. Хотелось бы еще одним щелчком скачать данный пример.
Спасибо за урок!
Наверное лучший канал по юньке который я видел...
Спасибо за полезный контент.
Хорошее видео. Не посмотрел раньше из-за сессии + сейчас в проекте я бомбанул и переписываю машину состоянии))0)
Ещё приятно слышать твой радостный голос в видео))
Радостный? А обычно какой?)
@@gamedevlavka Я помню как серьёзный и вдумчивый. Значит из-за универа забыл :Р
Круто спасибо!
Даа) Область применения фсм у меня страдает) жаль что без транзишн с ней у меня как раз проблемы)
Ещё бы урок по zenject
неплохо вышло, но не могу не заметить пару замечаний
1) метод AddState лучше сделать генерик, как и SetState, тогда не придется вызывать метод GetType, который выполняется в рантайме, а заменить на typeof(T), выполняющийся на этапе компиляции
2) можно кэшировать Type (который также будет браться через typeof) последнего установленного стейта и в проверке на одинаковый тип тоже можно убрать GetType, а оставить что то типа:
if (currentStateType == typeof(T)) return
жду новых видосов)
вообще у меня на гите лежит еще более сложная стейт машина, мог бы дать тебе на ревью)
Со всем согласен) мне главное смысл показать)
А реализаций, как я и сказал, чуть ли не бесконечность)
На гите давай, с радостью гляну)
@@gamedevlavka скину в телегу, здесь удаляет
Так State - Mashine и есть новичковый паттерн. Его сразу после Синглтона изучают.
Ничего не понял, но очень интересно.
А ничего и не надо понимать. Напридумывали всякой ерунды (паттернов), мало было GOF - так вот получите 100+ паттернов. И реализуют эту выдумку разными способами. И паттерны и гибкая методология и фреймворки - легаси код на выходе ) А есть ещё и антипаттерны...
Паттерн должен был решать типовую задачу, но когда их стало более ста, когда у каждого стало несколько реализаций, когда базовый mvc трактуют как тонкий-толстый контроллер и устраивают холивары (и еще какие и на любых уровнях от джуна до синьора)...есть над чем подумать.
А в чем твой пункт? Паттерны не нужны? Или их не нужно изучать? Поясни мысль.
@@ВладимирАсланов-э3ш паттерны не работают и не решают задачи бизнеса. Да, по сути они не нужны в силу невозможности их правильной реализации. Хотели как лучше, получилось как всегда.
Изучать их нужно для формального прохождения собеседования. Далее, в 90% случаев вы будете писать код с минимальным использованием паттернов. Никто не даст вам проектировать архитектуру бизнес приложения, где ошибка это потерянные миллионы. Но вы можете всегда поиграться, реализовав паттерн в вашем маленьком пет проекте.
Или снять ролик, где расскажете как использовать паттерн, чтобы сказать на собеседовании, что вы не только знаете паттерны, а ещё обучаете других людей.
@@NewUser78654 Полную околесицу несешь. Здесь канал о геймдеве и конкретно юнити. Какие бизнес-приложения, какие потерянные миллионы. Попробуй напиши игру без игрового цикла (тоже, внезапно, паттерн) или управление сложного персонажа без машины состояний. Ты поехавший.
@@ВладимирАсланов-э3ш паттерны нужны, но к месту. Не надо их тыкать кругом.
понятно, но зачем вообще это использовать?
какие то плюсы для серверной части? лучше при контроле античитом? лучше контроль за игроками в мультиплеере? почему нельзя просто получать или собирать состояния обычной стейтмашин каждого экземляра? зачем этот навес, интересно
Паттерн - это почти модно. Мода на них прошла, лет пять назад (Но это не точно (с)). Паттерн - это фишка на собеседовании, показать а знаешь ли ты GOF, банду четырех, Дядю Боба - модных и распиаренных чуваков из айти. Реального толку от паттерна мало.
Мы планируем, выбираем паттерн, реализуем, работает - модифицируем, изменяем под новые требования бизнеса/заказчика, получаем легаси код. )
Анализируем, выбираем паттерн, переписываем всё с нуля (о да - типовое в вакансиях, мы крутые ты нам нужен чтобы переписать легаси код) - модифицируем...снова легаси. Паттерн? Не сработал.
Кто вообще сказал, что код должен подчиняться паттерну, быть написан по паттерну? Заиграл технический долг в одном месте?
Прихожу к бизнес аналитику - говорю какие у вас паттерны? Смотрит удивленно - у нас тут бизнес, а не игрушки. Вот система, автоматически генерирующая архитектуру под изменяющиеся бизнес задачи - мы "рисуем" логику, она создает архитектуру. А программисты наполняют всё это кодом, без изменения архитектуры - заполнить готовый класс, метод. Никакого создания своего класса, объекта и прочих вещей.
@@NewUser78654 Паттерны это просто общий "язык", способ формализации проблем и решений, которые и так есть в любом коде. Даже когда ты не применяешь паттерны, ты их применяешь, как минимум потому, что в любом современном высокоуровневом языке (типа C# ) или движке (типа Unity) часть из них уже вшита и используется, а часть ты "переизобретаешь" пока пишешь свою программу. В C# вшиты нативные события, коллекции, итераторы и так далее, в Unity из коробки есть те же события, компонент (в любом монобехе), прототип (в перфабах), машина состояний (в аниматоре), апдейт, игровой цикл, а в плагинах и того больше. Ты не можешь написать сколько-нибудь сложную программу и особенно игру без использования паттернов. Банальный пул объектов это тоже паттерн. Просто ты, видимо, не знаешь о том, что та или иная проблема давно уже считается типовой, решается стандартно и у нее есть общее название, которое понимают другие программисты.
@@NewUser78654 По комментам в других постах я уже понял, что ты вообще никакого отношения к C# и Unity не имеешь, а может и геймдеву, только ходишь и теоретизируешь, пуляя глупости невпопад.
@@NewUser78654 паттерн - это не реализация, это общая идея решения задачи, "шаблон". Суть архитектуры не мазюкать грязными ручками тяп-ляпы, а использовать структурный подход, а любой структурный подход по сути является шаблонным, т.е. принадлежит к какому то паттерну
Спасибо за урок! У меня практический вопрос к автору канала и опытным разработчикам. Вот если мы применяем компонент Animator к некому персонажу или объекту, то какая практика считается хорошей, использовать машину состояний этого аниматора или дублировать ее своей и синхронизировать со стейтами аниматора?
Чем меньше код привязан к движку, тем лучше. Теоретический перенос механики в другой проект в дальнейшем намного упростится не завися от каких-то компонентов.
Я не понял. Сначала FsmStateWalk наследовался от FsmState, но потом там появились transform и speed, получается, теперь FsmStateWalk наследуется от FsmStateMovement? А как же SetState тогда, где T должно быть FsmState? И вообще, что это за тип данных Type? У меня на него движок ругается, из-за этого ничего не работает.
Type это специальный тип данных для хранения названия типа данных. Например, можно написать так: Type t = typeof(String); Здесь он для того, что бы в список состояний нельзя было добавить два состояния одного типа. При попытке добавить повторно такой ключ уже будет.
Может сразу PlayMayker поставить, не?
Если есть деньги, то конечно эффективнее сразу PlayMaker ставить.
Пиратить тоже можно, но от этого могут появляться разного рода проблемы, не рекомендую
Здравствуй, хочу реализовать реалистичное поведение персонажа, с учетом костей, с учетом физики... Движение ног влево, вправо, вперед, назад... Приятное поведение модели персонажа, лучше IK и анимации создать в блендере, а потом настраивать в юнити? Или лучше в блендере создать риггинг и через код в юнити создать IK, я видел там по поводу этого есть методы... У одного ютубера видел SHIMORO у него написано через IK... Размышляю по этому поводу, надеюсь у кого-то есть соображение, что лучше и как это реализовать. Было приятно если есть статья или видео на ютуб по этому поводу... Заранее спасибо...
Лучше взять готовое хорошо сделанное и разобрать по полочкам, пробовать собрать тоже самое с нуля рядышком. Самому с нуля разбираться очень долго и на это уйдут недели и месяцы труда.
Материалов не подскажу, т.к. не использую такой подход, увы
@@gamedevlavka Хорошо, спасибо... Буду пробовать...
Базово урок хороший, но мне кажется, что вы не очень явно выделили абстракцию.
У вас у одного класса состояния можно быть только один экземпляр.
Но как нам действовать, если у одного состояния могут быть разные параметры?
Например, состояние стана, которое может длиться разное время время в зависимости от полученного урона.
да все хорошо, конечно, но как обращаться к скриптам которые на сцене, например если ты бежишь то изменить фокус на камере? Да хоть что так как монобех мы как я понимаю из состояний убераем, то и со сценой ни каких взаимодействий не будет. Например при хотьбе или беге что бы включались звуки шагов. И как проверить какое текущее состояние? Например если идле то отключить звук шелеста плаща?
ну наверное можно навесить каких-нибудь событий
Привет, никак у тебя на канале не могу найти создание каких-то конкретных игр.(Возможно просто не нашёл) Очень хотелось бы посмотреть на применение всех фишек из твоих видосов на деле.
Такие видео начнутся в обозримом будущем, пока их нет)
Привет! Подскажите, пожалуйста, если я пишу простенький ИИ на основе FSM и мне необходимо, чтобы внутри какого-то состояния прокидывался рейкаст или в целом использовались какие-то монобеховские штуки, будет ли правильно наследовать базовый FsmState от монобеха?
Или лучше сделать метод, который принимает RayCastHit на уровне абстрактного класса FsmState и вызывать его в апдейте класса FsmExample по типу _currentState?.GetRaycast(hit);
@@styleoflazyphotography лучше вызывать метод FixedUpdate на FsmExample, соответственно в абстрактном классе FsmState добавить метод virtual FixedUpdate, и реализовать только в тех состояниях, где это надо
Как писать fsm понятно, а что с ним дальше делать и как его использовать - не понятно.
Это интуитивная фигня. Типа видишь, что у тебя в скрипте куча if'ов или switch'ей, которые вроде как постоянно проверяют состояние объекта, чтобы выполнить те или иные действия. И тогда запиливаешь стейт-машину и раскидываешь эти действия по отдельным состояниям. Плюсы этого паттерна в том, что: 1) не запутаешься в куче однотипных условных операторов и не будешь ловить баги из-за того, что забыл добавить какое-то условие в 100500-й строчке своего скрипта; 2) можешь легко добавлять и изменять эти состояния, не рискуя поломать основной скрипт. Как-то так.
Nu blyat chto proisxodit tam?
В чем разница между FSM и паттерном Состояние?
Состояние - это идея о том, что у объекта есть состояния и их можно переключать (абстрактная идея). FSM - идея о том, что объект обладает конечным количеством конкретных состояний, заключённых в одну сущность
Контекст в паттерне Состояние - это и есть машина состояний, просто в него добавлена логика хранения всех состояний, чтобы при каждой смене состояния не создавать новое через new. А FSM - это контейнер компонентов, где компоненты как бы состояния, а контейнер как бы машина состояний. FSM не имеет к паттерну Состояние, как и к машине состояний, никакого отношения.
Как-то некрасиво передавать ссылку на fsm в конструкторе состояний. Проще сразу делать это внутри метода AddState. Сделать метод SetFsm для класса fsmState или как-то так. Ведь раньше чем состояние добавится нам эта ссылка всё равно нужна не будет. Все методы состояния вызываются только после его добавления через смену состояний.
зачем придумывают на каждую элементарную часть программирования отдельные названия и постоянно используют слова типа "паттерн"? чтобы видео записывать на ютубе и курсы продавать? скоро будут видимо курсы по паттерну нажать энтер или паттерн использования переменных, паттерн написания пробелов...
В целом ты прав, паттерны привносят больше конструктивы, т.е. это именнованные способы решения задач, и в дискуссии достаточно укзать название а не описывать принцип работы.
Все супер, но есть момент. Если перемещение основано на физике (через компонент Rigidbody), то возникает сложность. Поясню, инпут хорошо читается в методе жизненного цикла Update, а всю работу с физикой хорошо делать в методе FixedUpdate. Как тут быть? Есть ли какое-то элегантное решение без добавления костылей в данный паттерн? UPD: для себя решил в FsmState добавить виртуальный метод PhysicsUpdate, тем самым разделив обработку инпута и обновление физики.
Хорошее решение. При этом не забывай, что FSM можно строить разные, одну с физикой, другую без физики. Это как пример тебе с намеком)