Главная » Правописание слов » Как правильно написать логику поведения бота unity

Слово Как правильно написать логику поведения бота unity - однокоренные слова и морфемный разбор слова (приставка, корень, суффикс, окончание):


Морфемный разбор слова:

Однокоренные слова к слову:

Визуальный редактор логики для Unity3d. Часть 1

Введение

Здравствуйте уважаемые читатели, в сегодняшней статье я хотел бы остановиться на таком феномене в разработке приложений на Unity3d, как визуальная разработка или если бы точнее, разработка с применением визуального представления кода и логики. И, прежде чем продолжить хочу сразу уточнить, речь не идет о визуальном программировании, от слова “совсем”, никаких Blueprint вариаций в мире Unity и никаких генераций C# кода. Так что же тогда подразумевается под визуальным редактором логики? Если вам интересен ответ на данный вопрос добро пожаловать под кат.

Что такое визуальный редактор логики

Очень часто, а некоторые утверждают что всегда, в ходе разработки программисты пишут много разного кода, который делает много разных вещей, от системных, до механик игрового процесса. Этот код, если программист “настоящий”, как правило делается унифицированным и изолированным, чтобы его можно было использовать повторно (в рамках Unity этим кодом являются компоненты, они же наследники MonoBehavior). Не трудно представить, что такого кода, может быть очень много, особенно, если это не первый проект. А теперь представим себе, что мы начинаем новый проект и нужно сделать много быстрых и разных прототипов, а команда программистов ограничена, и вся занята основным проектом или проектами. Гейм-дизайнеры негодуют, им надо тестировать, проверять, продюсеры бегают по менеджерам, пытаясь выцепить для себя программиста, деньги ограничены, время уходит и т. п. и т. д.

Другая сторона медали, мы (программисты) написали много кода в виде компонентов, они висят большим списком на разных объектах сцены. И вот мы расширяем команду, наняли нового программиста, он открывает сцену, чтобы разобраться и тонет в каше вопросов: кто кого вызывает, в каком порядке, какой компонент с каким связан и каким образом и т. п. Вы резонно скажите: — “ А документация?” Документация есть (хотя вовсе не факт), но тут речь идет о том, чтобы порог вхождения новых людей в команду был максимально низким, и время на этот процесс максимально коротким.

Как же решить описанные выше ситуации? Ответ в названии статьи – Визуальный Редактор Логики. Что же это такое? Это среда, которая позволяет в визуальном виде оперировать различными компонентами логики и настраивать их взаимосвязи (в “мягком” варианте), а также опосредованно от сцены оперировать объектами сцены. Если описать это в сказочно-простом виде, то это как в детстве собирать из кубиков разные конструкции (только в нашем случае, кубики не связаны между собой жестко, убрав нижней, наша конструкция не упадет).

Итак, с определением мы разобрались, но что это нам дает в итоге?

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

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

