Страницы

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

понедельник, 25 ноября 2019 г.

Как использовать маски слоев (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, например?

Может кто-нибудь объяснить подробно? Спасибо.
    


Ответы

Ответ 1



Запаситесь попкорном и колой (пирожками и чаем). Это будет долго, но познавательно. )) Что это и к чему? В Unity, как мы все (или не только лишь все) знаем, имеются слои (layers). Освежит память о том, что это (а если не знаете, то прочитать и ознакомиться) можно в документации Используются они по-разному. К примеру в 2D с их помощью можно обозначить и отделить задний/средний/передний план. Что-то будет сзади, что-то спереди. В более сложных играх слоёв может быть больше и объекты можно группировать по этим слоям. Например, можно сделать слой для NPC и выделить из него слой для враждебных NPC - Enemies и прочие. В общем случае манипуляции с layerMask (маской) позволяют выяснить, находится л проверяемый объект (GameObject) в указанном слое или нет (неважно, что это за объект и каким образом он проверяется). В зависимости от результата проверки производить задуманные действия. Рассмотрим, к примеру, Raycast. Если обратимся к документации, то можно увидеть что метод выглядит так: public static RaycastHit2D Raycast(Vector2 origin, Vector2 direction, float distanc = 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 и оно действительно входит в нашу маску 011 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 Там указано десятичное представление, но сути не меняет.

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

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