Jump to content

SFML 2D VB.Net 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.

VB.Net 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. Expand the generated Form1 a little bit and then double click it to open up the form code. Replace it with this startup code.

Program.cs


Imports SFML.Graphics
Imports SFML.Window
Public Class Form1
    Dim WithEvents _window As RenderWindow

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        StartSFMLProgram()
    End Sub

    Private Sub StartSFMLProgram()
        _window = New RenderWindow(New VideoMode(640, 480), "2D Spotlight Tutorial", Styles.Default)
        _window.SetVisible(True)

        While _window.IsOpen
            _window.DispatchEvents()
            _window.Clear(Color.White)
            _window.Display()
        End While
    End Sub

    Private Sub OnClosed(sender As Object, e As EventArgs) Handles _window.Closed
        _window.Close()
        Application.Exit()
    End Sub
End Class

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.
        Dim charTex As New Texture("Character.png")
        Dim mapTex As New Texture("Map.png")
        Dim charSprite As New Sprite(charTex)
        charSprite.Position = New SFML.System.Vector2f(290, 150)
        Dim mapSprite As 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:

        Dim mapSprite As New Sprite(mapTex)

Add:

        'Create a RenderTexture for our Dark/Night overlay
        Dim darkTex As New RenderTexture(640, 480)
        Dim darkSprite As 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.
        Dim lightTex As New Texture("Light.png")
        Dim lightSprite As 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:

Imports SFML.Graphics
Imports SFML.Window
Public Class Form1
    Dim WithEvents _window As RenderWindow

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        StartSFMLProgram()
    End Sub

    Private Sub StartSFMLProgram()
        'Load our map and character textures. Attach then to drawable sprite objects.
        Dim charTex As New Texture("Character.png")
        Dim mapTex As New Texture("Map.png")
        Dim charSprite As New Sprite(charTex)
        charSprite.Position = New SFML.System.Vector2f(290, 150)
        Dim mapSprite As New Sprite(mapTex)

        'Create a RenderTexture for our Dark/Night overlay
        Dim darkTex As New RenderTexture(640, 480)
        Dim darkSprite As New Sprite(darkTex.Texture)
        darkTex.Clear(New Color(0, 0, 0, 200))
        'Create the light texture to multiply over the dark texture.
        Dim lightTex As New Texture("Light.png")
        Dim lightSprite As 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)

        While _window.IsOpen
            _window.DispatchEvents()
            _window.Clear(Color.White)
            _window.Draw(mapSprite)
            _window.Draw(charSprite)
            _window.Draw(darkSprite)
            _window.Display()
        End While
    End Sub

    Private Sub OnClosed(sender As Object, e As EventArgs) Handles _window.Closed
        _window.Close()
        Application.Exit()
    End Sub
End Class

Link to comment
Share on other sites

Sub DrawNight()
        'Create a RenderTexture for our Dark/Night overlay
        NightGfx.Clear(New SFML.Graphics.Color(0, 0, 0, 200))

        For X = TileView.left To TileView.right
            For Y = TileView.top To TileView.bottom

                If IsValidMapPoint(X, Y) Then
                    If Map.Tile(X, Y).Type = TILE_TYPE_LIGHT Then
                        X = ConvertMapX(X * PIC_X) - 135
                        Y = ConvertMapY(Y * PIC_Y) - 20
                        'Create the light texture to multiply over the dark texture.
                        Dim lightSprite As New Sprite(lightGfx)
                        lightSprite.Position = New SFML.Window.Vector2f(X, Y)
                        NightGfx.Draw(lightSprite, New RenderStates(BlendMode.Multiply))
                    End If
                End If

            Next
        Next

        GameWindow.Draw(NightSprite)
    End Sub

this draws on a tile_type_light, but only 1 at a time, how can i make it so it draws more???

Link to comment
Share on other sites

nice tut JC, this is what i got :P

-snip-

any way to reduce it to increase FPS?

    [*]Make sure you only create the NightTex at start. Do not create it everyframe, just call clear and redraw it.

    [*]Only update the night tex if something has changed/Player has moved

    [*]This is complex. Use an off screen texture to store all the map lights when the map loads then every frame render that texture to an empty texture, only draw the player light, and render that. (Less multiplicative drawing.)

    [*]Accept that lighting is extremely resource hungry and that you should target other areas to optimize.

    this draws on a tile_type_light, but only 1 at a time, how can i make it so it draws more???

    
    
    Sub DrawNight()
    '- snip -
            NightGfx.Display()
            GameWindow.Draw(NightSprite)
        End Sub

    All you need to do (from what I can tell) is call the display function on the Night Texture after you draw all the lights.

Link to comment
Share on other sites

Sub DrawNight()
        'Create a RenderTexture for our Dark/Night overlay
        NightGfx.Clear(New SFML.Graphics.Color(0, 0, 0, 200))

        For X = TileView.left To TileView.right
            For Y = TileView.top To TileView.bottom

                If IsValidMapPoint(X, Y) Then
                    If Map.Tile(X, Y).Type = TILE_TYPE_LIGHT Then
                        X = ConvertMapX(X * PIC_X) - 135
                        Y = ConvertMapY(Y * PIC_Y) - 20

                        'Create the light texture to multiply over the dark texture.
                        Dim lightSprite As New Sprite(lightGfx)
                        lightSprite.Position = New SFML.Window.Vector2f(X, Y)
                        NightGfx.Draw(lightSprite, New RenderStates(BlendMode.Multiply))
                    End If
                End If

            Next
        Next
        NightGfx.Display()
        GameWindow.Draw(NightSprite)
    End Sub

still nothing :(

Link to comment
Share on other sites

We figured it out.  The problem was that he was using the X and Y variables for his loops and reusing them to calculate coordinates.  Here is the fixed code...

Sub DrawNight()
        'Create a RenderTexture for our Dark/Night overlay
        NightGfx.Clear(New SFML.Graphics.Color(0, 0, 0, 200))

        For X = TileView.left To TileView.right
            For Y = TileView.top To TileView.bottom

                If IsValidMapPoint(X, Y) Then
                    If Map.Tile(X, Y).Type = TILE_TYPE_LIGHT Then
                        dim X1 = ConvertMapX(X * PIC_X) - 135
                        dim Y1 = ConvertMapY(Y * PIC_Y) - 20

                        'Create the light texture to multiply over the dark texture.
                        Dim lightSprite As New Sprite(lightGfx)
                        lightSprite.Position = New SFML.Window.Vector2f(X1, Y1)
                        NightGfx.Draw(lightSprite, New RenderStates(BlendMode.Multiply))
                    End If
                End If

            Next
        Next
        NightGfx.Display()
        GameWindow.Draw(NightSprite)
    End Sub

Link to comment
Share on other sites

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...