Доброго времени суток!
Я разбираюсь в работе 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
Там указано десятичное представление, но сути не меняет.
Комментариев нет:
Отправить комментарий