📜 ⬆️ ⬇️

We make simple lighting in a 2D game. Detailed C # and XNA examples for beginners

In this publication I will try to very simply tell you how you can quickly and easily make dynamic lighting in a 2D game on XNA; without shaders, normal maps and in general, without additional resources. Our goal is to have a beautiful game scene with a night sky, a dark background, illuminated by lanterns, a front background and a game menu. The number of light sources is not limited (within reasonable limits, of course), the shape is arbitrary. Only the front background is lit. Readers should have basic XNA skills, since there will be a lot of code.

2d lighting

A few words as an introduction. I rummaged through a mountain of articles on how to make dynamic lighting in a 2D game, and it was a revelation to me that despite the abundance of material, most of them are simply useless, because I just can not repeat in the code what they write about. There are articles with examples of a few lines of pseudocode code. For a beginner, they are almost useless. There are working examples with shaders that can even be downloaded and run. Mountains of code, 2 light sources and not a word about how to add more. And the very theme of shaders for a simple 2D game is an obvious brute force. This is how the idea of ​​this article was born: to allow the reader to add decent dynamic lighting to his game in 30 minutes, thereby saving a lot of time and nerves.

So let's go. First, a little theory and simple examples. Draw a few squares on a gray background:
')
Squares on a gray background

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(); 

Here we fill the screen with black. Then we draw a gray rectangle box1, and on top of 3 squares: box2, box3 and box4. Now let's try to slightly change the parameters of the spriteBatch.Begin () method, namely:

 spriteBatch.Begin(SpriteSortMode.BackToFront, blendState); 

We declare the blendState variable as follows:

 var blendState = BlendState.Additive; 

Or so. What is the same:

 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; 

You get this result:

Squares on a gray background

In fact, we declare that when drawing box2 on top of box1, one should not overlap the pixels, but mix them in color. The mixing itself is carried out according to the formula:


In other words, if you have 2 pixels with the same coordinates and colors: R: 10 G: 20 B: 255 and R: 1 G: 2 B: 255, then the resulting pixel will come out with the color R: 11 G: 22 B: 255 i.e. will become lighter. From this we can conclude that by playing with the settings of the BlendState class, you can shade and light up individual areas of the sprites, thereby obtaining the desired 2D lighting effect. To light up the round area on the sprite, you need to draw a round dark gray sprite on top. To make the round area darker, on the contrary, you need to draw a round dark gray sprite again, but use the BlendFunction.ReverseSubtract function.

At the theory of everything, go to practice. To get a beautiful scene, as in the first picture, we will do the following:

  1. Cooking sprite with a background. To do this, draw the entire background on one sprite (RenderTarget2D);
  2. Cooking sprite with foreground;
  3. Cooking sprite with shadows. To do this, take the sprite from the second step and at the points of the light sources draw black round sprites - these are areas that will not be obscured.


sprite with shadows

Now all this is displayed on the screen:

  1. We draw the night sky;
  2. Draw a sprite with a background;
  3. Once again draw a sprite with the background, but with a coefficient of -0.9 (see code). We get a dark background;
  4. Draw the foreground;
  5. Draw a sprite with shadows, -0.9 coefficient;
  6. Draw a menu.

Result:

2d lighting

And, actually ready code:

 // 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(); 

I took the drawings from the game “Craft the World”, although I myself have nothing to do with the game. An attentive reader may notice strange artifacts on the edges of the building and trees - this is all because I rather casually cut the screenshot in Photoshop. The lighting technology itself has almost no effect on the display speed, so the overall performance of the game should not suffer. The finished project for Visual Studio 2010 can be downloaded here: xnagames.codeplex.com/releases/view/136161

I hope this article will be useful for beginners igrodelam. Good luck!

Source: https://habr.com/ru/post/241760/


All Articles