Приветствую!
Я знаю, и вы в глубине души знаете, чего не хватает вашим карточным играм или играм «три в ряд». Системы скрытности!
И конечно же, любая уважающая себя система скрытности должна уметь принимать в расчет освещенность окружения вокруг игрока. Я был изумлен, раскопав тему и обнаружив аномально малое количество инфы. Поэтому спешу поделиться плодами.
Сегодня мы не будем разрабатывать полноценную систему скрытности для игрока, рассмотрим чисто взаимодействия с освещением.
Способ 1: коллайдеры
Простой и не особо ресурсоёмкий способ.
К каждому источнику освещения добавляем по сферическому коллайдеру. Делаем его триггером. Выставляем размеры примерно равными радиусу света.
Остальное — ясно, как тень. Пишем простенький скрипт, где в OnTriggerEnter() размещаем активацию расчета освещенности (чтобы источники света не работали «вхолостую», когда игрока рядом нет).
Сам расчет освещенности будет расположен в Update(). По сути, это обычный Physics.Raycast(). Если попадает в игрока — игрок в зоне света. Если не попадает — значит, игрок за препятствиями и, значит, в тени.
Также сюда можно дописать расчет расстояния между игроком и источником света. Таким образом, для определения освещенности у нас будет служить простенький float, который будет меняться в зависимости от расстояния до источников света. А использовать его можно где душа пожелает.
В точке 1 освещенность близка к максимальной. В точке 2 освещенность минимальна — между светом и точкой препятствие. В точке 3 освещенность средняя.
И это не всё! Можно добавить триггер-коллайдеров в различные «зоны теней», где игрок должен прятаться. В лучших традициях Manhunt. Аналогичным образом можно помечать коллайдером светлые зоны, имитируя, например, свет прожектора.
Преимущества:
- Легко настроить Point light'ы.
- Довольно экономен в плане ресурсов, если не спамить источники света.
Недостатки:
- Тяжело настраиваются Spot light и Directional light. Если для первого достаточно отпозиционировать коллайдер в области света (чтобы повышать видимость игрока при входе), то второй представляется настоящим ужасом. Нужно либо размещать коллайдеры у каждой тени (чтобы снижать видимость игрока при входе), либо постоянно проверять с помощью Physics.Raycast() между игроком и «солнцем» — находится тот под лучами или в тени.
- Большое количество коллайдеров захламляет сцену, усложняя физику.
- Необходимо аккуратно работать с пересекающимися источниками света.
- Динамичный свет (перемещающийся или меняющий интенсивность) нужно отдельно дописывать через скрипты.
Способ 2: RenderTexture
Что мы тут делаем? По сути — получаем «скриншот» с камеры, причем необязательно с камеры основной. А затем анализируем цвет скриншота, чтобы узнать, насколько яркий свет падает на предмет.
Для начала нам нужен объект, с которого мы будем «читать» свет. Создаем обычную сферу или плоскость, делаем маленькой (scale 0.1), размещаем вплотную к полу, делаем белой, убираем коллайдер:
Добавляем камеру (обязательно убираем audio listener и проверяем, что не стоит таг MainCamera). Привязываем её к нашему объекту. Ставим её чуть выше, направляем вниз. Выставляем в настройках не основной дисплей. Делать ли её ортографической — это на ваш вкус.
Под конец позиционируем её так, чтобы она смотрела на наш объект и только на него.
Под конец настраиваем Culling mask основной и вторичных камер, чтобы основная не отображала наши «световые» объекты, а вторичные видели только их, не захламляясь ничем прочим.
И тут начинается самое интересное. Привязываем к камере скрипт:
public Camera cam; // наша камера
RenderTexture tex;
Texture2D _tex;
void Start () {
// Создаем изображение для "скриншота".
// Да, он всего в один пиксель размером - больше не надо.
// Depth лучше на 0 не ставить - появляются различные баги.
tex = new RenderTexture (1, 1, 8);
// RenderTexture "читать" нельзя,
// поэтому создаем текстуру, в которую его переводим.
_tex = new Texture2D (1, 1, TextureFormat.RGB24, false);
}
void Update () {
// назначаем текстуру "скриншота" камере
cam.targetTexture = tex;
cam.Render ();
// делаем полученный скриншот активным
RenderTexture.active = tex;
// записываем в Texture2D
_tex.ReadPixels (new Rect (0, 0, 1, 1), 0, 0);
_tex.Apply ();
Color col = _tex.GetPixel (0, 0);
float vis = (col.r + col.g + col.b) / 3;
}
На выходе получаем float vis, который, по сути, является числовой репрезентацией уровня освещения, падающего на наш объект. Если источник близко — предмет белый — vis равен 1. Если темно — предмет черный — vis равен ~0.
Нам не нужно проделывать вышеуказанную операцию каждый фрейм, так что встраиваем небольшой секундный таймер:
float interval = 0;
void Update ()
{
interval += Time.deltaTime;
if (interval < 1)
return;
interval = 0;
// наш код
}
Далее мы всю нашу систему привязываем к игроку, чтобы она передвигалась вместе с ним. И наша переменная vis автоматически выдает освещенность вокруг игрока!
Данную систему можно использовать не только в связке с игроком. Можно разместить её где угодно и как угодно, создавая своеобразные датчики света. Как правило для их реализации имеются более эффективные пути, но всегда приятно иметь альтернативы?
Преимущества очевидны, поговорим о недостатках.
Недостатки
- Каждый «детектор света» (если их больше одного) требует отдельной камеры.
- Texture2D.ReadPixels() — ну крайне медленная. Даже если проделывать её раз в секунду, а не каждый фрейм, даже если разбить функции записи и чтения текстур на разные фреймы, все равно бывают пролагивания в 40-110ms.
- Эта система не учитывает некоторые редкие случаи. Например, на персонажа светят фонариком. Персонаж хорошо освещен, но свет падает на него и за ним, а не вниз, соответственно, наш детектор света показывает низкий уровень освещенности. Решить проблему можно, например, размещая детектор не у пола, а на уровне груди персонажа. Тогда нужно ставить две камеры с противоположных сторон, чтобы читать свет с любой стороны. Что замедлит систему ещё вдвое.
Автор: Xatmo