Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Как использовать маски слоев (layermask) и для чего пишут 1 << layer?

Доброго времени суток!
Я разбираюсь в работе Unity, смотрю уроки, некоторые скрипты и иногда встречаю конструкцию вида 1 << layer, где layer является целым числом. Чаще это встречаю в методах, связанных с физикой, например Raycast
public int groundLayer = 5;
void SomeMethod() { // do smth... if (Physics.RaycastAll(transform.position, transform.forward, 100.0f, 1 << groundLayer).Length > 0) { ... } // do smth... }
И не только с физикой:
LayerMask Collisionmask; int layer = 5;
void SomeMethod() { // do smth... if ((Collisionmask.value & (1 << layer)) == 0) { ... } // do smth... }
Читал, что это какие-то маски. Они указывают на слои. Но мне не понятно, что это за маски, как их использовать, причем тут вообще слои? Что же такое 1 << layer, например?
Может кто-нибудь объяснить подробно? Спасибо.


Ответ

Запаситесь попкорном и колой (пирожками и чаем). Это будет долго, но познавательно. ))
Что это и к чему?
В Unity, как мы все (или не только лишь все) знаем, имеются слои (layers). Освежить память о том, что это (а если не знаете, то прочитать и ознакомиться) можно в документации. Используются они по-разному. К примеру в 2D с их помощью можно обозначить и отделить задний/средний/передний план. Что-то будет сзади, что-то спереди. В более сложных играх слоёв может быть больше и объекты можно группировать по этим слоям. Например, можно сделать слой для NPC и выделить из него слой для враждебных NPC - Enemies и прочие.
В общем случае манипуляции с layerMask (маской) позволяют выяснить, находится ли проверяемый объект (GameObject) в указанном слое или нет (неважно, что это за объект и каким образом он проверяется). В зависимости от результата проверки производить задуманные действия.
Рассмотрим, к примеру, Raycast. Если обратимся к документации, то можно увидеть, что метод выглядит так:
public static RaycastHit2D Raycast(Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);
Один из параметров: int layerMask = DefaultRaycastLayers. Работает он так: из некой точки в каком-то направлении выпускается луч (допустим это такая магия на расстоянии), этот луч падает на какую-то преграду (объект) и, например, если этот объект находится в слое Enemies - ушмякать его. О примере (Collisionmask.value & (1 << layer)) == 0 поговорим ниже. Но суть у него аналогичная: определить принадлежность за счет маски > сделать что-то в зависимости от...
Надеюсь с сутью понятно, пойдем далее.
Как это работает.
Количество слоев в Unity: 32. Нумеруются они с 0 до 31.

LayerMask (маска) - представляется в виде 32-битного целого числа:
0000 0000 0000 0000 0000 0000 0000 0000
Я отделил каждые 4 бита для лучшей читабельности.
Маска использует свои 32 бита для представления различных флагов (состояние объекта, например: 1 - включен, 0 - выключен). В данном конкретном случае каждый флаг (бит) представляет собой конкретный слой. Возьмем для разбора первые 8 слоев и запишем двоичное представление каждого слоя (Первый бит (младший) находится с правой стороны).
слой № Двоичное Десятичное ---------------------------------- Layer 0 = 0000 0001 = 1 Layer 1 = 0000 0010 = 2 Layer 2 = 0000 0100 = 4 Layer 3 = 0000 1000 = 8 Layer 4 = 0001 0000 = 16 Layer 5 = 0010 0000 = 32 Layer 6 = 0100 0000 = 64 Layer 7 = 1000 0000 = 128
Вот что представляют из себя слои в двоичном представлении. Как видим, конкретный слой определяется одним конкретным битом.
Теперь пробуем разбираться на примере if ((Collisionmask.value & (1 << layer)) == 0)
Мы видим переменную layer, которая является целым числом. Эта переменная — номер слоя (0, 1, 2, 3, 4 ...), НЕ десятичное и НЕ двоичное представление. Collisionmask в инспекторе представляет собой выпадающий список со слоями:

А Collisionmask.value в свою очередь является двоичным представлением комбинации выбранных слоёв (так как выбрать можем не один а несколько слоев). Для понимания: например, если мы хотим создать маску для слоя #2 и #5, то двоичное представление будет такое:
0010 0100 // #2,#5
или например слои #1, #2, #5, #6 будут представлять из себя:
0110 0110 // #1,#2,#5,#6
Что мы хотим? Мы хотим узнать, входит ли в эту маску проверяемый нами слой layer? И в зависимости от этого что-то сделать. В примере выше мы хотим зайти в условие и что-то сделать, если наш объект, находящийся в слое 5, не входит в указанную маску.
Допустим LayerMask = 0110 0110; // #1,#2,#5,#6, а номер нашего слоя int layer = 5;
Нужно слой 5 привести к его двоичному представлению, соответствующее в слоях. Посмотрим в табличку вверху. Слой 5 имеет десятичное представление равное 32. Оно, в свою очередь, в двоичном представлением является 0010 0000. Оно получается за счет применения побитого оператора сдвига влево << относительно самого младшего бита (0000 0001) именно на 5 значений (фантастика!). Сдвигаем единичку 5 раз влево:

Как видим получилось число 0010 0000 и оно действительно входит в нашу маску 0110 0110, потому что маска содержит единичку в позиции номер 6 справа, и слой 5 имеет единичку на такой же позиции. Если не верите на слово, то проверить легко: надо произвести побитовое умножение (побитовое И "&") между маской и проверяемым значением. Это, кстати, и делается в строке Collisionmask.value & (1 << layer)
0110 0110 // #1,#2,#5,#6 & (and) 0010 0000 // #5 --------- 0010 0000
Видим, что входит в маску. Если бы не входил, то побитовое умножение дало бы результат 0. Как пример доказательства, можем проверить входит ли слой 7 в данную маску или нет. Сдвигаем младший бит на 7 позиций влево:

Получаем 1000 0000. Проверяем
0110 0110 // #1,#2,#5,#6 & (and) 1000 0000 // #7 --------- 0000 0000
Видим 0. Не входит. И раз оно не входит, значит срабатывает наше условие сверху:
if ((Collisionmask.value & (1 << layer)) == 0) => if (0 == 0) => true
значит мы заходим в условие и делаем что нам нужно. Это то, что мы и искали.

Дополнение 1
Помимо проверки конкретного слоя на вхождение в маску, можно проверять, наоборот, все слои, кроме указанного. Нужно только использовать, так скажем, оператор инверсии (~). Он меняет в битовом представлении операнда 0 на 1, а 1 - на 0. Таким образом ~00000010 будет равен 11111101, что будет тестировать каждый слой, кроме #2. Если хотите исключить несколько слоев, то нужно будет скомбинировать маски через оператор "ИЛИ":
int layer1 = 3; int layer2 = 5; int layermask = ~((1 << layer1) | (1 << layer2)) // НЕПРАВИЛЬНО: ~(1 << layer1) | ~(1 << layer2)

Дополнение 2
На самом деле не всегда нужно писать 1 << что-то. Например в Raycast можно в параметр передать Collisionmask.value, где Collisionmask имеет тип LayerMask, а он, в свою очередь, в инспекторе выглядит как выпадающий список (см. изображение №2). Очень удобно.
Также не стоит хардкодить числами, т.е. не писать 1 << 7 или 1 << 5, т.к. можно забыть, что это за 5 и 7. Для того, чтоб узнать нужное число слоя, можно пользоваться LayerMask.NameToLayer, пример:
int npcLayer = LayerMask.NameToLayer("NPCLayer"); int npcMask = 1 << npcLayer;
и тоже самое при "групповых" масках:
int npcGoodLayer = LayerMask.NameToLayer("NPCGood"); int friendsLayer = LayerMask.NameToLayer("Friends"); int npcGoodMask = 1 << npcGoodLayer; int friendsMask = 1 << friendsLayer; int finalmask = npcGoodMask | friendsMask; // Or, (1 << npcGoodLayer) | (1 << friendsLayer)
Дополнение 3
На самом деле такая конструкция 1 << ... встречается не только в Юнити. Это известная практика вообще в программировании. К примеру есть у какого-либо производителя продукт, приложение и он предоставляет API для других программистов.
API - набор готовых классов, процедур, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) или операционной системой для использования во внешних программных продуктах. Используется программистами при написании всевозможных приложений. (c) Википедия
У API могут быть ограничения на доступ различного рода. Программист указывает маску, к чему он хочет получить доступ, а на сервере приложения проверяется это и в случае несовпадения по маске - выдает предупреждение или еще что-либо. Самый простой пример Вконтакте и его права доступа для API

Там указано десятичное представление, но сути не меняет.

Комментариев нет:

Отправить комментарий