CRM64Pro GDK v0.80.0
A free cross-platform game development kit built on top of SDL 3.0
Loading...
Searching...
No Matches
Tutorial 16.6: Scene Lighting

Overview

This tutorial keeps Scene lighting, light occlusion and particles in a small TMX map. It uses animated candle and pulse lights, a shadow-casting spotlight, and two fire effects. The C++ application only loads the Scene and provides independent runtime switches for lighting and particle effects.

Final result

CRM64Pro Tutorial 16.6 Scene Lighting

Runtime controls

  • L: Toggle Scene lighting.
  • E: Toggle Scene fire effects.
  • D: Toggle light, occluder and particle shape overlays.
  • Cursor keys: Scroll the Scene.
  • Q or ESC: Exit the tutorial.

Prerequisites

  • CRM64Pro GDK installed and configured with a supported C++17 compiler.
  • Tutorial package downloaded and fully extracted, preserving its folder structure.
  • Understand TMX object layers from the earlier Scene tutorials.
  • Included assets available under bin/Base/: map-ddraft1.png, lighting.tmx.
  • Keep the Tutorial_bg resource from Tutorial 02 in Tutorial.cdc.

What you will learn

  • Enable ambient Scene lighting from TMX properties.
  • Configure candle and pulse C64Light animation.
  • Configure a rotated spotlight and an occluding polyline.
  • Attach built-in FIRE particle presets to C64Particle objects.
  • Toggle lighting and particles independently with Scene methods.

Walkthrough

1. Enable the two Scene systems

The map enables lighting and particle effects separately. Ambient color and intensity darken the scene before individual lights are added.

<properties>
<property name="c64scn_version" type="int" value="40"/>
<property name="c64scn_lighting_ambient" type="color" value="#182238FF"/>
<property name="c64scn_lighting_ambient_intensity" type="float" value="0.28"/>
<property name="c64scn_lighting_enabled" type="bool" value="true"/>
<property name="c64scn_effects_enabled" type="bool" value="true"/>
</properties>

c64scn_version is required for a native CRM64Pro Scene. Without it, the map loads as generic TMX and its c64scn_* properties, including ambient lighting and the initial lighting/effects states, are ignored.

c64scn_effects_enabled only controls C64Particle objects. Animated lights remain part of the lighting system.

2. Create an animated candle

The object ID supplies deterministic variation. The candle animation changes intensity, radius and color without per-frame application code.

<object id="1" name="Candle" type="C64Light" x="230" y="410">
<properties>
<property name="c64scn_light_range_px" type="float" value="190"/>
<property name="c64scn_light_intensity" type="float" value="1.0"/>
<property name="c64scn_light_color" type="color" value="#FFF0B0FF"/>
<property name="c64scn_light_shadows" type="bool" value="true"/>
<property name="c64scn_light_animation" value="candle"/>
<property name="c64scn_light_animation_rate" type="float" value="8"/>
<property name="c64scn_light_animation_amount" type="float" value="0.32"/>
<property name="c64scn_light_animation_radius_amount" type="float" value="0.12"/>
<property name="c64scn_light_secondary_color" type="color" value="#E85018FF"/>
</properties>
</object>

The second C64Light in the map uses pulse, so you can compare a smooth color transition against irregular candle variation.

3. Cast a spotlight shadow

An angle between 0 and 360 makes a spotlight. Its Tiled rotation gives its direction; the polyline below it blocks its light and casts the visible shadow.

<object id="3" name="Spotlight" type="C64Light" x="485" y="58" rotation="90">
<properties>
<property name="c64scn_light_range_px" type="float" value="390"/>
<property name="c64scn_light_intensity" type="float" value="0.25"/>
<property name="c64scn_light_color" type="color" value="#B8D8FFFF"/>
<property name="c64scn_light_shadows" type="bool" value="true"/>
<property name="c64scn_light_angle_deg" type="float" value="64"/>
</properties>
</object>
<object id="4" name="Spotlight wall" type="C64Occluder" x="345" y="240">
<properties>
<property name="c64scn_occludes_light" type="bool" value="true"/>
</properties>
<polyline points="0,0 280,0"/>
</object>

Disable c64scn_light_shadows on a light when the visual does not need an occlusion pass. The pulse light does this in the tutorial.

4. Add fire as a Scene particle

Each C64Particle object owns its emitter. A named built-in preset avoids a separate particle asset for this small effect.

