Friday
Jan302009
Simple 2D Shadows in XNA
Friday, January 30, 2009 at 7:49AM
In this example, I'll show you how to add 2D shadows to your game with no new lines of code, no additional textures and no math. It turns out everything you need is already contained in Spritbatch.Draw.
No Shadow Shadow
I was getting tired of trying to learn ZBrush, so I went back to programming my little game engine. In the motto of why recreate the wheel, I started where I always do... at Google. I quickly found two interesting takes on 2D shadow creation. The first is recently posted source code found here from Catalin Zima, a Tech student in Romania. I also found it when it showed up as a link at Ziggyware. If you don't have Ziggyware added to your RSS feeds, you should do that NOW!. Some of the best educational tutorials have come out of that site. They had a contest in 2008 and the articles were mindblowing. Here is the link to the winners for 2008.
All I can say is WOW! Catalin's example is amazing! Unfortunately, I'm too early in my development to understand it. It's not a tutorial, mind you, just source code. To be honest when I ran the program, I wasn't entirely sure what I was looking at. That's due to my limited skills, not Catalin's work. But the bottom line is... it's over my head. I could spend a few weeks trying to understand the code and implement it or I could keep searching.
My next hit pulled up this. Very simple. Just draw a shadow copy of your existing texture and place it in your code. The problem I had with that is it means duplicating every texture where you want a shadow. I already have capacity issues with my little game so adding shadow textures seemed extreme. However this example gave me an idea.
What is a shadow really? Yes you can find wikis with large brain shadow math/algorithms. But I needed to keep things simple so I could understand it. All a shadow really is is a copy of your existing game texture, darkened, partially transparent and offset according to sun position. Well guess what? Spritebatch.Draw controls all of that.
Player behind tree on top of tree shadow.
I'll demonstrate with two examples. A Tree and my animated Player character. Remember.... no new lines of code, no new textures and no math.
In simplest form, all you have to do is make a copy of your spritebatch.draw statement and paste it underneath the one you are using to draw your tree.
I'm using overload #5 of the spritbatch.draw command. An overload is just a variation or form. Spritebatch.draw allows 7 different forms or variations. The overload you choose determines the variables you need to pass to spritebatch.draw within the parenthesis. So with overload #5, I have to pass the following. SpriteBatch.Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) This contains everything I need to make a shadow of my existing texture. So here are my two commands. I haven't added a "New" line of code. All I've done is copied an exisitng line (spriteBatch.Draw) and then changed some of the variables. I'm drawing the same tree twice but altering it in the second draw.
TreeTopRect = new Rectangle(500, 500, treeTop.Width, treeTop.Height);
This sets up the rectangle I'm going to place my treeTop texture in. I place my texture in a rectangle in the first draw statement. Then I use a different overload in the second draw statement, overload #6. I use new Vector2(x, y) in overload #6 to offset the position of the rectangle that contains my treeTop texture. I also change the color which includes transparency. And I give it a little bit of rotation by setting rotation to .2f. Also note that I change the layer depth so the tree shadow is always under the tree. (You turn layer depth on on in your spriteBatch.Begin command by declaring SpriteSortMode.BackToFront). This tells spritBatch.Draw to pay attention to the float values you set in layer depth. If you don't use this feature, the order of sprites on the screen will be the order that you draw them. Back being first and front being last. My personal preference is to always have control of this feature as much as possible so I always turn it on.
spriteBatch.Draw(treeTop, TreeTopRect, null, Color.White,
0f, new Vector2(0f, 0f), SpriteEffects.None, 0.2f);
spriteBatch.Draw(treeTop, new Vector2(TreeTopRect.X + 70,
TreeTopRect.Y - 20), null, new Color(0, 0, 0, 70), .2f,
new Vector2(0, 0), 1f, SpriteEffects.None, 0.5f);
All I have done in my second spriteBatch.Draw is redraw the same texture...treeTop, offset the X and Y coordinates of it's position and make the source rectangle null because I don't use that. Instead of saying Color.White, I use one of the overloads in Color. Amazingly, there are 9 overloads for Color. I use #8 which uses bytes to represent R, G, B, A. New Color(byte r, byte g, byte b, byte a). That's Red, Green, Blue and Alpha. A byte is 0 - 255. Zero is black, 255 is white. So when you are saying Color.White, it's the same as if you said New Color(255, 255, 255, 255). The Alpha is transparency which you can set on the fly for your texture. And here is the key.... If you make r, g and b all equal Zero, you get black. Set your Alpha to 70. Thus you are saying redraw my texture in a different position, make it black and make it transparent down to 70 instead of fully opaque at 255. I also change the rotation float variable to .2f in order to rotate my shadow texture just a little relative to the sun's position. And finally I set the layer depth so the shadow is always farther away from the camera or your face than the tree. A layer depth of 1f is as far away as you can get and 0f is as close to you as you can get. This allows you to position 2D sprites on the screen in an infinite number of layers, one on top of another.
Now to set this up, you would have already needed to declare
private Texture2D treeTop; at the top of your code in the main class.
Then you had to load treeTop with a texture in your Load Content method.
treeTop = content.Load<Texture2D>(@"Tiles/canopy145");
You'll need your spriteBatch.Draw statements sandwiched in between spritebatch.begin and spritebatch.end in your main draw method as usual.
spriteBatch.Begin(SpriteBlendMode.AlphaBlend,SpriteSortMode.BackToFront, SaveStateMode.None, camera.Transform);
spriteBatch.Draw.....
spriteBatch.Draw....
spriteBatch.End();
I use a 2dCamera in my little game thus the camera.Transform but you don't need this for this example.
Now if you want to extend this, you could make color a variable and make your shadow x, y positions variables. Then you could adjust your shadow position and transparency based upon wherever you want your sun. If you want to go crazy, you could have a day night cycle and alter your shadow positions and transparency based upon the time of day. That would be cool in an RPG.
In order to make color a variable, one way I have found that you can do it is to make color a Vector4.
float alpha = .2f;
color = new Color(new Vector4(0, 0, 0, alpha));
spriteBatch.Draw(treeTop, new Vector2(TreeTopRect.X + 70, TreeTopRect.Y - 20), null, color, .2f, new Vector2(0, 0),
1f, SpriteEffects.None, 0.5f);
Notice how I use the variable color in place of Color.White or New Color() in the second draw statement. The only thing to keep in mind is when you make color a Vector4, you're now using floats instead of bytes. So color is from 0 - 1.
You can apply the exact same logic to a player character. The added benefit is your X, Y offsets for your shadows all stay the same for every direction you draw your character, thus your shadows appear consistant as you turn your character. Here is a video of my character running and circling. I increased the alpha to 80 so the video would show the shadow better. I offset the player rectangle X +10 and Y - 5 on every player animation. So all player animations have the same offset. This places my Sun somewhere in the Southwestern sky. It's easier to see the shadows if you watch the video in HD.
No Shadow Shadow