После этого на какое-то время идея была отложена. Следующие проекты уже я делал на чистом коде, но в голове по-прежнему витала идея. Постепенно с учетом прошлых ошибок формировалось окончательное (как мне казалось) видение и список требований. И в 2017 году, после завершения очередного фриланс-проекта, я решил, что могу себе позволить потратить 6-7 месяцев на работу над этим плагином и попробовать его выложить в Asset Store (он и до сих пор лежит, и называется Panthea VS). С точки зрения опыта работы над таким сложным проектом, было все очень круто, финансовая стороны увы печальна, все-таки уметь программировать и уметь продавать, это вещи разные. Это был ноябрь 2017 года, после чего я слегка потерял мотивацию, развелся, сменил город, поменял полностью жизнь, и чтобы не впасть в самоедство решил посмотреть под другим углом на тему визуального редактора логики. Итогом стал uViLEd, который я решил выложить бесплатно. Поскольку я подписал фуллтайм контракт, то работать над ней пришлось по выходным вечерам и праздникам и заняло это весь 2018 и начало 2019 года. uViLEd — это большое переосмысление Panthea VS, полный рефакторинг кода под компилятор Roslyn (C# 7+), поэтому работает все только начиная с версии Unity3d 2018.3.

Примечание: на Panthea VS вышли несколько проектов (Android и iOS, в частности Грузовичок Лёва и машинки), в принципе опыт использования удачный, но всплыл момент, что одно дело написать редактор, другое дело научиться правильно его использовать (как бы это странно не звучало ).

uViLEd и как им пользоваться

Введение

Итак, что получилось у меня в итоге, сначала смотрим картинку, а затем продолжим (далее картинок будет больше).

Что же из себя представляет в своей основе визуальный редактор логики.

Ключевые возможности uViLEd

Работа с редактором

Для начала работы с редактором необходимо открыть сцену, а затем запустить сам редактор.

После запуска редактора инициализируем сцену (кнопка обновления в редакторе), после чего появится возможно создавать или добавлять существую логику в сцену.
После создания логики (файл, который будет описывать компоненты, их параметры, связи между компонентами, переменные и их значения), можно наполнять его смыслом. Для добавления компонента или переменной, достаточно перетащить соответствующий скрипт в область редактора логики. Альтернативный вариант воспользоваться каталогом, который формируется автоматически с использованием атрибута ComponentDefinition.

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

Рассмотрим подробнее что из себя представляем сам компонент в визуальном редакторе.

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

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

Связи устанавливаются на основе правила совпадения типов, которые передает и принимает точка. Исключения составляется входная точка, которая не принимает данных, с ней можно соединить любую выходную точку. При установке связи система автоматически проверяет совпадение типов и показывает можно или нет эту связь установить. Входные и выходные точки задаются в коде компонента с помощью следующих классов:

Первые два класса используются для входной и выходной точки, которые не принимают параметры, вторые соответственно наоборот. Т может быть любым типом.

Для того, чтобы вызвать цепочку связей, необходимо использовать функцию Execute.

Для того, чтобы обработать такой вызов, в коде компонента необходимо установить обработчик для входной точки (о том, где именно мы поговорим чуть позже).

И в завершении, хочу упомянуть важный момент о связях. Если связей из одной точки выходит несколько, то в редакторе есть возможность настроить порядок их вызова.

Работа с переменными

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

Как видно базовым классом для переменных является generic класс Variable, где T тип данных, которые заключены в переменной, Т может быть любым типом, который может быть сериализован.

В редакторе переменные отображаются в следующем виде:

Для изменения отображения значений переменной достаточно переопределить метод ToString в типе Т.

Таким образом переменная данного типа будет выглядеть как:

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

После этого, ссылку можно установить в инспекторе, при этом, в выпадающем меню, буду показаны только переменные типа CustomData, что значительно упрощает работу с ними.

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

Здесь стоит учитывать, что Changed работает по условию Equals, поэтому, если используются структуры и классы, этот метод необходимо переопределять, чтобы гарантировать правильность работы.

Работа с объектами Unity

Из-за особенностей работы системы uViLEd прямые ссылки на объекты Unity не могут быть использованы в ней, поскольку они не могут быть восстановлены при загрузке логики. Чтобы решить эту проблему, была создана специализированная оболочка VLObject, которая позволяет создавать такие ссылки, а также сохранять и загружать их. Помимо прочего, эта оболочка имеет специальный редактор свойств, который позволяет вам получать компоненты из любого объекта сцены (см. Рисунок ниже), если вы хотите обратиться к ним. С VLObject вы можете хранить ссылки не только на объекты сцены и их компоненты, но также на префабы и файлы ресурсов, такие как текстуры, звуки и т. п.

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

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

Создание компонента логики

Для того, чтобы создать компонент логики, программисту достаточно добавить в проект простой C# скрипт-файл (также можно создать сразу компонент или переменную через специальное меню во вкладке Project) и изменить в нем код на следующий:

Как было сказано ранее ComponentDefinition это атрибут, который позволяет автоматически формировать каталог компонентов, здесь следует учесть, что Color (цвет заголовка) задается в строке в виде HEX-формата.

LogicComponent – это базовый класс всех компонентов, в свою очередь являющийся наследником ScripatableObject.

Ниже приведен простой пример компонента, который делает ветвление по входящему значению типа bool:

Итак, как видно из кода, мы создали входную точку компонента, которая принимает значение типа bool и две выходных точки, которые вызываются в зависимости, какое значение мы получили.

Наверное, у вас сейчас возникает вопрос, что это за Constructor такой? Поясняю. По умолчанию ScriptableObject не поддерживает методы типа Start, Update и т. п., но при этом поддерживаются методы Awake, OnEnable, OnDisable и OnDestroy. Так вот Awake (как и OnEnable), в случае если ScriptableObject создается через метод CreateInstance вызывается всегда, и в этом, собственно, и есть проблема. Из-за того, что объект создается в памяти для сериализации в режиме редактора необходимо было исключить работу кода компонента в этот момент, поэтому и был добавлен аналог Awake в виде метода Constructor, тоже самое касается и методов OnDisable и OnDestroy при удалении объекта в редакторе. Если необходимо корректно обработать удаление компонента (при выгрузке сцены, например), то необходимо использовать интерфейс IDisposable.

В целом, как видно, ничего сложно в создании компонентов нет. Это обычный класс, в котором может быть любой код, какой только захотите. В частном случае компоненты вообще могут не содержать входных и выходных точек, а общаться с помощью глобальный сообщений. Кстати, для этого в uViLEd присутствует класс GlobalEvent — это система сообщений, основанная на типах данных (подробнее о ней можно почитать в моей статье).

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

Для этого в коде компонента, достаточно имплементировать один или оба интерфейса IInputPointParse и IOutputPointParse. Ниже приведет пример кода абстрактного generic-класса для компонентов ветвления Switch, здесь автоматически создаются выходные точки, в зависимости от параметра SwitchValues.

Отладка логики

Для отладки логики в uViLEd предусмотрено несколько механизмов:

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

Заключение

В данной статье я постарался вкратце познакомить вас с подходом в разработке который я применяю в своих текущих проектах. Несмотря на изначальный скепсис (и мой в том числе) практика показывает значительно удобство его применения, особенно при прототипировании. Помимо прочего значительно упростилась работа гейм-дизайнеров, они не лезут в сцену, не тыкают в объекты для настройки игрового процесса, для них создается отдельная логика с набором данных переменных и компонентов, в которых они могут спокойно все настраивать. Также большое преимущество дает тот факт, что в моих проектах, часто контент загружается извне. С применением визуального редактора логики я могу обновлять баланс игрового процесса без обновления основного приложения, в некоторых случаях, можно менять и саму логику.

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

Это была первая часть из серии статей про визуальный редактор логики uViLEd, далее будут части про:

PS: я постарался рассказать о ключевых моментах uViLEd, если у вас появится желание вы можете ознакомится с ним скачав плагин из Asset Store, там присутствует полная документация (правда на английском): руководство пользователя, гайд для программистов и API.

Источник

Правильно программируем. Используем полиморфизм. Общая логика игровых персонажей

Unity3D. ИСПОЛЬЗУЕМ ПРЕИМУЩЕСТВА ООП. ПОЛИМОРФИЗМ

ВНИМАНИЕ!
Сразу хочу предупредить, читайте комментарии к статье, там KumoKairo предложил более совершенный подход к решению поставленных в статье задач.

Доброго времени суток. В этом уроке мы рассмотрим несколько важных приемов программирования на движке Unity 3D. Думаю, статья поможет как начинающим, так и опытным игроделам.

Целью статьи ставлю осознание мощи и безграничной удобности использования всех прелестей ООП. В частности ПОЛИМОРФИЗМ. Кроме того затронем несколько других важных вопросов.

Так же немного поговорим о концепции создания собственных моторов, общей логике игровых персонажей и о методах взаимодействия их между собой и с окружающим миром. Мы поговорим, как сделать это просто и логично, легко редактируемо (модернизируемо). Как сделать так чтобы при добавлении новых элементов в игру, не приходилось переписывать (дополнять) уже написанное.

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

Статья рассчитана на людей имеющих представление Unity3D. Если вы этого представления не имеете, настоятельно рекомендую прочитать эти статьи:

1) Очень хорошие статьи: habrahabr.ru/post/112287, habrahabr.ru/post/113859
Во второй статье примеры скриптов на JS. Думаю что проблем перевести на C# не будет, даже если вы новичок и в JS и в С#. Единственное скажу что строка на JS:

На С# будет выглядеть так:

А такая строка на JS:

На С# будет выглядеть так:

2)Статья: habrahabr.ru/post/141362 Там же на сайте есть продолжение этой статьи. Называются они «Unity3d. Уроки от Unity 3D Student (B00-B16)» Всего 4 статьи. На данный момент, по крайней мере. Там справа вверху есть поисковик на сайте. Думаю, справитесь.

3) habrahabr.ru/post/148410 Тоже необходимо прочесть и осознать. Есть еще вторая часть этой статьи. Опять вам поможет поисковик на сайте.

5) Много на этом форуме интересных статей. Поищите сами, если хотите.
Итак. Вы усвоили все что прочитали, поэкспериментировали, возможно, уже сделали маленькую игру. Возможно не маленькую. Плохо если вы только прочитали эти статьи, но ничего сами не написали. Практика и еще раз практика

Но сначала рассмотрим парочку второстепенных вопросов. (На всякий случай)

Первая половина статьи носит демонстрационный характер. Переписывать код не надо.
Я употребляю следующие слова как синонимы: скрипт-класс

КЛАССЫ, ЭКЗЕМПЛЯРЫ КЛАССОВ

Мы занимаемся объектно-ориентированным программированием (ООП). Существуют Классы и экземпляры классов. Класс – это как бы формочка или чертёж объекта. А отлитая фигурка по формочке – это экземпляр класса. Все автобусы являются экземплярами класса Автобус. Автобус Васи, вашего соседа, это экземпляр класса Автобус. У Васиного автобуса есть наклейка на левом борту, царапины по всему корпусу, спущено правое переднее колесо, пол бака бензина, разбито одно стекло, не работает одна из дверей. Этот автобус обладает многими индивидуальными свойствами. У другого вашего соседа тоже есть автобус. У него тоже есть царапины, наклейки, но нет разбитых окон, колеса не спущены и еще много индивидуальных особенностей именно этого автобуса. Оба автобуса экземпляры класса Автобус. Класс автобус – это чертеж. Чертеж — это еще не созданный автобус и на нем нельзя покататься. У него нет никаких царапин и спущенных колёс. Без чертежа нельзя создать ни одного автобуса. Без описания класса мы не сможем создать ни один экземпляр класса. Класс служит не только для того чтобы создавать экземпляры, по нему можно например узнать длину автобуса. Но вот покататься не чертеже нельзя. В Unity имена классов пишутся с большой буквы (GameObject, Transform, Animation и т.п.), а имена экземпляров классов с маленькой.