<object id="6" name="Candle fire" type="C64Particle" x="230" y="410">
<properties>
<property name="c64scn_particle_preset" value="FIRE"/>
<property name="c64scn_particle_autoplay" type="bool" value="true"/>
</properties>
</object>

The particle remains independent of the candle light: E pauses and hides fire, while L only enables or disables the lightmap.

5. Load the TMX and enable layers

The code uses the regular Scene loader. The map owns its lighting values; the application sets the viewport, enables smooth rendering layers, and explicitly starts lighting and particle effects.

const Sint32 idScene = mC64.sceneMgr().loadFromFile(BASEDIR "lighting.tmx");
g_pState->pScene = mC64.sceneMgr().get(idScene);
if(!g_pState->pScene) return false;
SDL_Rect rViewport = { iViewX, iViewY, iViewW, iViewH };
if(!g_pState->pScene->setViewport(&rViewport)
|| !g_pState->pScene->setLayerFeatures(1, SLF_RENDER, true)
|| !g_pState->pScene->setLayerFeatures(2, SLF_UPDATE | SLF_RENDER, true)
|| !g_pState->pScene->setLayerFeatures(1, SLF_SMOOTHSCROLL, true)
|| !g_pState->pScene->setLayerFeatures(2, SLF_SMOOTHSCROLL, true)) return false;
g_pState->pScene->setLightingEnabled(true);
g_pState->pScene->setEffectsEnabled(true);

6. Toggle lighting and particles separately

Lighting, fire effects and debug shapes start enabled. The runtime controls intentionally use separate Scene switches, which makes the two systems easy to diagnose and offers a simple quality setting.

else if(Event.key.key == SDLK_L)
{
const bool bEnabled = !State.pScene->isLightingEnabled();
State.pScene->setLightingEnabled(bEnabled);
pLog->msg(LL_INFO, "Lighting: %s\n", bEnabled ? "ON" : "OFF");
}
else if(Event.key.key == SDLK_E)
{
const bool bEnabled = !State.pScene->isEffectsEnabled();
State.pScene->setEffectsEnabled(bEnabled);
pLog->msg(LL_INFO, "Fire effects: %s\n", bEnabled ? "ON" : "OFF");
}

Complete source

The tutorial source is authoritative. The following listing is included directly from validation/source/Tutorial_16_Scene_Lighting.cpp.

