Dynamic lighting
From VbGORE Visual Basic Online RPG Engine
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.
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:
<vb> 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 </vb>
In frmMain's code, find:
<vb> '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
</vb>
After, add:
<vb> LightX = MousePos.X </vb>
Find:
<vb> 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
</vb>
After, add:
<vb> LightY = MousePos.Y </vb>
In module "TileEngine", find:
<vb> '************** Update weather ************** </vb>
Before, add:
<vb> '************** Dynamic lighting ************
UpdateLight
</vb>
Find:
<vb> ReDim MapData(1 To MapInfo.Width, 1 To MapInfo.Height) As MapBlock </vb>
After, add:
<vb> ReDim MapLight(1 To MapInfo.Width, 1 To MapInfo.Height) As Long </vb>
Find:
<vb> '****** Update screen ****** </vb>
After, add:
<vb> '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
</vb>
Find:
<vb>
'Cache the TileBufferOffset value to prevent always having to calculate it on the fly TileBufferOffset = ((10 - TileBufferSize) * 32)
</vb>
After, add:
<vb>
'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)
</vb>
Last thing would be to change some declarations. Find:
<vb> Public Type MapBlock </vb>
In that type, find:
<vb>
Light(1 To 24) As Long
</vb>
After, add:
<vb>
DefaultLight(1 To 24) As Long
</vb>
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.