Jump to content

SFML 2D C# Spotlight Tutorial


jcsnider

Recommended Posts

This has been requested wayyyy to many times so I am finally caving in. Here is a really simple tutorial for making basic spotlights in C# and VB.Net with SFML. If you are using another rendering library this may still help.

C# Guide

Step 1

Start with a blank WinForms project, add references to the SFML .dlls and place the csfml dlls into the output folder of the application. Here is my starting code for reference.

Program.cs

using SFML.Graphics;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LightingTutSharp
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [sTAThread]
        static void Main()
        {
            MySFMLProgram app = new MySFMLProgram();
            app.StartSFMLProgram();
        }
    }
    class MySFMLProgram
    {
        RenderWindow _window;
        public void StartSFMLProgram()
        {
            _window = new RenderWindow(new VideoMode(640, 480), "2D Spotlight Tutorial",Styles.Default);
            _window.SetVisible(true);
            _window.Closed += new EventHandler(OnClosed);

            while (_window.IsOpen)
            {
                _window.DispatchEvents();
                _window.Clear(Color.White);
                _window.Display();
            }
        }
        void OnClosed(object sender, EventArgs e)
        {
            _window.Close();
        }
    }
}

Step 2

Lets go ahead and add some graphics.

I am going to be using these two...

Save both of these images to your projects build folder!

RNG1xxJ18ZwVxBIcxK.png

Right Click and Save As > "Character.png"

and

LykoP72lvmgT1XN3t2.png

Right Click and Save As > "Map.png"

Step 3

Now, lets add the code to load these graphics.

Before:

            _window = new RenderWindow(new VideoMode(640, 480), "2D Spotlight Tutorial",Styles.Default);

Add:

            //Load our map and character textures. Attach then to drawable sprite objects.
            Texture charTex = new Texture("Character.png");
            Texture mapTex = new Texture("Map.png");
            Sprite charSprite = new Sprite(charTex);
            charSprite.Position = new SFML.System.Vector2f(290, 150);
            Sprite mapSprite = new Sprite(mapTex);

Step 4

The next step is to render our graphics to the screen.

Before:

_window.Display();

Add:

                _window.Draw(mapSprite);
                _window.Draw(charSprite);

If you run your program you should see a window like this.

EtKHSZ0bYyzlMumive.png

So, how do we darken the window?

Well, lets take a step back and thing about how we would do it outside of code.

Making the map dark is easy. We can take a layer of black, make it semi transparent, and then throw it on top of the image like so.

This is how you could do it using GIMP.

56u7GvEL38q5U6Wjir.gif

Step 5

Lets do it via code!

Lights often change, so we have to generate the dark texture and the lights at runtime. We can do this with a RenderTexture.

After:

            Sprite mapSprite = new Sprite(mapTex);

Add:

            //Create a RenderTexture for our Dark/Night overlay
            RenderTexture darkTex = new RenderTexture(640,480);
            Sprite darkSprite = new Sprite(darkTex.Texture);
            darkTex.Clear(new Color(0,0,0,200));
            darkTex.Display();

and

After:

                _window.Draw(charSprite);

Add:

                _window.Draw(darkSprite);

Compile again, your window should look like this now:

9tnZvSkPDgfIGxalaf.png

So, how do we add light!?

We cannot render our dark texture and then render something light on top of it, or else we would get a terrible effect that looks like this.

RwVzgOngNoMRxTvEZo.png

So, we have to cut-out piece of the dark texture before it is drawn to the game screen. This is done by multiplicative blending.  Give me a moment to try to explain myself.

Let's put this into numbers.

I am telling you:

Transparency l9CPmF5w3liCN1Olh1.png  ==  0

and

White 3YANOAhjBpH0e1oRZE.png == 1

Bear with me for a moment while I explain. Remember the rules you were taught in elementary school?

Any Number * 1 = Itself.

Any Number * 0 = 0

We are going to use those same rules with our graphics. With that same logic, I am telling you that

Transparency l9CPmF5w3liCN1Olh1.png  *  Black V3YLIwCyOMAKdWtgSQ.png == Transparency l9CPmF5w3liCN1Olh1.png

White 3YANOAhjBpH0e1oRZE.png  *  Black V3YLIwCyOMAKdWtgSQ.png == Black V3YLIwCyOMAKdWtgSQ.png

Do you see the trick now? If not it's fine but we are going to put this concept to work.

Lets say we had this image loaded as a texture....

gPqFCnqilj7CJhYx93.png

and then we multiplied that somewhere over our darkness texture

EZm5FNvDX8jH0rZNz2.png

What would happen? The center of our light graphic is transparent, so multiplying would remove all color from our dark texture, while the white on the edges would multiply leaving black with a smooth transition in the middle. Something like the graphic below:

ZMSz4ZgNnogkgejGYM.png

Want to see it in action? Let's try it!

Step 6

We need to add our new light texture!

jaATKyGhyUiGdvhWOm.png

Right Click and Save this Image as > "Light.png"

Add it to your projects build folder.

Then in your code.

After:

            darkTex.Clear(new Color(0,0,0,200));

Add:

//Create the light texture to multiply over the dark texture.
            Texture lightTex = new Texture("Light.png");
            Sprite lightSprite = new Sprite(lightTex);
            lightSprite.Position = new SFML.System.Vector2f(156, 24);
            //We are putting the light onto the drawTex by multiplying. Not drawing to the game window.
            darkTex.Draw(lightSprite, new RenderStates(BlendMode.Multiply));

Now if you run the application you will see a nice spotlight effect!

Final Product:

rWBItCRTFtvxxQHJdJ.png

Here is the full code too!

using SFML.Graphics;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LightingTutSharp
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [sTAThread]
        static void Main()
        {
            MySFMLProgram app = new MySFMLProgram();
            app.StartSFMLProgram();
        }
    }
    class MySFMLProgram
    {
        RenderWindow _window;
        public void StartSFMLProgram()
        {
            //Load our map and character textures. Attach then to drawable sprite objects.
            Texture charTex = new Texture("Character.png");
            Texture mapTex = new Texture("Map.png");
            Sprite charSprite = new Sprite(charTex);
            charSprite.Position = new SFML.System.Vector2f(290, 150);
            Sprite mapSprite = new Sprite(mapTex);

            //Create a RenderTexture for our Dark/Night overlay
            RenderTexture darkTex = new RenderTexture(640,480);
            Sprite darkSprite = new Sprite(darkTex.Texture);
            darkTex.Clear(new Color(0,0,0,200));
            darkTex.Display();

            //Create the light texture to multiply over the dark texture.
            Texture lightTex = new Texture("Light.png");
            Sprite lightSprite = new Sprite(lightTex);
            lightSprite.Position = new SFML.System.Vector2f(156, 24);
            //We are putting the light onto the drawTex by multiplying. Not drawing to the game window.
            darkTex.Draw(lightSprite, new RenderStates(BlendMode.Multiply));
            darkTex.Display();
            

            _window = new RenderWindow(new VideoMode(640, 480), "2D Spotlight Tutorial",Styles.Default);
            _window.SetVisible(true);
            _window.Closed += new EventHandler(OnClosed);

            while (_window.IsOpen)
            {
                _window.DispatchEvents();
                _window.Clear(Color.White);
                _window.Draw(mapSprite);
                _window.Draw(charSprite);
                _window.Draw(darkSprite);
                _window.Display();
            }
        }
        void OnClosed(object sender, EventArgs e)
        {
            _window.Close();
        }
    }
}

Link to comment
Share on other sites

  • 5 months later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...