Имеет смысл все имена переменных начинать с маленькой буквы, чтобы отличать их от имен классов. Так диктует нам Unity.

КЭШИРОВАНИЕ

Многие программы записывают куда-либо промежуточные или вспомогательные результаты работы, чтобы не вычислять их каждый раз, когда они понадобятся. Это ускоряет работу, но требует дополнительной памяти (оперативной или дисковой).


Рассмотрим этот скрипт. Что в нем плохого? Вроде все хорошо. Некий объект движется в Лево с постоянной скоростью 10 единиц в секунду. Именно благодаря Time.deltaTime происходит равномерное движение, не зависящее от текущей производительности на конкретном компьютере.

Плохо в этом скрипте то, что каждый кадр вызывается transform.position, а точнее плохо, что вызывается transform. На самом деле transform написанный у нас в Update() означает gameObject.transform. А это в свою очередь то же самое что gameObject.GetComponent ( ). А вот это уже означает что каждый кадр ваш скрипт ищет среди всего что повешено на gameObject именно transform. А зачем каждый раз искать, когда можно найти один раз и запомнить(кэшировать) (обратите внимание gameObject написано с маленькой буквы. То есть это экземпляр класса GameObject)

Казалось бы, ничего не изменилось. Но на самом деле это гораздо рациональнее использует ресурсы машины.
Не верите мне. Откройте документацию Unity и там сказано кэшировать НАДО.

Рассмотрим еще пару примеров того что кэшировать надо:

Внимательно почитайте комментарии в коде

Разумеется, кэшировать есть смысл, только если то, что мы собираемся кэшировать будет много раз использоваться. (Часто это более одного раза). Кроме того стоит понимать если мы удалим у нашего gameObject компонент который предварительно кэшировали в переменную “A”, а потом снова добавили, в «А» уже ничего не будет. Поэтому нет смысла кэшировать то, что постоянно удаляется и снова добавляется. Хотя можно кэшировать сразу после очередного добавления. Кэшировать можно только Экземпляры класса (может ещё что-нибудь врать не буду), но вот кэшировать типы данных и структуры не надо.
Например, это можно кэшировать


Там справа написано, что это класс(class), да еще и значок слева тоже особенный. (Красным обведено)
А вот это кэшировать нельзя

Так как position это не экземпляр класса. Это структура Vector3

И опять Visual Studio нам показывает, что Vector3 это структура. И значок у неё тоже другой.
Итак. Кэшировать только классы. Переменные всякие типа float, byte, vector3 и т.п. не кэшировать! Ну сами понимаете что:

Кэшировать нужно не только скрипты, повешенные на объекты, но и сами объекты.

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

ОБЩАЯ КОНЦЕПЦИЯ МОБА (ПЕРСОНАЖА)

Все эти скрипты могут управлять передвижением персонажа (моба). Управляют не сами, а через промежуточный скрипт называющийся МОТОРОМ. Тесть у Мотора есть несколько методов необходимых для управления персонажем. Например:

Зачем это надо? Есть две основные причины. Во-первых это просто логично и в случае если вам захочется что-то поменять в Моторе вам не придётся менять код в разных скриптах которые двигают персонажа. Они все делают это через один скрипт. Измените код в Моторе и все. Кроме того запуск анимации при движении тоже осуществляется из Мотора. Особенно это важно, если проект разрабатывается командой. Человек, который делает мотор, понятия не имеет, откуда будут вызываться методы мотора. А человек делающий скрипт Поведения понятия не имеет, как реализован метод MobMotor.MoveLeft() главное, что он двигает персонажа влево. Это все что ему нужно. Таким образом, для слаженной работы им надо знать только два простых факта. Моб умеет ходить влево и вправо. Все! Ничего лишнего. Во вторых…… самое главное во вторых. Но мы еще не знаем что такое полиморфизм. Вернемся к этому преимуществу позже.

Продолжаем. У нас есть главный скрипт, который следит за обстановкой и включает нужные скрипты когда надо. А те перемещают персонаж только через Мотор. Есть скрипт, который отвечает за Здоровье персонажа. Например:


Кстати напомню Public — означает, что объявленная переменная(метод и т.п.) будет доступна всем скриптам вообще. Private-доступно только внутри самого скрипта

Рассмотрим скрипт MobHp подробно. Персонаж появляется и сразу имеет здоровье 100 единиц. Когда персонаж получает дамаг(получение дамага отслеживает главный скрипт а не сам MobHp), главный скрипт вызывает метод MobHp.SetDamage(float damage). Этот метод возвращает либо true либо false. Если вернулось true, значит моб пережил получение дамага. И главный скрипт продолжает свое функционирование. Если вернулось false, моб умер. Что происходит с мобом после смерти главному скрипту все равно. Он этого не знает. В нашем случае при смерти воспроизведется звуковой файл (звук умирания персонажа) и через 5 секунд персонаж исчезнет(см. картинку). Роль главного скрипта проста. Он в случае получения false отключает все скрипты Поведения. Отключает Мотор персонажа. Он отключит все. То есть моб замрет, проиграется звук умирания моба, а затем он исчезнет. Конечно, можно в скрипт MobHp добавить генерацию осколков, воспроизведение анимации падения на землю персонажа. Все что захотите. Прелесть в том, что скрипт MobHp полностью независим. Если основной скрипт и MobHp будут программировать разные люди, то все что им понадобится знать для слаженной работы, это то, что персонаж получает дамаг через метод MobHp.SetDamage(float dfamage). И если Метод вернет false, значит, моб умер. Все! Никакой лишней информации. И если создатель MobHp захочет изменить содержимое скрипта, это ни как не касается основного скрипта!

Его величество ПОЛИМОРФИЗМ

Итак, мы добрались. Тех, кто не бросил читать поздравляю. Повествование басни о полиморфизме начну с нескольких простых жизненных примеров, а затем мы распространим их на нашу общую концепцию моба (персонажа).

Для определенности пусть наш Главный скрипт называется MyBehaviour

И теперь перебираем всех мобов и наносим им дамаг(Обратите внимание. Мы представили что у главного скрипта(MyBehaviour) есть метод MyBehaviour.SetDamage(float damage), который в свою очередь вызывает метод MobHp.SetDamage(float damage) у скрипта MobHp. Оба скрипта висят на персонаже(мобе))
Код в скрипте на снаряде.

Надо вводить новый тег для башни «tower». А скрипт самого снаряда примет вид:

Какие минусы у такого подхода. Очевидно, что скрипт Снаряда зависит от количества видов мобов. Таким образом, если вы захотите добавить один вид моба, вам придётся залезть в скрипт Снаряда и модернизировать его. Теперь представим что в игре 500 видов мобов. Скрипт снаряда будет иметь порядка 1500 строк кода. Стоит учесть, что в игре наверняка будет несколько десятков видов снарядов. Считайте сами. Если скрипт Снаряда будет делать один человек, а основные скрипты мобов и пушек и других персонажей (MyBehaviour, MyBehaviour2, MyBehaviour3 и т.д.) другой человек, то для слаженной работы им потребуется знать количество мобов и точные названия(а может и содержание) их основных скриптов (представьте что названия будут не вида MyBehaviourN, а RobotBehaviour, TowerBehaviour, MonsterBehaviour, TigerBehaviour и т.д.)

Вернемся к выключателю, которому все равно, что он включает. Если у вас в игре 5 видов дверей, то для каждого нужен будет свой выключатель (кнопка). И если вы захотите добавить один вид двери, то вам придётся добавлять кнопку, либо модернизировать имеющиеся. И если кнопки и двери будут программировать разные люди, то им придется знать все названия (а может и содержание) всех скриптов всех дверей и кнопок.

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