I was getting tired of trying to learn ZBrush, so I went back to programming my little game engine. In the motto of why recreate the wheel, I started where I always do... at Google. I quickly found two interesting takes on 2D shadow creation. The first is recently posted source code found here from Catalin Zima, a Tech student in Romania. I also found it when it showed up as a link at Ziggyware. If you don't have Ziggyware added to your RSS feeds, you should do that NOW!. Some of the best educational tutorials have come out of that site. They had a contest in 2008 and the articles were mindblowing. Here is the link to the winners for 2008.
All I can say is WOW! Catalin's example is amazing! Unfortunately, I'm too early in my development to understand it. It's not a tutorial, mind you, just source code. To be honest when I ran the program, I wasn't entirely sure what I was looking at. That's due to my limited skills, not Catalin's work. But the bottom line is... it's over my head. I could spend a few weeks trying to understand the code and implement it or I could keep searching.
My next hit pulled up this. Very simple. Just draw a shadow copy of your existing texture and place it in your code. The problem I had with that is it means duplicating every texture where you want a shadow. I already have capacity issues with my little game so adding shadow textures seemed extreme. However this example gave me an idea.
What is a shadow really? Yes you can find wikis with large brain shadow math/algorithms. But I needed to keep things simple so I could understand it. All a shadow really is is a copy of your existing game texture, darkened, partially transparent and offset according to sun position. Well guess what? Spritebatch.Draw controls all of that.
Player behind tree on top of tree shadow.
I'll demonstrate with two examples. A Tree and my animated Player character. Remember.... no new lines of code, no new textures and no math.In simplest form, all you have to do is make a copy of your spritebatch.draw statement and paste it underneath the one you are using to draw your tree.
I'm using overload #5 of the spritbatch.draw command. An overload is just a variation or form. Spritebatch.draw allows 7 different forms or variations. The overload you choose determines the variables you need to pass to spritebatch.draw within the parenthesis. So with overload #5, I have to pass the following. SpriteBatch.Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) This contains everything I need to make a shadow of my existing texture. So here are my two commands. I haven't added a "New" line of code. All I've done is copied an exisitng line (spriteBatch.Draw) and then changed some of the variables. I'm drawing the same tree twice but altering it in the second draw.
TreeTopRect = new Rectangle(500, 500, treeTop.Width, treeTop.Height);
This sets up the rectangle I'm going to place my treeTop texture in. I place my texture in a rectangle in the first draw statement. Then I use a different overload in the second draw statement, overload #6. I use new Vector2(x, y) in overload #6 to offset the position of the rectangle that contains my treeTop texture. I also change the color which includes transparency. And I give it a little bit of rotation by setting rotation to .2f. Also note that I change the layer depth so the tree shadow is always under the tree. (You turn layer depth on on in your spriteBatch.Begin command by declaring SpriteSortMode.BackToFront). This tells spritBatch.Draw to pay attention to the float values you set in layer depth. If you don't use this feature, the order of sprites on the screen will be the order that you draw them. Back being first and front being last. My personal preference is to always have control of this feature as much as possible so I always turn it on.
spriteBatch.Draw(treeTop, TreeTopRect, null, Color.White,
0f, new Vector2(0f, 0f), SpriteEffects.None, 0.2f);
spriteBatch.Draw(treeTop, new Vector2(TreeTopRect.X + 70,
TreeTopRect.Y - 20), null, new Color(0, 0, 0, 70), .2f,
new Vector2(0, 0), 1f, SpriteEffects.None, 0.5f);
All I have done in my second spriteBatch.Draw is redraw the same texture...treeTop, offset the X and Y coordinates of it's position and make the source rectangle null because I don't use that. Instead of saying Color.White, I use one of the overloads in Color. Amazingly, there are 9 overloads for Color. I use #8 which uses bytes to represent R, G, B, A. New Color(byte r, byte g, byte b, byte a). That's Red, Green, Blue and Alpha. A byte is 0 - 255. Zero is black, 255 is white. So when you are saying Color.White, it's the same as if you said New Color(255, 255, 255, 255). The Alpha is transparency which you can set on the fly for your texture. And here is the key.... If you make r, g and b all equal Zero, you get black. Set your Alpha to 70. Thus you are saying redraw my texture in a different position, make it black and make it transparent down to 70 instead of fully opaque at 255. I also change the rotation float variable to .2f in order to rotate my shadow texture just a little relative to the sun's position. And finally I set the layer depth so the shadow is always farther away from the camera or your face than the tree. A layer depth of 1f is as far away as you can get and 0f is as close to you as you can get. This allows you to position 2D sprites on the screen in an infinite number of layers, one on top of another.
Now to set this up, you would have already needed to declare
private Texture2D treeTop; at the top of your code in the main class.
Then you had to load treeTop with a texture in your Load Content method.
treeTop = content.Load<Texture2D>(@"Tiles/canopy145");
You'll need your spriteBatch.Draw statements sandwiched in between spritebatch.begin and spritebatch.end in your main draw method as usual.
spriteBatch.Begin(SpriteBlendMode.AlphaBlend,SpriteSortMode.BackToFront, SaveStateMode.None, camera.Transform);
spriteBatch.Draw.....
spriteBatch.Draw....
spriteBatch.End();
I use a 2dCamera in my little game thus the camera.Transform but you don't need this for this example.
Now if you want to extend this, you could make color a variable and make your shadow x, y positions variables. Then you could adjust your shadow position and transparency based upon wherever you want your sun. If you want to go crazy, you could have a day night cycle and alter your shadow positions and transparency based upon the time of day. That would be cool in an RPG.
In order to make color a variable, one way I have found that you can do it is to make color a Vector4.
float alpha = .2f;
color = new Color(new Vector4(0, 0, 0, alpha));
spriteBatch.Draw(treeTop, new Vector2(TreeTopRect.X + 70, TreeTopRect.Y - 20), null, color, .2f, new Vector2(0, 0),
1f, SpriteEffects.None, 0.5f);
Notice how I use the variable color in place of Color.White or New Color() in the second draw statement. The only thing to keep in mind is when you make color a Vector4, you're now using floats instead of bytes. So color is from 0 - 1.
You can apply the exact same logic to a player character. The added benefit is your X, Y offsets for your shadows all stay the same for every direction you draw your character, thus your shadows appear consistant as you turn your character. Here is a video of my character running and circling. I increased the alpha to 80 so the video would show the shadow better. I offset the player rectangle X +10 and Y - 5 on every player animation. So all player animations have the same offset. This places my Sun somewhere in the Southwestern sky. It's easier to see the shadows if you watch the video in HD.
Reader Comments (2)
[...] Be sure to take a look at the full article here. [...]
Thanks for the link and the kind words.