Unity action что это
Русские Блоги
каталог
1. Введение в блог
Использование Action и Func <> может упростить процесс объявления событий. В этой статье описывается только регистрация событий с помощью Action и Func <>.
2.Action And Func<>
public delegate void MyDelegate();
public event MyDelegate myEvent;
Вышеупомянутые два выражения могут быть сокращены как: публичное событие Action myEvent;
Действие: делегировать без параметров и без возвращаемого значения
Действие <>: делегировать с параметрами
Func : делегат с возвращаемым значением
Func : делегировать с параметрами и возвращаемым значением
3. Конкретное использование
Напрямую читайте код, все смотрят на код, это понятно
TestOne: сценарий 1 объявляет событие
TestTwo: метод 2 связан со сценарием 2
Очень понятный и понятный.
4. Ресурсы Скачать
Исходный код этой статьи:
5. Заключение
Я надеюсь, что студенты могут что-то получить после просмотра, и что у блогеров ограниченные возможности. Если в статье что-то не так, я надеюсь, что вы можете дать указатели для общения.
Группа обмена QQ: 806091680 (Чинар)
Эта группа была создана блоггером CSDN Чинаром, и я рекомендую это! Я тоже в группе!
Интеллектуальная рекомендация
Разработчик PL / SQL удаленно входит в систему с ошибкой идентификатора соединения Oracle TNS
Мозга
Обратитесь к источнику: IBM DeveloperWorks: https://www.ibm.com/developerworks/cn/linux/l-cn-cmake/ содержание: 1. Введение в Cmake 2, обработка каталога файлов с несколькими источниками 3, найти и ис.
Settings.System.getInt, чтобы получить некоторые настройки в Setting
В пользовательских компонентахpropertiesНеправильное использование неправильноVueГрамматика модифицировать.
Функции событий
Скрипт в Unity не похож на традиционную идею программы, где код работает постоянно в цикле, пока не завершит свою задачу. Вместо этого, Unity периодически передаёт управление скрипту при вызове определённых объявленных в нём функций. Как только функция завершает исполнение, управление возвращается обратно к Unity. Эти функции известны как функции событий, т.к. их активирует Unity в ответ на события, которые происходят в процессе игры. Unity использует схему именования, чтобы определить, какую функцию вызвать для определённого события. Например, вы уже видели функцию Update (вызывается перед сменой кадра) и функцию Start (вызывается прямо перед первым кадром объекта). В Unity доступно гораздо большее количество функций событий; полный список с дополнительной информацией по их применению можно найти на странице документации класса MonoBehaviour. Далее указаны одни из самых важных и часто встречающихся событий.
Обычные Update события
Физический движок также обновляется фиксированными по времени шагами, аналогично тому как работает отрисовка кадра. Отдельная функция события FixedUpdate вызывается прямо перед каждым обновлением физических данных. Т.к. обновление физики и кадра происходит не с одинаковой частотой, то вы получите более точные результаты от кода физики, если поместите его в функцию FixedUpdate, а не в Update.
Также иногда полезно иметь возможность внести дополнительные изменения в момент, когда у всех объектов в сцене отработали функции Update и FixedUpdate и рассчитались все анимации. В качестве примера, камера должна оставаться привязанной к целевому объекту; подстройка ориентации камеры должна производиться после того, как целевой объект сместился. Другим примером является ситуация, когда код скрипта должен переопределить эффект анимации (допустим, заставить голову персонажа повернуться к целевому объекту в сцене). В ситуациях такого рода можно использовать функцию LateUpdate.
События инициализации
Зачастую полезно иметь возможность вызвать код инициализации перед любыми обновлениями, происходящими во время игры. Функция Start вызывается до обновления первого кадра или физики объекта. Функция Awake вызывается для каждого объекта в сцене в момент загрузки сцены. Учтите, что хоть для разных объектов функции Start и Awake и вызываются в разном порядке, все Awake будут выполнены до вызова первого Start. Это значит, что код в функции Start может использовать всё, что было сделано в фазе Awake.
События GUI
В Unity есть система для отрисовки элементов управления GUI поверх всего происходящего в сцене и реагирования на клики по этим элементам. Этот код обрабатывается несколько иначе, нежели обычное обновление кадра, так что он должен быть помещён в функцию OnGUI, которая будет периодически вызываться.
Вы также можете определять события мыши, которые срабатывают у GameObject’а, находящегося в сцене. Это можно использовать для наведения орудий или для отображения информации о персонаже под указателем курсора мыши. Существует набор функций событий OnMouseXXX (например, OnMouseOver, OnMouseDown), который позволяет скрипту реагировать на действия с мышью пользователя. Например, если кнопка мыши нажата в то время, когда курсор мыши находится над определённым объектом, то, если в скрипте этого объекта присутствует функция OnMouseDown, она будет вызвана.
События физики
Физический движок сообщит о столкновениях с объектом с помощью вызова функций событий в скрипте этого объекта. Функции OnCollisionEnter, OnCollisionStay и OnCollisionExit будут вызваны по началу, продолжению и завершению контакта. Соответствующие функции OnTriggerEnter, OnTriggerStay и OnTriggerExit будут вызваны когда коллайдер объекта настроен как Trigger (т.е. этот коллайдер просто определяет, что его что-то пересекает и не реагирует физически). Эти функции могут быть вызваны несколько раз подряд, если обнаружен более чем один контакт во время обновления физики, поэтому в функцию передаётся параметр, предоставляющий дополнительную информацию о столкновении (координаты, “личность” входящего объекта и т.д.).
Система событий и откликов или задатки Visual Scripting для Unity3D
Введение
В прошлой моей статье были приведены способы обеспечения “мягкой” связи между компонентами игровой логики, основанные на уведомлениях и подписки на них. В общем смысле подобные уведомления можно посылать на любое, какое нам захочется, действие или событие, возникающее в работе компонента: от изменения переменной, до более сложных вещей. Однако зачастую определенные события требуют от нас выполнить ряд действий, которые нецелесообразно делегировать. Самым простым примером является звуковое оформление — в компоненте возникло событие, которое требует звукового сопровождения. В простейшем варианте мы вызовем функцию AudioSource.Play(), в чуть более сложном, функцию обертки над звуковой системой. В принципе ничего страшного в этом нет, если проект небольшой и в команде мало людей, которые совмещают в себе множество ролей, но если это проект крупный, где есть несколько программистов и саунд-дизайнер, то, в частности, настройка звуков превратится для программиста в кошмар. Не секрет же, что мы стараемся абстрагироваться от контента и поменьше с ним работать в плане настройки, ибо правильнее, если этим будут заниматься ответственные специалисты, а не мы.
Описанный выше пример применим не только к звукам, но и к множеству других вещей: анимации, эффекты, действия с элементами GUI и т.д., и т.п. Все это в совокупности представляет большую проблему на относительно больших проектах. В ходе своей работы я сталкивался с подобным множество раз и в основном все мои решения сводились к облегчению моего труда, что по сути тупиковый вариант, поскольку управление контентом напрочь отрезалось от дизайнеров. В конце концов, было решено придумать некую систему, которая позволила бы ответственным лицам самим управлять подобными вещами с минимальным участием программистов. Об этой системе и пойдет речь в данной статье.
События и отклики
Немного вступления
Я думаю ни для кого не секрет, что из себя представляет игровой процесс с точки зрения программиста. Однако все же я поясню, чтобы читателям была понятна моя точка зрения.
Итак, весь код, который мы пишем для реализации игрового процесса изначально представляет из себя набор компонентов, каждый из которых является генератором ряда событий, происходящих в игровом мире и влияющих на него, либо на другие компоненты. В простейшем варианте разбиение логики на компоненты происходит по событиям, которые группируются по критерию источника: персонаж, противник, интерфейс, окно интерфейса и т.д и т.п. Однако, сами по себе события еще не формируют игровой процесс, нам нужные действия (отклики), совершаемые в ответ на события. Например, персонаж сделал шаг и сгенерировал соответствующее события, в ответ на которое нам надо проиграть звук, проиграть эффект пыли из-под ног и возможно еще потрясти камеру. В более сложном варианте действия, совершаемые в ответ на события, могут также генерировать новые события, я это называю “эффектом звуковой волны”. Таким образом формируются “поверхность” нашего игрового процесса, как цепь событий и откликов.
В большинстве случаев описанный выше механизм формируется непосредственно в коде, но как было сказано ранее, ряд событий может (и будет) связан с контентом, обеспечивающим оживление и восприятие игры — ту часть, за которую отвечают художники и дизайнеры. При не правильном процессе управления проектом и неверном подходе к производству игры большинство подобных задач решается за счет программиста. Художник заленился – программист напишет “хак” и все будет хорошо. Для времен зарождения “russian-геймдев” это было хорошо, однако сейчас нужны качество, скорость и гибкость, поэтому я решил пойти другим путем и путь этот связан с Unity Editor
Extensions.
Пара слов об архитектуре
Ниже представлена небольшая блок-схема, показывающая основные элементы, необходимые для реализации механизма событий и откликов, которая позволит настраивать дизайнерам определенные элементы игровой логики самостоятельно.
Итак, базовыми элементами системы являются EventPoint (точка события в компоненте) и Action (отклик на событие). Каждый из этих элементов требует для настройки специализированного редактора, который будет реализован через Editor Extensions, в нашем случае через custom inspector (подробнее об этом будет написано в отдельном разделе).
В базовом сценарии каждая точка события может порождать не один отклик, а множество, при этом каждый отклик, может также быть генератором событий и содержать в себе точки входа в них. Помимо этого, отклик должен уметь оперировать базовыми сущностями движка Unity3D.
На основе выше написанного, я решил, что наиболее простым вариантом будет сделать Action наследником от MonoBehaviour, а EventPoint сделать частью логики компонентов в виде публичного поля, которое должно сериализоваться стандартным механизмом Unity3D.
Остановимся подробнее на этих элементах.
Как видно из кода все достаточно просто. EventPoint по сути — это список, в котором хранится набор Action’ов, которые привязаны к нему. Вход в точку события в коде происходит через вызов функции Occurence. ActionsList сделан публичным полем, чтобы он мог быть сериализован средствами Unity3D.
Как было сказано выше Action’ы должен быть наследником от MonoBehaviour, а также поскольку точке события должно быть все равно, что за отклик она вызывает, сделаем обертку над этим классом в виде абстракции.
Банально и примитивно. Функция Execute необходима для запуска логики отклика при вхождении в точку события.
Не сложно заметить, что основа системы, совершенно не представляет из себя никакой сложности и в общем-то примитивна. Все проблемы рождает реализация визуальной части системы, связанная с инспектором. Подробнее об этом читайте ниже.
Редактор
Прежде, чем приступить к разбору кода реализации редактора, хочу показать то, к чему мы будем стремиться визуально, а далее уже рассмотрим, как этого добиться. Ниже представлены изображения инспектора с точками событий и откликами на них.
Переопределение инспектора для компонента.
Получение и вывод списка откликов
Для того, чтобы система была максимально гибкой, нам необходимо в инспекторе выводить все реализованные нами отклики (классы наследники от ActionBase) и путем простого выбора из списка добавлять их к точке события.
Как видно, мы получаем список всех наследников от ActionBase, а затем исключаем саму асбтракцию из списка. Имя отклика берется из имени класса. Здесь можно пойти более сложным путем и забирать имя через атрибут класса и также через атрибут можно задавать и короткое описание, что делает отклик. Поскольку я именую классы детально то, я решил пойти по более простому пути.
Вывод списка точек события и публичных полей компонента
Поскольку нам надо переопределить способ вывода только точек событий, то функция рисования инспектора примет следующий вид.
Главный вопрос, который возникает при взгляде на данный код – это почему используется рефлексия, а не SerializedProperty. Причина проста, поскольку мы переопределяем инспектор для всех наследников MonoBehaviour, то мы понятия не имеем об именовании полей компонентов и их связи с типом, следовательно, мы не можем воспользоваться функцией serializedObject.FindProperty().
Теперь, что касается непосредственной отрисовки инспектора для точек события. Я не буду приводить код целиком, поскольку там достаточно все просто: используется FoldOut стиль для сворачивания и разворачивания информации об откликах. Остановимся на ключевых моментах.
Инспектор для откликов (Actions)
Как видно из кода выше, отклик добавляется как компонент к объекту, как итог такого действия, этот класс будет отображаться в окне инспектора и будет обработан соответствующим образом (с отображением всех полей и т.п.). Для нас это не является удобным, поскольку мы ходим все манипуляции по настройке параметров производить в одном месте. Первым делом запретим Unity выводить параметры классов откликов в инспекторе.
Поскольку система никак не ограничена (хотя это и возможно) в длине цепочки событий и откликов, то удаление отклика, стоящего в начале или середине цепочки, должно сопровождаться удалением всех откликов, стоящих ниже, а для этого надо рекурсивно пройти по этой цепочке и планомерно удалить все, включая компоненты с объекта, а затем уже удалить базовый отклик.
Отрисовка полей класса, реализующего отклик (Action)
Как было сказано ранее, мы не можем использовать serializedObject.FindProperty() в коде, ибо имена полей нам недоступны, помимо прочего для откликов нам не доступен и serializedObject, поэтому, как и для EventPoint, используется рефлексия, что в свою очередь является самым неудобным моментом во всей системе.
Как видно, нам придется определять вывод редактора для всех нужных типов поля ручками. С одной стороны, это жесть, с другой, есть возможность переопределить редактор поля, чем я пользуюсь, например, для звуков.
Если посмотреть на систему целиком, в ней нет ничего сложного, однако те преимущества, которые она дает, просто несопоставимы. Экономия времени и сил огромная. Фактически от программиста теперь требуется по запросу от дизайнеров создать в нужном компоненте логики точку события и вход в нее, а далее, что называется “умыть руки”, конечно помимо этого еще требуется написание кода откликов, но поскольку они являются по сути компонентами Unity, то здесь все ограничено лишь фантазией: можно делать крупные сложные вещи, можно сделать много мелких односложных.
В начале статьи я упомянул, что делегирование обработки событий другим компонентам бывает нецелесообразным, но на самом деле в этом есть свои преимущества с позиции пайплайна. Используя систему сообщений (смотри предыдущую статью), можно определить специализированные компоненты, для их обработки, чтобы иметь возможность создать входы в точки событий. Если поместить такие компоненты в заранее определенное место, то дизайнерам не придется рыскать по иерархии объектов. Этим подходом, например, можно пользоваться для настройки звукового окружения.
Как было сказано ранее система не ограничена длинной цепочки событий и откликов, по сути дизайнер может создавать сколько угодно большие и сложные логики поведения (отсыл к Visual Scripting), однако на практике это не удобно и не читаемо, поэтому лучше ограничивать цепочки на уровне хотя бы устных договоренностей. В ходе своей работы, мы с напарником использовали такие цепочки в расширенном варианте — там для редактирования использовалось отдельное окно (EdtiorWindow). Все было хорошо, пока не появлялась необходимость создавать сложные ветвления в логике. Разобраться спустя некоторое время в этой каше было просто нереально. Именно поэтому в своей системе я ушел в сторону простого решения на основе инспектора.
Unity Events, Actions, and BroadcastMessage
Events are a key part of general C# development. In Unity, they can sometimes be overlooked and less optimal options are used. If you’ve never used events, you’ll be happy to know that they’re easy to get started with and add a lot of value to your project architecture.
Before I cover the Event system in detail, I’d like to go over some of the common alternatives you’ll see in Unity projects.
BroadcastMessage
The BroadcastMessage method is part of the MonoBehaviour class. It allows you to send a loosely coupled message to all active gameobjects.
BroadcastMessage is simple to use and accomplishes the task of sending a message from one gameObject to another.
The biggest issue with BroadcastMessage is how it refers to the method that will be called. Because it takes a string as the first parameter, there is no compile time verification that the method actually exists.
It can be prone to typos, and it introduces danger when refactoring / renaming your methods. If the method name in the string doesn’t match the method name in your classes, it will no-longer work, and there’s no obvious indication of this beyond your game failing to work properly.
The other parameters are also not tightly coupled, so there’s no parameter verification. If your method required a string and an int, but you call BroadcastMessage with two strings, your call will fail, and again there’s no compile time indication of this issue.
Another big drawback to BroadcastMessage is the fact that it only broadcasts to children. For the example given, the UI Text would only receive the message if it’s a child of the player.
This does not work
Update Polling
Another common technique I see in Unity projects is polling properties in the Update() method.
Polling in an Update() method is fine for many things, but generally not the cleanest way to deal with cross gameobject communication.
In this example, we update the text of our UI every frame to match the HP value of the player. While this works, it’s not very extensible, it can be a bit confusing, and it requires us to make variables public that may not really need to be.
It also get a lot messier using Update Polling when we want to only do things on a specific situation. For updating the player HP UI, we may not mind doing it every frame, but imagine we want to play a sound effect when the player takes damage too, suddenly this method becomes much more complicated.
Events
If you’ve never coded an event, you’ve probably at least hooked into one before.
One built in Unity event I’ve written about recently is the SceneManager.sceneLoaded event.
This event fires whenever a new scene is loaded.
You can register for the sceneLoaded event and react to it like this.
Each event can have a different signature, meaning the parameters the event will pass to your method can vary.
In the example, we can see that the sceneLoaded event passes two parameters. The parameters for this event are the Scene and the LoadSceneMode.
Creating your own Events
Now, let’s see how we can build our own events and tie them into the example before.
In this example, we create a new delegate named PlayerTookDamageEvent which takes a single integer for our HP value.
Then we use the delegate to create an event named OnPlayerTookDamage.
Now, when we take damage, our Player class actually fires our new event so all listeners can deal with it how they like.
We have to check our event for null before calling it. If nothing has registered with our event yet, and we don’t do a null check, we’ll get a null reference exception.
Next, we need to register for this newly created event. To do that, we’ll modify the PlayerHPBar script like this.
To test our event, let’s use this PlayerDamager.cs script.
This script calls the TakeDamage() method on the Player every 5 seconds.
TakeDamage() then calls the OnPlayerTookDamage event which causes our PlayerHPBar to update the text.
Let’s see how this looks in action.
Example playing at 10x speed
We can see here that the players HP is decreasing and the text is updating.
Sidebar – Script Execution Order
You may have noticed something strange though. The first value shown is -1. This caught me off guard the first time, but the cause is visible in the code.
Before you continue reading, take a look and see if you can find it.
In our Player.cs script, we set the HP to 10 in the Start() method.
Our PlayerDamager.cs script also starts dealing damage in the Start() method.
Because our script execution order isn’t specified, the PlayerDamager script happens to be running first.
Fix #1
There are a few ways we can fix this.
We could change the script execution order so that Player always executes before PlayerDamager.
In the Script Execution Order screen, you can set the order as a number. Lower numbered scripts are run before higher numbered scripts.
Fix #2 – Better
While this would work, there’s a much simpler and cleaner option we can use.
We can change the Player.cs script to set our HP in the Awake() method instead of Start().
Awake() is always called before Start(), so script execution order won’t matter.
Back to Events
So now we have our event working, but we haven’t quite seen a benefit yet.
Let’s add a new requirement for our player. When the player takes damage, let’s play a sound effect that indicates that they were hurt.
PlayerImpactAudio
To do this, we’ll create a new script named PlayerImpactAudio.cs
Notice on line 13, we register for the same OnPlayerTookDamage event that we used in the PlayerHPBar.cs script.
One of the great things about events is that they allow multiple registrations.
Because of this, we don’t need to change the Player.cs script at all. This means we’re less likely to break something.
If you’re working with others, you’re also less likely to need to do a merge with another developers code.
We’re also able to more closely adhere to the single responsibility principal.
The single responsibility principle states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All itsservices should be narrowly aligned with that responsibility.
Robert C. Martin expresses the principle as follows: [1] “A class should have only one reason to change.”
The GameObject & AudioSource
You may have also noticed line 5 which tells the editor that this component requires another component to work.
Here, we’re telling it that we need an AudioSource on the gameobject. We do this because line 11 looks for an AudioSource to play our sound effect from.
For this example, we’ve created a new gameobject and attached both our PlayerImpactAudioSource.cs script and an AudioSource.
Then we need to assign an AudioClip. As you can see in the example, I’ve recorded my own sound effect and named it “oww”.
Now when we hit play, a sound effect triggers every time the TakeDamage() method is called.
Actions – Use these!
If this is all new to you, don’t worry, we’re almost done, and it gets easier.
Let’s take another quick look at how we defined the event in our Player.cs script.
First, we define the event signature, declaring that our event will pass one integer named “hp”.
Then we declare the event so that other code can register for it.
With Actions, we can cut that down to one line.
That’s all there is to it. Nothing else needs to change. All the other scripts work exactly the same. We’ve simply reduced the amount of code needed for the same effect.
While this is great for the majority of events, there is one reason you may want to still use the occasional event. That would be when your event has many parameters that can be easily confused with each other. My recommendation for that situation however is to re-think your events and see if the amount of data you’re passing is larger than it needs to be. If you really need to pass a lot of data to an event though, another great option is to create a new class or struct and fill it with your data, then pass that into the event.
Final Tip
Before I go, it’s also worth mentioning that you can have multiple parameters to an Action. To do this, simply comma separate your parameter types like this.
If you have questions or comments about Events or using Action, please leave a comment or send me an email.