Конечно, есть способы решения этой проблемы. Но самый изящный и простой в понимании, на мой взгляд, это ПОЛИМОРФИЗМ. Не буду писать строгих определений, вы их сами можете найти.

Кстати в чем разница между Protected и Private? Все что Private не наследуется. Все что Protected наследуется. Итак если хотим унаследовать Глобально объявленный(Public) метод или переменную и др., то ничего делать не надо. Она и так наследуется. Если хотим унаследовать локальную для данного класса переменную, то вместо Private надо в родительском классе написать Protected.
Итак с этого момента даю команду, КОДИТЬ! Все что дальше будет написано мною, должно быть написано вами. Экспериментируйте, разбирайтесь, набивайте ручки.

NewMobHp и другие скрипты нам мне пригодятся удалим их(если сделали). Кстати класс NewMobHp является наследником не только MobHp, но и MonoBehaviour. Так как MobHp является наследником класса MonoBehaviour.

Вот так: MonoBehaviour—> MobHp—> NewMobHp.

MobHp непосредственный(ближний) родитель для NewMobHp.
Открывайте Unity, мы начинаем кодить! Возможно, кое что будет не понятно, но когда доберемся до создания маленькой игры все станет ясно.

Создадим папочку MyScript. В ней создадим скрипт MyBehaviour. Это будет родитель для всех главных скриптов всех мобов(вообще всего что умеет получать дамаг). Мы можем создать в нем метод SetDamage. И в дальнейшем, новые главные скрипты будут наследовать его. И у всех мобов будет этот метод. Но у каждого вида моба процесс получения дамага может быть реализован по-разному. Как сделать, чтобы метод реализовывался по своему, для каждого скрипта?

ПОЛИМОРФИЗМ — не знаю, как дать ему определение. Может вы сами потом его дадите. Мы лучше рассмотрим все на примере. Напишем в MyBehaviour такое:

Пустой метод SetDamage. Зачем он нужен? Да он нам не нужен. Обратите внимание на ключевое слово Virtual (смотри рисунок). Это означает, что данный метод может быть переопределен в других скриптах, которые будут наследниками скрипта MyBehaviour. То есть они его наследуют, но реализуют по-своему. Теперь сделаем скрипт MobBehaviour:

И так разберем, что тут происходит:
1- Мы явно указали, что MobBehaviour наследник класса MyBehaviour
2- Пишу слово Override, ставлю пробел и Visual Studio сама подсказывает, что мы можем ПЕРЕОПРЕДЕЛИТЬ. Как видите там есть SetDamage(float damage), он то нам и нужен. Все остальные методы, это методы объявленные в Родительских скриптах более дальних чем MyBehaviour. Например ToString(). Мы можем переопределить ToString()и запрограммировать по своему работу этого метода для нашего класса MobBehaviour. Но нам это не надо. Выберем SetDamage(float damage)и видим (на рисунке цифра 3) что данный метод изначально определен в MyBehaviour(Тоесть он унаследован от класса MyBehaviou). Можете посмотреть где определены ToString() и другие. Теперь выберем строку SetDamage(float damage), нажмем Enter и вот что Visual Studio нарисовала сама:

Base.SetDamage(damage); — здесь Base- это как рас родитель(класс) в котором был определен метод SetDamage(float damage). Или если сказать по-другому, ссылка на класс, от которого был унаследован этот метод. У нас он определен в MyBehaviour. Он пуст, если вы не забыли. То есть можно что — нибидь туда добавить. И тогда Base.SetDamage(damage); будет вызывать этот код, а после можно что-нибудь добавить для конкретного наследника. Это нужно если у вех наследников MyBehaviour в методе SetDamage(float damage) будет что-то общее. Но нам это не нужно. Пока удалите все лишнее из MobBehaviour. Оставьте так

Отвлечемся от MobBehaviour. Создадим, новый скрипт MyHp

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

Все, как и в прошлый раз, только есть слово Override в пере объявлении метода SetDamage(float damage). И указано что класс является наследником класса MyHp. Еще немного изменено умирание моба. Нет никаких звуков, моб просто увеличится при смерти в 3 раза.

Снова вспоминаем о MobBehaviour. Сделаем его таким:

Мы пере объявили метод SetDamfge(float damage). Обратите внимание на то, что thisHp объявлена как MyHp, хотя Скрипт, отвечающий за здоровье, называется MobHp. Вот он ПОЛИМОРФИЗМ. Мы можем работать с MobHp так же как с MyHp. Мы можем переменной типа MyHp присвоить экземпляр класса MobHp, так как он является наследником MyHp. Таким образом, скрипту MobBehaviour все равно какой наследник класса MyHp лежит в переменной thisHp. Главное что у thisHp есть метод SetDamfge(float damage). И его можно использовать! Мы можем поменять скрипт MobHp на другой (тоже наследник MyHp). И все будет работать.

Мы будем искать среди под трансформов (можно сказать дочерних трансформов. Но не в смысле наследников как с клссами) трансформ камеры с помощью метода transform.FindChild(“Main Camera”). Тесть этот метод найдет среди подобъектов FirstPersonController`а объект с именем “Main Camera” и достанет из него Transform. И мы этот Transform кэшируем в переменную cameraTransform.
7-если с последнего выстрела прошло больше чем shootPause, то можно делать выстрел.
8-Создаем копию патрона.
9-Получаем rigiBody патрона.
10-Добавляем скорость патрону.
11-Запоминаем время выстрела.
Кидаем этот скрипт на игрока. Перетягиваем префаб патрона куда надо

Запускаем игру. Находим моба. Стреляем в него, используя левую кнопку мыши. Считаем выстрелы. Он должен разбухнуть с 4 попадания. В консоли появилось «Моб умер. ». Моб разбухает и через 5 секунд исчезает.

Моб разбух так как в скрипте MobHp мы написали thisTransform.localScale = new Vector3 ( 3, 3, 3 ). Если у вас моб не достаточно разбух, измените троечки на большие значения. Ну, вот мы сделали то, что вы и так, наверное, могли сделать. В чем же прелесть полиморфизма? А вот в чем. Создадим еще одного моба. Тоже шарик. Повесим на него новый скрипт MonsterHp:

Как видите, он почти не отличается от скрипта MobHp. Все отличия подчеркнуты красным цветом. Мы кэшировали материал, и меняем его цвет в зависимости от здоровья. Если вы не знаете, как создать цвет, который хотите, посмотрите в интернетах по поводу RGB цветовой модели.

Сделаем у нашего главного героя тег «Player»

Теперь создадим скрипт MonsterBehaviour:

Обратите внимание, мы кэшировали transform игрока, а не весь gameObject. Мы будем обращаться именно к трансформу, а через gameObject это бы выглядело так: player.transform что эквивалентно player.getComponent(). А зачем это каждый раз делать, когда можно один раз и сразу кэшировать. Конечно, от пары десятков лишних getComponent ничего особо не поменяется. Но если проект очень большой. И каждый программист будет тыкать, где попало getComponent и GameObject.Find(…) и GameObject.FindGameObjectWithTag (…)и т.п. Это в совокупности может замедлить выполнение программы. Понизить FPS. А вам оно надо?

Повесим скрипты MonsterBehaviour и MonsterHp на нашего нового моба (Новый шарик). У меня получилась такая сцена:

Маленький шарик — это патрон(Bullet). Его можно удалить, все равно он уже в префабах (папка MyPrefab). Обратите внимание, у меня оба моба называются одинаково(Shape). Можно поменять. Назовем Нового моба Monster, а старого Tower.

Запускаем игру. Подходим к новому мобу поближе. Он начинает двигаться в нашу сторону. Отбегаем подальше, он останавливается. Стреляем в моба, он меняет цвет. Стреляем еще, он краснеет и уже не бегает за нами. Через 5 секунд исчезает. Можно пострелять в старого моба и убедиться, что он все еще способен получать дамаг и умирать.

Итак, в чем прелесть ПОЛИМОРФИЗМА. Мы добавили моба и написали ему скрипт. Но мы ничего не писали в скрипте игрока и в скрипте патрона.


Это аналог скрипта BulletScript. Это скрипт Танкового патрона. При соударении с чем-либо он при помощи GameObject.FindGameObjectsWithTag ( «Mob» ) получает массив всех игровых объектов с тегом «Mob». Проверяет, есть ли у этого объекта компонент MyBehaviour(или его наследник) и если есть, то проверяет расстояние до этого объекта. И если это расстояние меньше чем atacDistanse, то этому объекту, через интерфейс (метод SetDamage) MyBehaviour`а (или любого его наследника) наносится дамаг.

ПОДВЕДЕМ ИТОГИ

В данном простом проекте мы использовали Полиморфизм в двух местах.

Вот ссылка на урок полиморфизма от создателей Unity unity3d.com/learn/tutorials/modules/intermediate/scripting/polymorphism. На мой взгляд, он отвратительный и не понятный. Я уже не говорю про то, что он на английском. Более подробно о полиморфизме можете почитать тут sharp-generation.narod.ru/C_Sharp/methods.html. Но для начала прочтите первые пару глав этого самоучителя (на который ссылка дана), а то не понятно будет.

