Dynamic lighting

From VbGORE Visual Basic Online RPG Engine

[edit] Introduction

This tutorial will teach you how to add dynamic circular lights. These lights are calculated by a few variables, such as the size and intensity of the light, and modify the map's light values directly to present the light, allowing for "moving" lights such as a lantern for characters, or a fireball that emits a light.

300px

[edit] Adding the code

First create a new module in GameClient.vbp by clicking "Project->Add Module->OK" in the Menu. Name the module "Lighting". Add the following code to the module:

Option Explicit
 
Public LightX As Long
Public LightY As Long
Public Const LightSize As Single = 320
Public Const LightStrength As Single = 1
 
Public LightAddX As Long
Public LightAddY As Long
 
Public MapLight() As Long
 
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
 
Private Function CalcLight(ByVal X As Long, ByVal Y As Long, ByVal Light As Long) As Long
Dim ARGB(3) As Byte
Dim Dist As Long
Dim i As Long
Dim k As Long
Dim a As Long
 
    'Find the distance of the verticy and light
    Dist = Distance(X, Y, LightX, LightY)
 
    'Confirm the distance is valid
    If LightSize > Dist Then
 
        'Find the ARGB value of the light
        CopyMemory ARGB(0), Light, 4
 
        'Calculate the modifier value to the light once instead of for each color
        a = ((LightSize - Dist) * LightStrength)
 
        'Update the RGB values
        For i = 0 To 2
 
            'Calculate the new light value
            k = CLng(ARGB(i)) + a
            If k > 255 Then k = 255
            If k < 0 Then k = 0
 
            'Put the variable back now that we know it is in byte range
            ARGB(i) = k
 
        Next i
 
        'Store the new light value
        CalcLight = D3DColorARGB(ARGB(3), ARGB(2), ARGB(1), ARGB(0))
 
    Else
 
        'Return the original value since the light is too far away from the location to affect it
        CalcLight = Light
 
    End If
 
End Function
 
Public Sub UpdateLight()
Dim X As Long
Dim Y As Long
Dim i As Long
Dim R As Long
 
    '*** Tiles ***
 
    'Calculate each visual verticy once (to prevent duplicate calculations)
    For X = 1 To MapInfo.Width
        For Y = 1 To MapInfo.Height
            If X = MapInfo.Width And Y = MapInfo.Height Then
                R = MapData(X - 1, Y - 1).DefaultLight(4)
            ElseIf X = MapInfo.Width Then
                R = MapData(X - 1, Y).DefaultLight(2)
            ElseIf Y = MapInfo.Height Then
                R = MapData(X, Y - 1).DefaultLight(3)
            Else
                R = MapData(X, Y).DefaultLight(1)
            End If
            MapLight(X, Y) = CalcLight((X - LightAddX) * 32, (Y - LightAddY) * 32, R)
        Next Y
    Next X
 
    'Apply the lights to the tiles
    For X = 1 To MapInfo.Width - 1
        For Y = 1 To MapInfo.Height - 1
            For i = 1 To 24
                With MapData(X, Y)
                    Select Case i
                        Case 1: .Light(i) = MapLight(X, Y)
                        Case 2: .Light(i) = MapLight(X + 1, Y)
                        Case 3: .Light(i) = MapLight(X, Y + 1)
                        Case 4: .Light(i) = MapLight(X + 1, Y + 1)
                        Case 5: .Light(i) = MapLight(X, Y)
                        Case 6: .Light(i) = MapLight(X + 1, Y)
                        Case 7: .Light(i) = MapLight(X, Y + 1)
                        Case 8: .Light(i) = MapLight(X + 1, Y + 1)
                        Case 9: .Light(i) = MapLight(X, Y)
                        Case 10: .Light(i) = MapLight(X + 1, Y)
                        Case 11: .Light(i) = MapLight(X, Y + 1)
                        Case 12: .Light(i) = MapLight(X + 1, Y + 1)
                        Case 13: .Light(i) = MapLight(X, Y)
                        Case 14: .Light(i) = MapLight(X + 1, Y)
                        Case 15: .Light(i) = MapLight(X, Y + 1)
                        Case 16: .Light(i) = MapLight(X + 1, Y + 1)
                        Case 17: .Light(i) = MapLight(X, Y)
                        Case 18: .Light(i) = MapLight(X + 1, Y)
                        Case 19: .Light(i) = MapLight(X, Y + 1)
                        Case 20: .Light(i) = MapLight(X + 1, Y + 1)
                        Case 21: .Light(i) = MapLight(X, Y)
                        Case 22: .Light(i) = MapLight(X + 1, Y)
                        Case 23: .Light(i) = MapLight(X, Y + 1)
                        Case 24: .Light(i) = MapLight(X + 1, Y + 1)
                        Case Else: .Light(i) = MapLight(X, Y)
                    End Select
                End With
            Next i
        Next Y
    Next X
 
End Sub
 
Public Sub SetAmbientLight(ByVal Color As Long)
Dim X As Long
Dim Y As Long
Dim i As Long
 
    On Error GoTo 0
 
    'Set the default light
    For X = 1 To MapInfo.Width
        For Y = 1 To MapInfo.Height
            With MapData(X, Y)
                For i = 1 To 24
                    .DefaultLight(i) = Color
                Next i
            End With
        Next Y
    Next X
 
End Sub
 
Private Function Distance(ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Single
 
    Distance = Sqr(((X2 - X1) * (X2 - X1)) + ((Y2 - Y1) * (Y2 - Y1)))
 
End Function

In frmMain's code, find:

'Move on X axis
        Case DIMOFS_X
            If Windowed Then
                OldMousePos = MousePos
                GetCursorPos MousePos
                MousePos.X = MousePos.X - (Me.Left \ Screen.TwipsPerPixelX)
                MousePos.Y = MousePos.Y - (Me.Top \ Screen.TwipsPerPixelY)
                MousePosAdd.X = -(OldMousePos.X - MousePos.X)
                MousePosAdd.Y = -(OldMousePos.Y - MousePos.Y)
            Else
                MousePosAdd.X = (DevData(LoopC).lData * MouseSpeed)
                MousePos.X = MousePos.X + MousePosAdd.X
                If MousePos.X < 0 Then MousePos.X = 0
                If MousePos.X > frmMain.ScaleWidth Then MousePos.X = frmMain.ScaleWidth
            End If
            Moved = 1

After, add:

LightX = MousePos.X

Find:

MousePosAdd.Y = (DevData(LoopC).lData * MouseSpeed)
                MousePos.Y = MousePos.Y + MousePosAdd.Y
                If MousePos.Y < 0 Then MousePos.Y = 0
                If MousePos.Y > ScreenHeight Then MousePos.Y = ScreenHeight
            End If
            Moved = 1

After, add:

LightY = MousePos.Y

In module "TileEngine", find:

'************** Update weather **************

Before, add:

'************** Dynamic lighting ************
 
    UpdateLight

Find:

ReDim MapData(1 To MapInfo.Width, 1 To MapInfo.Height) As MapBlock

After, add:

ReDim MapLight(1 To MapInfo.Width, 1 To MapInfo.Height) As Long

Find:

'****** Update screen ******

After, add:

'Fixes problem with Lightcircle/Mousepos
      If UserPos.X > 14 Then
        LightAddX = UserPos.X - 14
      Else
        LightAddX = 0
      End If
 
      If UserPos.Y > 10 Then
        LightAddY = UserPos.Y - 10
      Else
        LightAddY = 0
      End If

Find:

    'Cache the TileBufferOffset value to prevent always having to calculate it on the fly
    TileBufferOffset = ((10 - TileBufferSize) * 32)

After, add:

    'Set the ambient light - you will probably want to set this in the map's file instead of directly in the code
    SetAmbientLight D3DColorARGB(255, 50, 50, 50)

Last thing would be to change some declarations. Find:

Public Type MapBlock

In that type, find:

    Light(1 To 24) As Long

After, add:

    DefaultLight(1 To 24) As Long

[edit] To-do

The following are additions that would be highly beneficial to this custom feature:

  • Smarter updating: Only update the dynamic lights when they change, not every frame.
  • Map-based ambient: The ambient value is saved in the map file, not set from a constant in the code.
  • Map editor ambient support: The ambient light shown in the map editor, allowing mappers to see what the lighting looks like while developing.
  • Map Specific: The lightening would not be shown on specific maps.
Personal tools