/* ----------------------------------------------------------------------
CRM64Pro GDK - Roberto Prieto
Copyright (C) 2013-2026 MegaStorm Systems
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
------------------------------------------------------------------------
Tutorial 16 Scene Lighting
------------------------------------------------------------------------
Overview:
This tutorial loads a TMX Scene with animated lights, occluder shadows and
fire particles. The map owns visual properties; this code configures the
viewport, initial render state and the two independent Scene switches.
Key concepts:
- Configure ambient light in the TMX map.
- Use C64Light objects for candle, pulse and spotlight lighting.
- Use C64Occluder geometry to cast shadows.
- Use C64Particle objects with the FIRE preset.
- Enable and toggle lighting and particle effects independently.
Layer layout:
- Layer 1: Image background.
- Layer 2: C64Light, C64Occluder and C64Particle objects.
Controls:
- L: Toggle Scene lighting.
- E: Toggle Scene particle effects.
- D: Toggle light, occluder and particle debug shapes.
- Cursor Keys: Scroll the Scene.
- Q / ESC: Quit.
------------------------------------------------------------------------ */
#include "CRM64Pro.h"
#include "RunParameter.h"
using namespace CRM64Pro;
#define BASEDIR "Base/"
#ifdef CRM64PRO_PLATFORM_WINDOWS
#define OUTPUTDIR "Win64/"
#elif defined(CRM64PRO_PLATFORM_LINUX)
#define OUTPUTDIR "Linux/"
#elif defined(CRM64PRO_PLATFORM_MACOS)
#define OUTPUTDIR "macOS/"
#endif
#define RESOURCE_BG_CDC_NAME "Tutorial_bg"
#define OUTPUT_CDC OUTPUTDIR"Tutorial.cdc"
static const Sint32 iScreenW = 960;
static const Sint32 iScreenH = 540;
static const Sint32 iViewW = 640;
static const Sint32 iViewH = 480;
static const Sint32 iViewX = (iScreenW - iViewW) / 2;
static const Sint32 iViewY = (iScreenH - iViewH) / 2;
static const Sint32 iLogicRate = 20;
static const float fLogicStep = 1.0f / static_cast<float>(iLogicRate);
static const float fScrollSpeed = 320.0f;
// Store the resources used by the render callback.
struct TutorialState
{
Scene* pScene = nullptr;
Sint32 idBgImage = -1;
Sint32 idFont = -1;
bool bDebugShapes = true;
};
static TutorialState* g_pState = nullptr;
// Draw the Scene and its screen-space controls.
static Sint32 renderWrapper(Sint32)
{
if(!g_pState || !g_pState->pScene) return 0;
Main& mC64 = Main::instance();
Image* pBackground = mC64.imageMgr().get(g_pState->idBgImage);
Font* pFont = mC64.fontMgr().get(g_pState->idFont);
if(pBackground) pBackground->render();
g_pState->pScene->render();
if(pFont)
{
pFont->setPosition(static_cast<float>(iViewX), 516.0f);
pFont->render("Cursors scroll L lighting E fire effects D debug Q/Esc quit");
}
return 0;
}
// Load the TMX scene and enable the layers used by the tutorial.
static bool loadScene()
{
if(!g_pState) return false;
Main& mC64 = Main::instance();
const Sint32 idScene = mC64.sceneMgr().loadFromFile(BASEDIR "lighting.tmx");
g_pState->pScene = mC64.sceneMgr().get(idScene);
if(!g_pState->pScene) return false;
SDL_Rect rViewport = { iViewX, iViewY, iViewW, iViewH };
if(!g_pState->pScene->setViewport(&rViewport)
|| !g_pState->pScene->setLayerFeatures(1, SLF_RENDER, true)
|| !g_pState->pScene->setLayerFeatures(2, SLF_UPDATE | SLF_RENDER, true)
|| !g_pState->pScene->setLayerFeatures(1, SLF_SMOOTHSCROLL, true)
|| !g_pState->pScene->setLayerFeatures(2, SLF_SMOOTHSCROLL, true)) return false;
g_pState->pScene->setLightingEnabled(true);
g_pState->pScene->setEffectsEnabled(true);
return g_pState->pScene->setDebugObjectOverlay(SDO_OBJECT_SHAPE,
SDOT_LIGHT | SDOT_OCCLUDER | SDOT_PARTICLE, g_pState->bDebugShapes);
}
// Release tutorial-owned resources after complete or partial setup.
static void closeTutorial()
{
Main& mC64 = Main::instance();
mC64.sceneMgr().close(0);
mC64.fontMgr().close(0);
mC64.imageMgr().close(0);
}
// Run the Scene lighting tutorial.
int main(int argc, char* argv[])
{
if(!runParameterReadRender(argc, argv, eRenderer)) return 1;
atexit(CMem::destroy);
Main& mC64 = Main::instance();
TutorialState State;
g_pState = &State;
Log* pLog = mC64.logMgr().get();
pLog->init("Tutorial_16_Scene_Lighting", LL_DEBUG, LM_FILE | LM_STDOUT,
OUTPUTDIR"Tutorial_16_Scene_Lighting.log");
if(!mC64.timer().init() || !mC64.timer().setRate(0, iLogicRate))
{
return -1;
}
Screen* pScreen = mC64.configMgr().get();
pScreen->setSize(iScreenW, iScreenH);
pScreen->setDriver(eRenderer);
pScreen->setTitle("Tutorial 16 Scene - Lighting");
if(pScreen->show() < 0 || !pScreen->setRenderCallback(renderWrapper))
{
return -1;
}
State.idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
State.idFont = mC64.fontMgr().getBuiltin("CourierNew10White");
if(State.idBgImage <= 0 || State.idFont <= 0 || !mC64.fontMgr().get(State.idFont) || !loadScene())
{
pLog->msg(LL_ERROR, "Failed to load the lighting tutorial resources or Scene.\n");
closeTutorial();
return -1;
}
pLog->msg(LL_INFO, "Lighting tutorial loaded. Cursors: scroll, L: lighting, E: fire effects, D: debug shapes.\n");
SDL_Event Event;
bool bDone = false;
while(!bDone)
{
while(mC64.update(&Event))
{
if(Event.type == SDL_EVENT_QUIT) bDone = true;
if(Event.type != SDL_EVENT_KEY_DOWN) continue;
if(Event.key.key == SDLK_ESCAPE || Event.key.key == SDLK_Q) bDone = true;
else if(Event.key.key == SDLK_L)
{
const bool bEnabled = !State.pScene->isLightingEnabled();
State.pScene->setLightingEnabled(bEnabled);
pLog->msg(LL_INFO, "Lighting: %s\n", bEnabled ? "ON" : "OFF");
}
else if(Event.key.key == SDLK_E)
{
const bool bEnabled = !State.pScene->isEffectsEnabled();
State.pScene->setEffectsEnabled(bEnabled);
pLog->msg(LL_INFO, "Fire effects: %s\n", bEnabled ? "ON" : "OFF");
}
else if(Event.key.key == SDLK_D)
{
State.bDebugShapes = !State.bDebugShapes;
State.pScene->setDebugObjectOverlay(SDO_OBJECT_SHAPE,
SDOT_LIGHT | SDOT_OCCLUDER | SDOT_PARTICLE, State.bDebugShapes);
}
}
if(!bDone)
{
// Scroll layer 1; linked layers follow its default parallax ratio.
const float fStep = fScrollSpeed * fLogicStep;
float fDX = 0.0f;
float fDY = 0.0f;
if(mC64.getKeyState(SDLK_LEFT)) fDX -= fStep;
if(mC64.getKeyState(SDLK_RIGHT)) fDX += fStep;
if(mC64.getKeyState(SDLK_UP)) fDY -= fStep;
if(mC64.getKeyState(SDLK_DOWN)) fDY += fStep;
if(fDX != 0.0f || fDY != 0.0f)
{
float fX = 0.0f;
float fY = 0.0f;
if(State.pScene->getLayerPosition(1, &fX, &fY) >= 0)
State.pScene->setLayerPosition(1, Position(fX + fDX), Position(fY + fDY));
}
State.pScene->update();
}
}
closeTutorial();
return 0;
}
Screen * get(Sint32 idScreen=0)
Get a pointer to the screen using its handle.
Definition CoreConfig.cpp:1497
void setMTFriendly(Uint8 iDelayMS)
Set the multithread-friendly scheduler yield.
Definition CoreConfig.cpp:1183
Font Object class.
Definition CRM64Pro.h:4741
Sint32 render(const string &sText, bool bShowCursor=false, Sint32 iCurPos=-1, Sint32 idRes=0)
Render a text using this font.
Definition GraphicsFont.cpp:1095
bool setPosition(const Position &rPosX, const Position &rPosY, bool bInterpolate=false)
Set the font position.
Definition GraphicsFont.cpp:1037
Sint32 getBuiltin(const string &sName)
Get a built-in embedded font id using its name.
Definition GraphicsFont.cpp:1436
Sint32 close(Sint32 idFont) override
Close and destroy a font.
Definition GraphicsFont.cpp:1363
Font * get(const string &sName)
Get a pointer to the font using its name.
Definition GraphicsFont.cpp:1235
Image Object class.
Definition CRM64Pro.h:3356
Sint32 render(Sint32 idRes=0, SDL_FRect *pSrc=nullptr, SDL_FRect *pDst=nullptr)
Render the image.
Definition GraphicsImage.cpp:1575
Image * get(Sint32 idImg)
Get a pointer to the image using its handle.
Definition GraphicsImage.cpp:2279
Sint32 load(const string &sCDCFile, const string &sName)
Load an image stored in a CDC file.
Definition GraphicsImage.cpp:2476
Sint32 close(Sint32 idImg) override
Close and destroy an image.
Definition GraphicsImage.cpp:2178
Log Object class.
Definition CRM64Pro.h:771
CRM64Pro GDK Main class.
Definition CRM64Pro.h:502
Scene class.
Definition CRM64Pro.h:11200
Sint32 loadFromFile(const string &sTMXFile, ISceneObjectFactory *pFactory=nullptr)
Load a level from a TMX file.
Definition SceneMapLoad.cpp:1575
Screen Object class.
Definition CRM64Pro.h:1055
Sint32 show()
Show the screen.
Definition CoreConfig.cpp:362
Sint32 setDriver(eConfigRendererDriver eCRD)
Set the renderer driver.
Definition CoreConfig.cpp:454
bool setRenderCallback(RenderCallback fnCallback)
Set a callback function for rendering graphics at Render Frame Rate.
Definition CoreConfig.cpp:765
Sint32 setSize(Sint32 iWidth, Sint32 iHeight)
Set the size of the screen.
Definition CoreConfig.cpp:526
bool setTitle(const string &sName)
Set the title.
Definition CoreConfig.cpp:674
void setStatsLevel(eMemStatsLevel eMSL)
Set the memory stats gathering level.
Definition CMemoryManager.cpp:618
void destroy()
Releases the internal heap and displays final stats.
Definition CMemoryManager.cpp:954
@ MSL_HIGH
MSL_NORMAL plus deallocations and sizes logging for each module.
Definition CRM64Pro.h:387
eConfigRendererDriver
Screen renderer drivers.
Definition CRM64Pro.h:1013
@ CRD_SOFTWARE
Screen::setDriver(): software renderer driver. Slow but works on all platforms.
Definition CRM64Pro.h:1015
bool msg(eLogLevel eLL, const char *szFmt,...)
Write a message to the log.
Definition CoreLog.cpp:448
Log * get(Sint32 idLog=0)
Get a pointer to the log object.
Definition CoreLog.cpp:921
bool init(const string &sAppname, eLogLevel eLL, eLogMode eLM, const string &sFilename="", const string &sAuthor="", const string &sEmail="", const string &sWeb="")
Initialize the log object.
Definition CoreLog.cpp:338
@ LM_FILE
Log output mode for the 'lm' parameter in CRM64Pro::Log::init(). Output to a file and overwrite exist...
Definition CRM64Pro.h:748
@ LM_STDOUT
Log output mode for the 'lm' parameter in CRM64Pro::Log::init(). Output to standard output (stdout).
Definition CRM64Pro.h:750
@ LL_INFO
Runtime events (startup/shutdown). Messages without timestamp.
Definition CRM64Pro.h:764
@ LL_DEBUG
Detailed information for flow control.
Definition CRM64Pro.h:765
@ LL_ERROR
Runtime errors that do not require immediate termination.
Definition CRM64Pro.h:762
Timer & timer()
Get reference to Timer module.
Definition CoreMain.cpp:730
Sint32 update(SDL_Event *evUser=nullptr)
Manage the GDK update cycle with a fixed virtual logic frame rate.
Definition CoreMain.cpp:451
ImageMgr & imageMgr()
Get reference to Image Manager.
Definition CoreMain.cpp:754
SceneMgr & sceneMgr()
Get reference to Scene Manager.
Definition CoreMain.cpp:814
static void terminate()
Main explicit destructor.
Definition CoreMain.cpp:266
static Main & instance()
Access the Main singleton.
Definition CoreMain.cpp:252
LogMgr & logMgr()
Get reference to Log Manager.
Definition CoreMain.cpp:724
bool getKeyState(SDL_Keycode keycode) const
Get the current state of a key.
Definition CoreMain.cpp:686
FontMgr & fontMgr()
Get reference to Font Manager.
Definition CoreMain.cpp:778
ConfigMgr & configMgr()
Get reference to Config Manager.
Definition CoreMain.cpp:712
@ SLF_RENDER
Enable the rendering feature. Enabling this flag also enables SLF_UPDATE. Disabling render does not d...
Definition CRM64Pro.h:10079
@ SLF_SMOOTHSCROLL
Enable the smooth rendering feature. Check render() for further details.
Definition CRM64Pro.h:10080
@ SLF_UPDATE
Enable the updating feature. Check update() for further details.
Definition CRM64Pro.h:10078
@ SDO_OBJECT_SHAPE
Draw geometric shapes for selected object types.
Definition CRM64Pro.h:10094
@ SDOT_LIGHT
C64Light objects.
Definition CRM64Pro.h:10110
@ SDOT_OCCLUDER
C64Occluder objects.
Definition CRM64Pro.h:10111
@ SDOT_PARTICLE
C64Particle objects.
Definition CRM64Pro.h:10113
Sint32 close(Sint32 idScene) override
Close and destroy a Scene.
Definition Scene.cpp:2298
Scene * get(Sint32 idScene)
Get a pointer to the Scene using its handle.
Definition Scene.cpp:2355
bool init(eTimerState eState=TS_INIT)
Initialize the timer system.
Definition CoreTimer.cpp:543
bool setRate(Uint16 iR, Uint16 iL)
Set preferred Render and Logic Frame Rate.
Definition CoreTimer.cpp:556
CRM64Pro GDK.
Definition AudioTrack.cpp:36
Absolute coordinate or helper-relative position used by layout APIs.
Definition CRM64Pro.h:346

Navigation

Previous: Tutorial 16.5 - Scene War Game

Back to the tutorials index

This is the last tutorial in the series.