Переосмыслим командный подход к созданию крупных проектов.

Вернемся к большой схеме. «Действие» — это класс, наследники которого получают несколько методов, необходимых для реализации событий. Например, это могут быть методы StartAction(), Open(), Close(). Наследниками класса «Действие» являются все сюжетные скрипты, скрипты дверей, люков и т.п. Такой подход позволяет создать универсальный триггер (кнопку и т.п.), который может вызвать любой из этих методов (или сразу несколько методов) у массива скриптов «Действие». Это очень удобно. В Unity даже есть стандартный скрипт для такого триггера. Только он немного усовершенствован. Благодаря полиморфизму каждый наследник класса «Действие» по-своему реализует методы StartAction(), Open(), Close(). Разные двери по-разному открываются, разные сюжетные скрипты по-разному реализуют StartAction(). Пример с дверью: игрок заходит в универсальный триггер, к триггеру привязана дверь (точнее наследник класса «Действие» от этой двери).Триггер вызывает метод Open(). Настройка триггера заключается в том, чтобы поставить галочку, какой метод именно надо вызвать у указанного наследника класса «Действие». То есть не надо к каждому классу двери делать свой класс кнопки. Кнопка универсальная. А в ней объявлена публичная переменная:

Далее в коде кнопки (триггера и т.п.) при определенных обстоятельствах (нажатие кнопки или вхождение в триггер нужного объекта) вызывается нужный метод объекта action

Снова смотрим на большую схему. Находим там Моторы игрока, зомби, и пушки. Мы уже обсуждали концепцию мотора. Но теперь есть Главный мотор, от которого все остальные наследуют методы. Будет очень хорошо, если у всех моторов будут одинаковые методы. Например, могут быть такие методы Jump(), MoveDirection(Vector3 direction), Run(bool run),Stop(), LookTo(Vector3 direction), GoToPoint(Vector3 point). Все методы кроме последнего нужны Игроку. А вот последний для мобов. Он может содержать в себе алгоритм поиска пути и т.п. Благодаря полиморфизму каждый наследник главного мотора сможет по-своему реализовать эти методы, а так же можно будет менять мотор с одного на другой. Они полностью взаимозаменяемы.

Теперь рассмотрим Скрипты поведения. «Зомби-не занят», «Зомби-атака» и др. Они так же являются наследниками одного скрипта. Можно выделить общий метод. Например, SetTarget(Transform target). Через этот метод можно передать скрипту поведения текущую цель моба. Ведь именно наследник класса MyBehaviour занимается анализом обстановки, следовательно, он должен при включении нужного скрипта поведения передать ему необходимую информацию. В реальной игре этих методов, возможно, будет больше.

3) После составления подобной схемы приступаем к её реализации. Сначала делаем скрипты «родители». Все остальные будут наследоваться от них. Например, скрипт «родитель» мотора можно сделать таким:

Красным подчеркнуто ключевое слово, которое позволит наследникам этого класса пере определить эти методы по-своему. Пере объявление будет выглядеть так:

Тогда в MonsterBehaviour заведём переменную типа MyMotor, в которой будет храниться какой-либо наследник скрипта MyMotor, повешенный на этого моба. Конечно, моб не прыгает в нашей игре. Это просто пример. Для мобов можно придумать еще один метод у класса MyMotor и назвать его SetTarget(Transform target). И когда надо двигаться за игроком (или другим мобом) использовать этот метод. А Мотор моба сам будет принимать решение о направлении движения прыжках и т.п.
На последнем примере хорошо видно: все, что нужно для реализации полиморфизма — это ключевые слова Virtual и Override, а так же явное указание родителя класса

Вот и все. Несколько «слов» в коде сделают наши скрипты независимыми и взаимозаменяемыми. Вообще надо делать все скрипты не зависимыми друг от друга.

Продолжим использовать полиморфизм и другие прелести ООП в следующей статье.

Источник

Теперь вы знаете какие однокоренные слова подходят к слову Как правильно написать логику поведения бота unity, а так же какой у него корень, приставка, суффикс и окончание. Вы можете дополнить список однокоренных слов к слову "Как правильно написать логику поведения бота unity", предложив свой вариант в комментариях ниже, а также выразить свое несогласие проведенным с морфемным разбором.

Какие вы еще знаете однокоренные слова к слову Как правильно написать логику поведения бота unity:



Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *