Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
Aml Pages - это электронная записная книжка, оперативный блокнот на все случаи жизни, каталогизатор документов. Позволяе...
McAfee Stinger - это бесплатный, не требующий установки антивирус, для обнаружения и удаления с компьютера известных вир...
Удобная программа для работы с музыкальными коллекциями. Она понимает самые распространенные виды музыкальных форматов, ...
Программа для управления драйверами, которая позволяет полностью удалить драйвер из системы. Driver Fusion позволяет уда...
Программа для видеочатов высокого качества, обмена сообщениями и дешевыми звонками на телефоны в любую страну....
OSzone.net Microsoft Разработка приложений Языки программирования Делаем простое освещение в 2D игре. Подробные примеры на C# и XNA для новичков RSS

Делаем простое освещение в 2D игре. Подробные примеры на C# и XNA для новичков

Текущий рейтинг: 3 (проголосовало 2)
 Посетителей: 485 | Просмотров: 573 (сегодня 0)  Шрифт: - +
В этой публикации я постараюсь предельно просто рассказать, как можно легко и быстро сделать динамическое освещение в 2D игре на XNA; без шейдеров, карт нормалей и вообще, без дополнительных ресурсов. Целью у нас будет красивая игровая сцена с ночным небом, темным задним фоном, освещенным фонарями, передним фоном и игровым меню. Количество источников света не ограничено (в разумных пределах, конечно), форма произвольная. Освещается только передний фон. Читателю желательно иметь элементарные навыки работы с XNA, поскольку будет много кода.

*
Увеличить


Несколько слов в качестве вступления. Я перерыл гору статей о том, как можно сделать динамическое освещение в 2D игре, и для меня стало откровением, что несмотря на обилие материала, большинство из них попросту бесполезны, т.к. я элементарно не могу повторить в коде то, о чем там пишут. Есть статьи с примерами в несколько строк кода на псевдокоде. Для новичка они практически бесполезные. Есть работающие примеры с шейдерами, которые можно даже скачать и запустить. Горы кода, 2 источника света и ни слова о том, как добавить еще. Да и сама тема шейдеров для простенькой 2D игры — это явный перебор. Вот так и родилась идея этой статьи: дать возможность читателю за 30 минут добавить в свою игру приличное динамическое освещение, тем самым сэкономив кучу времени и нервов.


Итак, поехали. Для начала немного теории и простых примеров. Нарисуем несколько квадратов на сером фоне:

*

GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
spriteBatch.Draw(box1, Vector2.Zero, Color.White);
spriteBatch.Draw(box2, new Vector2(40, 50), Color.White);
spriteBatch.Draw(box4, new Vector2(150, 50), Color.White);
spriteBatch.Draw(box3, new Vector2(260, 50), Color.White);
spriteBatch.End();

Здесь мы заливаем экран черным цветом. Потом рисуем серый прямоугольник box1, и поверху 3 квадрата: box2, box3 и box4. Теперь попробуем немного изменить параметры метода spriteBatch.Begin(), а именно:

spriteBatch.Begin(SpriteSortMode.BackToFront, blendState);

Переменную blendState мы объявляем так:

var blendState = BlendState.Additive;

Или так. Что одно и то же:

var blendState = new BlendState();

blendState.AlphaBlendFunction = BlendFunction.Add;
blendState.AlphaDestinationBlend = Blend.One;
blendState.AlphaSourceBlend = Blend.SourceAlpha;
blendState.BlendFactor = Color.White;
blendState.ColorBlendFunction = BlendFunction.Add;
blendState.ColorDestinationBlend = Blend.One;
blendState.ColorSourceBlend = Blend.SourceAlpha;
blendState.ColorWriteChannels = ColorWriteChannels.All;
blendState.ColorWriteChannels1 = ColorWriteChannels.All;
blendState.ColorWriteChannels2 = ColorWriteChannels.All;
blendState.ColorWriteChannels3 = ColorWriteChannels.All;
blendState.MultiSampleMask = -1;

Получится такой результат:

*

По сути, мы заявляем, что рисуя box2 поверху box1, нужно не перекрывать пиксели, а смешивать их по цвету. Само смешивание проводиться по формуле:

  • (source * sourceBlendFactor) blendFunction (destination * destinationBlendFactor)
  • (box2.RGB * BlendState.ColorSourceBlend) BlendFunction.Add (box1.RGB * BlendState.ColorDestinationBlend)
  • (box2.RGB * Blend.SourceAlpha) + (box1.RGB * BlendState.One)
  • (box2.RGB) + (box1.RGB) (если картинки не прозрачны)


Другими словами, если у вас есть 2 пикселя с одинаковыми координатами и цветами: R:10 G:20 B:255 и R:1 G:2 B:255, то результирующий пиксель выйдет с цветом R:11 G:22 B:255, т.е. станет светлее. Из этого можно сделать вывод, что играя настройками класса BlendState, можно затенять и засвечивать отдельные области спрайтов, тем самым получая желаемы эффект 2D освещения. Чтобы немного засветить круглую область на спрайте, нужно нарисовать круглый темно-серый спрайт поверху. Чтобы, наоборот, сделать круглую область темнее, нужно опять же нарисовать круглый темно-серый спрайт, но использовать функцию BlendFunction.ReverseSubtract.

На этом с теорией все, переходим к практике. Чтобы получить красивую сцену, как на первой картинке, будем делать следующее:

  1. Готовим спрайт с задним планом. Для этого весь задний план рисуем на одном спрайте (RenderTarget2D);
  2. Готовим спрайт с передним планом;
  3. Готовим спрайт с тенями. Для этого берем спрайт со второго шага и в точках источников света рисуем черные круглые спрайты — это области, которые не будут затеняться.



*
Увеличить


Теперь всё это выводим на экран:

  1. Рисуем ночное небо;
  2. Рисуем спрайт с задним планом;
  3. Еще раз рисуем спрайт с задним планом, но с коэффициентом -0.9 (см. код). Получим темный задний план;
  4. Рисуем передний план;
  5. Рисуем спрайт с тенями, коэффициент -0.9;
  6. Рисуем меню.


Результат:

*
Увеличить


И, собственно готовый код:

// 1. Готовим спрайт с задним планом.

GraphicsDevice.SetRenderTarget(backgroundSprite);
GraphicsDevice.Clear(Color.Transparent);

spriteBatch.Begin();
spriteBatch.Draw(background, Vector2.Zero, Color.White);
spriteBatch.End();

// 2. Готовим спрайт с передним планом.

GraphicsDevice.SetRenderTarget(foregroundSprite);
GraphicsDevice.Clear(Color.Transparent);

spriteBatch.Begin();
spriteBatch.Draw(foreground, Vector2.Zero, Color.White);
spriteBatch.End();

// 3. Готовим спрайт с тенями.

GraphicsDevice.SetRenderTarget(foregroundShadow);
GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
spriteBatch.Draw(foregroundSprite, Vector2.Zero, Color.White);
spriteBatch.Draw(light, new Vector2(620, 490), null, Color.White, 0.0f,
new Vector2(light.Width / 2, light.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.Draw(light, new Vector2(100, 500), null, Color.White, 0.0f,
new Vector2(light.Width / 2, light.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.Draw(light, new Vector2(620, 90), null, Color.White, 0.0f,
new Vector2(light.Width / 2, light.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.Draw(light, new Vector2(290, 270), null, Color.White, 0.0f,
new Vector2(light.Width / 2, light.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.Draw(light, new Vector2(400, 270), null, Color.White, 0.0f,
new Vector2(light.Width / 2, light.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.Draw(light, new Vector2(510, 270), null, Color.White, 0.0f,
new Vector2(light.Width / 2, light.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.End();

// Теперь это все выводим на экран.

GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.Black);

// 1. Рисуем ночное небо.
// 2. Рисуем спрайт с задним планом.

spriteBatch.Begin();
spriteBatch.Draw(sky, Vector2.Zero, Color.White);
spriteBatch.Draw(backgroundSprite, Vector2.Zero, Color.White);
spriteBatch.End();

// Готовим обьект BlendState.

var blendState = new BlendState();

blendState.AlphaBlendFunction = BlendFunction.ReverseSubtract;
blendState.AlphaDestinationBlend = Blend.One;
blendState.AlphaSourceBlend = Blend.BlendFactor;

// Тот самый загадочный коэффициент -0.9 (255 * 0.9 = 230, BlendFunction.ReverseSubtract = -1)

{
blendState.BlendFactor = new Color(230, 230, 230, 255);
blendState.ColorBlendFunction = BlendFunction.ReverseSubtract;
}

blendState.ColorDestinationBlend = Blend.One;
blendState.ColorSourceBlend = Blend.BlendFactor;
blendState.ColorWriteChannels = ColorWriteChannels.All;
blendState.ColorWriteChannels1 = ColorWriteChannels.All;
blendState.ColorWriteChannels2 = ColorWriteChannels.All;
blendState.ColorWriteChannels3 = ColorWriteChannels.All;
blendState.MultiSampleMask = -1;

// 3. Еще раз рисуем спрайт с задним планом, но с коэффициентом -0.9.

spriteBatch.Begin(SpriteSortMode.BackToFront, blendState);
spriteBatch.Draw(backgroundSprite, Vector2.Zero, Color.White);
spriteBatch.End();

// 4. Рисуем передний план.

spriteBatch.Begin();
spriteBatch.Draw(foregroundSprite, Vector2.Zero, Color.White);
spriteBatch.End();

// 5. Рисуем спрайт с тенями, коэффициент -0.9.

spriteBatch.Begin(SpriteSortMode.BackToFront, blendState);
spriteBatch.Draw(foregroundShadow, Vector2.Zero, Color.White);
spriteBatch.End();

// 6. Рисуем меню.

spriteBatch.Begin();
spriteBatch.Draw(menu, Vector2.Zero, Color.White);
spriteBatch.End();

Рисунки я взял с игры «Craft the World», хотя сам к игре никакого отношения не имею. Внимательный читатель может отметить странные артефакты на гранях здания и деревьев — это все потому, что я довольно небрежно порезал скриншот в фотошопе. Сама техника освещения на скорость отображения практически не влияет, поэтому общая производительность игры страдать не должна. Готовый проект для Visual Studio 2010 можно скачать здесь: xnagames.codeplex.com/releases/view/136161

Надеюсь, эта статья будет полезна начинающим игроделам. Желаю удачи!

Автор: Андреев Сергей  •  Иcточник: msdn.microsoft.com  •  Опубликована: 18.03.2015
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:  


Оценить статью:
Вверх
Комментарии посетителей RSS

Чтобы оставить комментарий, зарегистрируйтесь или войдите с учетной записью социальной сети.