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.5: Scene War Game

Overview

This tutorial builds a strategy map with weighted A* pathfinding, animated grid movement, manual camera scrolling, terrain diagnostics and fog of war.

Final result

CRM64Pro Tutorial 16.5 Scene War Game

Runtime controls

  • Right-click: Move the player unit.
  • Cursor keys: Scroll the map.
  • F1: Show or hide the grid and terrain weights.
  • F2: Show or hide fog of war.
  • 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 Scene tile layers and fixed-rate logic.
  • Included assets available under bin/Base/: map-war1.png, map-war1-heightmap.png, sprite.png.
  • Keep the Tutorial_bg resource from Tutorial 02 in Tutorial.cdc.

What you will learn

  • Import a visual map as a Scene tile layer.
  • Build movement costs from a heightmap image.
  • Find weighted eight-direction routes with A*.
  • Render a smoothly moving sprite over a logical grid.
  • Maintain visible and explored fog cells.
  • Save the generated Scene and its resources.

Walkthrough

1. Store grid and visual movement state

The unit keeps its current cell, destination, in-cell pixel offset and current A* path.

struct Player
{
Sint32 iIDSprite;
Sint32 iX, iY; // Grid coordinates (cells)
float fOffX, fOffY; // Visual pixel offset from center of current cell
Sint32 iTargetX, iTargetY; // Destination (grid cells)
bool bIsMoving;
vector<SDL_Point> vPath; // Path to follow (list of grid points)
Sint32 iCurrentPathIndex; // Current step in the path
};

2. Order the A* open set

The priority queue places the node with the lowest estimated total cost first.

struct Node
{
Sint32 iX, iY;
float fF, fG; // f = total cost (g + h), g = path cost from start
// Reverse comparison so priority_queue selects the lowest cost first.
bool operator<(const Node& rOther) const
{
return fF > rOther.fF;
}
};

3. Prepare the weighted search

Cost and predecessor grids cover the map. Eight directions allow cardinal and diagonal movement.

// Data structures for A*
priority_queue<Node> vOpenSet;
vector<vector<float>> vvfCostSoFar(iHeight, vector<float>(iWidth, -1.0f));
vector<vector<SDL_Point>> vvCameFrom(iHeight, vector<SDL_Point>(iWidth, { -1, -1 }));
// Start node
vOpenSet.push({ iStartX, iStartY, 0.0f, 0.0f });
vvfCostSoFar[iStartY][iStartX] = 0.0f;
// Directions (8-way movement: cardinal + diagonals)
const Sint32 iDirs[8][2] = { {0,1}, {1,0}, {0,-1}, {-1,0}, {1,1}, {1,-1}, {-1,1}, {-1,-1} };

4. Expand cheaper routes

Blocked cells are skipped. Terrain weight and diagonal distance determine the accumulated cost.

Uint32 iWeight = pMap->iCellMap[iNY][iNX];
if(iWeight == 255) continue; // Unwalkable terrain
// Cost: grid distance (1.0 or 1.414 for diagonal) * terrain weight
float fDist = (rDir[0] != 0 && rDir[1] != 0) ? 1.414f : 1.0f;
float fNewCost = vvfCostSoFar[mCurrent.iY][mCurrent.iX] + (fDist * (float)iWeight);
if(vvfCostSoFar[iNY][iNX] == -1.0f || fNewCost < vvfCostSoFar[iNY][iNX])
{
vvfCostSoFar[iNY][iNX] = fNewCost;
float fPriority = fNewCost + (abs(iEndX - iNX) + abs(iEndY - iNY)); // Manhattan distance heuristic
vOpenSet.push({ iNX, iNY, fPriority, fNewCost });
vvCameFrom[iNY][iNX] = { mCurrent.iX, mCurrent.iY };
}

5. Reconstruct the route

Predecessors are followed from the destination to the start, then reversed for movement.

if(vvCameFrom[iEndY][iEndX].x != -1)
{
SDL_Point mCurr = { iEndX, iEndY };
while(mCurr.x != iStartX || mCurr.y != iStartY)
{
vPath.push_back(mCurr);
mCurr = vvCameFrom[mCurr.y][mCurr.x];
}
reverse(vPath.begin(), vPath.end());
}

6. Convert the heightmap into costs

Each map cell samples the center of its source block. Red is blocked; grayscale becomes a movement weight.

// Sample the center of each tile.
Sint32 iPX = iX * g_pState->iTileSize + (g_pState->iTileSize / 2);
Sint32 iPY = iY * g_pState->iTileSize + (g_pState->iTileSize / 2);
if(iPX >= sH->w) iPX = sH->w - 1;
if(iPY >= sH->h) iPY = sH->h - 1;
Uint8 iR = 0, iG = 0, iB = 0, iA = 0;
if(!SDL_ReadSurfacePixel(sH, iPX, iPY, &iR, &iG, &iB, &iA))
{
mImageMgr.close(idImgH);
mLog.msg(LL_ERROR, "Failed to read heightmap image pixels.\n");
return false;
}
// Red blocks movement; grayscale controls movement cost.
Uint32 iWeight = 255;
if(iR <= 200 || iG >= 50 || iB >= 50)
{
const Sint32 iBrightness = (iR + iG + iB) / 3;
iWeight = 128 - static_cast<Uint32>(static_cast<float>(iBrightness) * 0.439f);
if(iWeight < 16) iWeight = 16;
}
pLayerH->setCellValue(iY, iX, iWeight);

7. Reveal and remember terrain

Previously visible cells become explored, then a circular area around the unit becomes fully visible.

// 1. Downgrade currently visible cells (0) to explored (g_pState->iFogFirstGID).
// Using full map scan (Option A) which is sufficient for this map size.
for(Sint32 iY = 0; iY < iH; iY++)
{
for(Sint32 iX = 0; iX < iW; iX++)
{
// If cell is currently visible (0), fade it to semi-transparent (explored)
if(pFog->iCellMap[iY][iX] == 0)
{
pFog->iCellMap[iY][iX] = g_pState->iFogFirstGID;
}
}
}
// 2. Reveal new cells (circular radius)
Sint32 iRadius = 3;
Sint32 iR2 = iRadius * iRadius;
for(Sint32 iY = -iRadius; iY <= iRadius; iY++)
{
for(Sint32 iX = -iRadius; iX <= iRadius; iX++)
{
// Check circular distance
if(iX * iX + iY * iY <= iR2)
{
Sint32 iGX = iGridX + iX;
Sint32 iGY = iGridY + iY;
if(iGX >= 0 && iGX < iW && iGY >= 0 && iGY < iH)
{
pFog->iCellMap[iGY][iGX] = 0; // Set to 0 (No Tile) = Fully Visible
}
}
}
}

8. Render between cells

The logical cell plus pixel offset produces smooth world movement. The Scene context converts it to the viewport.

// Calculate absolute world position using Grid + Sub-cell Offset
float fWorldX = (static_cast<float>(g_pState->pPlayer->iX * g_pState->iTileSize)) + g_pState->pPlayer->fOffX;
float fWorldY = (static_cast<float>(g_pState->pPlayer->iY * g_pState->iTileSize)) + g_pState->pPlayer->fOffY;
// Apply scroll and viewport offset to get screen coordinates
SDL_FPoint pScreen = rContext.worldToScreen(fWorldX, fWorldY, true);
float fScreenX = pScreen.x;
float fScreenY = pScreen.y;

9. Build fog tiles at runtime

One generated image contains explored and opaque fog tiles; the Scene assigns their global IDs.

// Tile 1: semi-transparent (50% alpha)
SDL_Rect r1 = { 0, 0, g_pState->iTileSize, g_pState->iTileSize };
if(!SDL_FillSurfaceRect(sFog, &r1, SDL_MapRGBA(SDL_GetPixelFormatDetails(sFog->format), NULL, 0, 0, 0, 128)))
{
SDL_DestroySurface(sFog);
mLog.msg(LL_ERROR, "Failed to draw the fog image.\n");
return false;
}
// Tile 2: opaque (black 100% alpha)
SDL_Rect r2 = { g_pState->iTileSize, 0, g_pState->iTileSize, g_pState->iTileSize };
if(!SDL_FillSurfaceRect(sFog, &r2, SDL_MapRGBA(SDL_GetPixelFormatDetails(sFog->format), NULL, 0, 0, 0, 255)) ||
pFogImage->assignSurface(sFog, 1) < 0)
{
SDL_DestroySurface(sFog);
mLog.msg(LL_ERROR, "Failed to finish the fog image.\n");
return false;
}

10. Convert clicks into path requests

A right-button edge is converted through Scene coordinates and only starts movement when A* returns a route.

if(g_pState->pScene->mouseToCell(&iTX, &iTY, 1))
{
// Only log if target changes
if(iTX != g_pState->pPlayer->iTargetX || iTY != g_pState->pPlayer->iTargetY)
{
mLog.msg(LL_INFO, "Moving to grid cell: (%d, %d)\n", iTX, iTY);
}
// Pathfinding logic: trigger movement only if target changes
if(iTX != g_pState->pPlayer->iX || iTY != g_pState->pPlayer->iY) {
g_pState->pPlayer->vPath = findPath(g_pState->pPlayer->iX, g_pState->pPlayer->iY, iTX, iTY);
if(!g_pState->pPlayer->vPath.empty()) {
g_pState->pPlayer->iCurrentPathIndex = 0;
g_pState->pPlayer->bIsMoving = true;
}
}
}

11. Save and clean up

The final Scene and CDC are checked when saved. Cleanup also handles partial initialization.

if(g_pState->pScene->save(OUTPUTDIR"wargame.tmx", "", "wargame.cdc", true) < 0)
{
mLog.msg(LL_ERROR, "Failed to save the War Game Scene.\n");
closeTutorial();
return -1;
}
static void closeTutorial()
{
Main& mC64 = Main::instance();
mC64.sceneMgr().close(0);
mC64.spriteMgr().close(0);
mC64.tileMgr().close(0);
mC64.fontMgr().close(0);
mC64.imageMgr().close(0);
Main::terminate();
}

Complete source

The tutorial source is authoritative. The following listing is included directly from validation/source/Tutorial_16_Scene_WarGame.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 WarGame
------------------------------------------------------------------------
Overview:
This tutorial loads a strategy map, assigns terrain costs, moves a unit
with A* pathfinding, renders the unit, and updates fog of war.
Key concepts:
- Build terrain costs from a heightmap image.
- Use A* pathfinding on a weighted grid.
- Convert between screen, world and cell coordinates.
- Render sprites from a layer callback.
- Reveal fog of war around the moving unit.
- Move a unit at a fixed logic rate.
Layer layout:
- Layer 1: Visual background.
- Layer 2: Logic heightmap/weights.
- Layer 3: Sprite placement layer.
- Layer 4: Fog of war layer.
Controls:
- Right-Click: Move player unit.
- Cursor Keys: Manual camera scroll.
- F1: Toggle grid + heightmap debug.
- F2: Toggle fog of war.
- Q / ESC: Quit.
Loop order:
- Poll events and capture input state.
- Apply camera scroll input.
- Resolve right-click target cell and path request.
- Run fixed-step movement/fog updates.
- Render from callback using Scene::render().
------------------------------------------------------------------------ */
#include "CRM64Pro.h"
#include "RunParameter.h"
using namespace CRM64Pro;
// Paths used by the tutorial.
#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"
// Screen and viewport size.
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;
// Logic timing.
static const Sint32 iLogicRate = 20;
static const float fLogicStep = 1.0f / static_cast<float>(iLogicRate);
static const float fScrollSpeed = 320.0f; // Pixels per second
static const float fPlayerSpeed = 80.0f; // Pixels per second
// Player unit state.
// Grid coordinates drive pathfinding; offsets draw smooth movement between cells.
struct Player
{
Sint32 iIDSprite;
Sint32 iX, iY; // Grid coordinates (cells)
float fOffX, fOffY; // Visual pixel offset from center of current cell
Sint32 iTargetX, iTargetY; // Destination (grid cells)
bool bIsMoving;
vector<SDL_Point> vPath; // Path to follow (list of grid points)
Sint32 iCurrentPathIndex; // Current step in the path
};
// State shared by callbacks.
struct TutorialState
{
Player* pPlayer = nullptr;
Sint32 idBgImage = -1;
Sint32 idDebugFont = -1;
Sint32 iFogFirstGID = 0; // GID for semi-transparent fog. GID+1 is opaque.
Sint32 iTileSize = 0; // Determined at runtime from map
Scene* pScene = nullptr;
};
static TutorialState* g_pState = nullptr;
// Screen render callback.
Sint32 renderWrapper(Sint32)
{
if(!g_pState || !g_pState->pScene) return 0;
Main& mC64 = Main::instance();
Image* pBG = mC64.imageMgr().get(g_pState->idBgImage);
if(pBG) pBG->render();
g_pState->pScene->render();
return 0;
}
// Tile layer used to show terrain weights.
class SceneLayerHeightMap : public SceneLayerTile
{
public:
SceneLayerHeightMap(const string& sName, Sint32 iH, Sint32 iW) : SceneLayerTile(sName, iH, iW)
{
}
// Draw terrain weights on top of visible cells.
Sint32 render(Scene* pScene, SceneLayerContext& rContext) override
{
if(!g_pState) return 0;
Font* pFont = Main::instance().fontMgr().get(g_pState->idDebugFont);
if(!pFont) return 0;
// Only draw cells inside the current viewport.
VisibleRange mVR = calculateVisibleRange(rContext.rViewport, rContext.fScrollX, rContext.fScrollY);
for(Sint32 iLY = mVR.iCYStart; iLY < mVR.iCYEnd; iLY++)
{
Sint32 iCY = iLY;
if(iCY < 0 || iCY >= getHeight()) continue;
// Convert the row position to screen coordinates.
float fScreenY = rContext.worldToScreen(0.0f, static_cast<float>(iLY * mVR.iTileH), true).y;
for(Sint32 iLX = mVR.iCXStart; iLX < mVR.iCXEnd; iLX++)
{
Sint32 iCX = iLX;
if(iCX < 0 || iCX >= getWidth()) continue;
Uint32 iWeight = iCellMap[iCY][iCX];
// Ensure x position includes scroll offset
float fRawX = rContext.worldToScreen(static_cast<float>(iLX * mVR.iTileW), 0.0f, true).x;
float fScreenX = floorf(fRawX);
char szBuf[16];
snprintf(szBuf, 16, "%d", iWeight);
Sint32 iFW = pFont->getWidth(szBuf);
Sint32 iFH = pFont->getHeight();
pFont->setPosition(fScreenX + (mVR.iTileW - iFW) / 2, fScreenY + (mVR.iTileH - iFH) / 2);
pFont->render(szBuf);
}
}
return 0;
}
};
// --- A* pathfinder ---
// Standard A* implementation for grid-based pathfinding with weights.
struct Node
{
Sint32 iX, iY;
float fF, fG; // f = total cost (g + h), g = path cost from start
// Reverse comparison so priority_queue selects the lowest cost first.
bool operator<(const Node& rOther) const
{
return fF > rOther.fF;
}
};
// Find a walkable route between two map cells.
vector<SDL_Point> findPath(Sint32 iStartX, Sint32 iStartY, Sint32 iEndX, Sint32 iEndY)
{
vector<SDL_Point> vPath;
if(!g_pState || !g_pState->pScene) return vPath;
SceneLayerTile* pMap = g_pState->pScene->accessLayerTile(2); // Layer 2 is heightmap
if(!pMap) return vPath;
Sint32 iWidth = pMap->getWidth();
Sint32 iHeight = pMap->getHeight();
// Validation: bounds and target walkable check
if(iStartX < 0 || iStartX >= iWidth || iStartY < 0 || iStartY >= iHeight ||
iEndX < 0 || iEndX >= iWidth || iEndY < 0 || iEndY >= iHeight) return vPath;
// Check if target is unwalkable (255)
if(pMap->iCellMap[iEndY][iEndX] == 255) return vPath;
// Data structures for A*
priority_queue<Node> vOpenSet;
vector<vector<float>> vvfCostSoFar(iHeight, vector<float>(iWidth, -1.0f));
vector<vector<SDL_Point>> vvCameFrom(iHeight, vector<SDL_Point>(iWidth, { -1, -1 }));
// Start node
vOpenSet.push({ iStartX, iStartY, 0.0f, 0.0f });
vvfCostSoFar[iStartY][iStartX] = 0.0f;
// Directions (8-way movement: cardinal + diagonals)
const Sint32 iDirs[8][2] = { {0,1}, {1,0}, {0,-1}, {-1,0}, {1,1}, {1,-1}, {-1,1}, {-1,-1} };
while(!vOpenSet.empty())
{
Node mCurrent = vOpenSet.top();
vOpenSet.pop();
if(mCurrent.iX == iEndX && mCurrent.iY == iEndY) break; // Path found!
for(auto& rDir : iDirs)
{
Sint32 iNX = mCurrent.iX + rDir[0];
Sint32 iNY = mCurrent.iY + rDir[1];
if(iNX >= 0 && iNX < iWidth && iNY >= 0 && iNY < iHeight)
{
Uint32 iWeight = pMap->iCellMap[iNY][iNX];
if(iWeight == 255) continue; // Unwalkable terrain
// Cost: grid distance (1.0 or 1.414 for diagonal) * terrain weight
float fDist = (rDir[0] != 0 && rDir[1] != 0) ? 1.414f : 1.0f;
float fNewCost = vvfCostSoFar[mCurrent.iY][mCurrent.iX] + (fDist * (float)iWeight);
if(vvfCostSoFar[iNY][iNX] == -1.0f || fNewCost < vvfCostSoFar[iNY][iNX])
{
vvfCostSoFar[iNY][iNX] = fNewCost;
float fPriority = fNewCost + (abs(iEndX - iNX) + abs(iEndY - iNY)); // Manhattan distance heuristic
vOpenSet.push({ iNX, iNY, fPriority, fNewCost });
vvCameFrom[iNY][iNX] = { mCurrent.iX, mCurrent.iY };
}
}
}
}
// Reconstruct path by backtracking from end to start
if(vvCameFrom[iEndY][iEndX].x != -1)
{
SDL_Point mCurr = { iEndX, iEndY };
while(mCurr.x != iStartX || mCurr.y != iStartY)
{
vPath.push_back(mCurr);
mCurr = vvCameFrom[mCurr.y][mCurr.x];
}
reverse(vPath.begin(), vPath.end());
}
return vPath;
}
// Update fog of war.
// Resets visibility and calculates new visible area based on player position.
void updateFog(int iGridX, int iGridY)
{
if(!g_pState || !g_pState->pScene) return;
SceneLayerTile* pFog = g_pState->pScene->accessLayerTile(4);
if(!pFog) return;
Sint32 iW = pFog->getWidth();
Sint32 iH = pFog->getHeight();
// 1. Downgrade currently visible cells (0) to explored (g_pState->iFogFirstGID).
// Using full map scan (Option A) which is sufficient for this map size.
for(Sint32 iY = 0; iY < iH; iY++)
{
for(Sint32 iX = 0; iX < iW; iX++)
{
// If cell is currently visible (0), fade it to semi-transparent (explored)
if(pFog->iCellMap[iY][iX] == 0)
{
pFog->iCellMap[iY][iX] = g_pState->iFogFirstGID;
}
}
}
// 2. Reveal new cells (circular radius)
Sint32 iRadius = 3;
Sint32 iR2 = iRadius * iRadius;
for(Sint32 iY = -iRadius; iY <= iRadius; iY++)
{
for(Sint32 iX = -iRadius; iX <= iRadius; iX++)
{
// Check circular distance
if(iX * iX + iY * iY <= iR2)
{
Sint32 iGX = iGridX + iX;
Sint32 iGY = iGridY + iY;
if(iGX >= 0 && iGX < iW && iGY >= 0 && iGY < iH)
{
pFog->iCellMap[iGY][iGX] = 0; // Set to 0 (No Tile) = Fully Visible
}
}
}
}
}
// Update sprite position on layer 3 (Logical grid update)
// This keeps the "Entities" tile layer in sync with where units are logically.
void updateSpriteMap(Sint32 iOldX, Sint32 iOldY, Sint32 iNewX, Sint32 iNewY)
{
if(!g_pState || !g_pState->pScene) return;
SceneLayerTile* pSprLayer = g_pState->pScene->accessLayerTile(3);
if(!pSprLayer) return;
// Clear old position
if(iOldX >= 0 && iOldX < pSprLayer->getWidth() && iOldY >= 0 && iOldY < pSprLayer->getHeight())
pSprLayer->setCellValue(iOldY, iOldX, 0);
// Set new position
if(iNewX >= 0 && iNewX < pSprLayer->getWidth() && iNewY >= 0 && iNewY < pSprLayer->getHeight())
pSprLayer->setCellValue(iNewY, iNewX, 1000); // Mark presence (value > 0)
}
// Callback: layer 3 render
// Renders the sprite with sub-pixel precision using the offset values.
void onRenderSprites(const SceneLayerContext& rContext)
{
if(!g_pState || !g_pState->pPlayer) return;
Sprite* pSpr = Main::instance().spriteMgr().get(g_pState->pPlayer->iIDSprite);
if(!pSpr) return;
// Calculate absolute world position using Grid + Sub-cell Offset
float fWorldX = (static_cast<float>(g_pState->pPlayer->iX * g_pState->iTileSize)) + g_pState->pPlayer->fOffX;
float fWorldY = (static_cast<float>(g_pState->pPlayer->iY * g_pState->iTileSize)) + g_pState->pPlayer->fOffY;
// Apply scroll and viewport offset to get screen coordinates
SDL_FPoint pScreen = rContext.worldToScreen(fWorldX, fWorldY, true);
float fScreenX = pScreen.x;
float fScreenY = pScreen.y;
// Optimization: scissor test to avoid rendering off-screen sprites
if(fScreenX > rContext.rViewport.x - 64 && fScreenX < rContext.rViewport.x + rContext.rViewport.w &&
fScreenY > rContext.rViewport.y - 64 && fScreenY < rContext.rViewport.y + rContext.rViewport.h)
{
pSpr->setPosition(fScreenX, fScreenY);
pSpr->render();
}
}
// Set up sprite animations.
// Configures the sprite sheet and defines the animation states.
void setupSpriteAnimations(Sint32 iIDSpr)
{
Sprite* pSpr = Main::instance().spriteMgr().get(iIDSpr);
if(!pSpr) return;
// Configure the sprite sheet layout
pSpr->setOffset(48, 48);
// Map logical states to animation rows
// Calculate Hotspot: (48-32)/2 = 8, (48-32) = 8 and frame times
Sint32 iHX = 8;
Sint32 iHY = 8;
for(Sint32 i = 0; i <= 10; i++)
{
pSpr->setFrameTimeIndex(i, -1, 150);
pSpr->setAnimHotSpotIndex(i, iHX, iHY);
}
pSpr->setFrameTimeIndex(0, -1, 1000);
// Set initial state
}
// Set up the Scene architecture.
// Creates the layers and loads resources.
bool setupTutorial(const string& rMapName, Sint32 iTileSize)
{
if(!g_pState || !g_pState->pScene) return false;
Log& mLog = *Main::instance().logMgr().get();
ImageMgr& mImageMgr = Main::instance().imageMgr();
TileMgr& mTileMgr = Main::instance().tileMgr();
string sMapFile = rMapName + ".png";
string sHeightMapFile = rMapName + "-heightmap.png";
mLog.msg(LL_INFO, "Setting up War Game Layers for map '%s' (TileSize: %d)...\n", rMapName.c_str(), iTileSize);
// 1. Layer 1: background (visuals)
if(g_pState->pScene->importLayerFromFile(1, sMapFile, "Background", iTileSize, iTileSize) < 0)
{
mLog.msg(LL_ERROR, "Failed to load background map from '%s'.\n", sMapFile.c_str());
return false;
}
// Auto-detect map size in tiles
SceneLayerTile* pLayerBg = g_pState->pScene->accessLayerTile(1);
if(!pLayerBg) return false;
Sint32 iMapW = pLayerBg->getWidth();
Sint32 iMapH = pLayerBg->getHeight();
// Get tilesize from the loaded layer (source of truth)
g_pState->iTileSize = pLayerBg->getCellWidth();
if(iMapW <= 0 || iMapH <= 0 || g_pState->iTileSize <= 0)
{
mLog.msg(LL_ERROR, "Background layer has invalid dimensions.\n");
return false;
}
mLog.msg(LL_INFO, "Layer 1 imported. Map Size: %dx%d tiles. Tile Size: %d.\n", iMapW, iMapH, g_pState->iTileSize);
// 2. Layer 2: heightmap (logic)
SceneLayerHeightMap* pLayerH = new(std::nothrow) SceneLayerHeightMap("HeightMap", iMapH, iMapW);
if(!pLayerH)
{
mLog.msg(LL_ERROR, "Failed to allocate the heightmap layer.\n");
return false;
}
if(!pLayerH->setCellWidth(g_pState->iTileSize) ||
!pLayerH->setCellHeight(g_pState->iTileSize) ||
g_pState->pScene->addLayer(pLayerH, 2) < 0)
{
delete pLayerH;
mLog.msg(LL_ERROR, "Failed to add the heightmap layer.\n");
return false;
}
if(!g_pState->pScene->setLayerFeatures(2, SLF_RENDER | SLF_UPDATE, false) ||
!g_pState->pScene->setLayerAlphaMod(2, 128)) // Semi-transparent debug view
{
mLog.msg(LL_ERROR, "Failed to configure the heightmap layer.\n");
return false;
}
// Load heightmap data from image
Sint32 idImgH = mImageMgr.loadFromFile(sHeightMapFile);
if(idImgH <= 0)
{
mLog.msg(LL_ERROR, "Failed to load heightmap image: %s\n", sHeightMapFile.c_str());
return false;
}
Image* pHeightImage = mImageMgr.get(idImgH);
SDL_Surface* sH = pHeightImage ? pHeightImage->getSurface() : nullptr;
if(!sH || sH->w <= 0 || sH->h <= 0)
{
mImageMgr.close(idImgH);
mLog.msg(LL_ERROR, "Failed to access heightmap image pixels.\n");
return false;
}
for(Sint32 iY = 0; iY < iMapH; iY++)
{
for(Sint32 iX = 0; iX < iMapW; iX++)
{
// Sample the center of each tile.
Sint32 iPX = iX * g_pState->iTileSize + (g_pState->iTileSize / 2);
Sint32 iPY = iY * g_pState->iTileSize + (g_pState->iTileSize / 2);
if(iPX >= sH->w) iPX = sH->w - 1;
if(iPY >= sH->h) iPY = sH->h - 1;
Uint8 iR = 0, iG = 0, iB = 0, iA = 0;
if(!SDL_ReadSurfacePixel(sH, iPX, iPY, &iR, &iG, &iB, &iA))
{
mImageMgr.close(idImgH);
mLog.msg(LL_ERROR, "Failed to read heightmap image pixels.\n");
return false;
}
// Red blocks movement; grayscale controls movement cost.
Uint32 iWeight = 255;
if(iR <= 200 || iG >= 50 || iB >= 50)
{
const Sint32 iBrightness = (iR + iG + iB) / 3;
iWeight = 128 - static_cast<Uint32>(static_cast<float>(iBrightness) * 0.439f);
if(iWeight < 16) iWeight = 16;
}
pLayerH->setCellValue(iY, iX, iWeight);
}
}
mImageMgr.close(idImgH);
// 3. Layer 3: sprites (container)
if(g_pState->pScene->createLayer("Sprites", SLT_TILE, 3, iMapW, iMapH) < 0)
{
mLog.msg(LL_ERROR, "Failed to create the sprite layer.\n");
return false;
}
SceneLayerTile* pLayerS = g_pState->pScene->accessLayerTile(3);
if(!pLayerS || !pLayerS->setCellWidth(g_pState->iTileSize) ||
!pLayerS->setCellHeight(g_pState->iTileSize) ||
!g_pState->pScene->setLayerOnRenderEnd(3, onRenderSprites) ||
!g_pState->pScene->setLayerFeatures(3, SLF_RENDER, true))
{
mLog.msg(LL_ERROR, "Failed to configure the sprite layer.\n");
return false;
}
// 4. Layer 4: fog of war
// Create 2-tile fog system (semi-transparent + opaque)
Sint32 idFogImg = mImageMgr.create("FogImage");
SDL_Surface* sFog = SDL_CreateSurface(g_pState->iTileSize * 2, g_pState->iTileSize, SDL_PIXELFORMAT_RGBA8888);
Image* pFogImage = mImageMgr.get(idFogImg);
if(idFogImg <= 0 || !pFogImage || !sFog)
{
if(sFog) SDL_DestroySurface(sFog);
mLog.msg(LL_ERROR, "Failed to create the fog image.\n");
return false;
}
// Tile 1: semi-transparent (50% alpha)
SDL_Rect r1 = { 0, 0, g_pState->iTileSize, g_pState->iTileSize };
if(!SDL_FillSurfaceRect(sFog, &r1, SDL_MapRGBA(SDL_GetPixelFormatDetails(sFog->format), NULL, 0, 0, 0, 128)))
{
SDL_DestroySurface(sFog);
mLog.msg(LL_ERROR, "Failed to draw the fog image.\n");
return false;
}
// Tile 2: opaque (black 100% alpha)
SDL_Rect r2 = { g_pState->iTileSize, 0, g_pState->iTileSize, g_pState->iTileSize };
if(!SDL_FillSurfaceRect(sFog, &r2, SDL_MapRGBA(SDL_GetPixelFormatDetails(sFog->format), NULL, 0, 0, 0, 255)) ||
pFogImage->assignSurface(sFog, 1) < 0)
{
SDL_DestroySurface(sFog);
mLog.msg(LL_ERROR, "Failed to finish the fog image.\n");
return false;
}
// Create tile resource (2x1 tiles)
Sint32 idFogTile = mTileMgr.create("FogTiles");
Tile* pFogTile = mTileMgr.get(idFogTile);
if(idFogTile <= 0 || !pFogTile || pFogTile->assignImage(idFogImg, 1) < 0 ||
!pFogTile->setOffset(1, g_pState->iTileSize, g_pState->iTileSize)) // Split into tiles
{
mLog.msg(LL_ERROR, "Failed to create the fog tiles.\n");
return false;
}
if(g_pState->pScene->createLayer("FogOfWar", SLT_TILE, 4, iMapW, iMapH) < 0)
{
mLog.msg(LL_ERROR, "Failed to create the fog layer.\n");
return false;
}
// This creates GID for tile 1 (semi). GID+1 is tile 2 (opaque).
g_pState->iFogFirstGID = g_pState->pScene->createTileSet(idFogTile);
if(g_pState->iFogFirstGID <= 0)
{
mLog.msg(LL_ERROR, "Failed to attach the fog tileset.\n");
return false;
}
// Initialize fog with opaque tile (GID + 1)
SceneLayerTile* pLayerFog = g_pState->pScene->accessLayerTile(4);
if(!pLayerFog || !pLayerFog->setCellWidth(g_pState->iTileSize) ||
!pLayerFog->setCellHeight(g_pState->iTileSize))
{
mLog.msg(LL_ERROR, "Failed to configure the fog layer.\n");
return false;
}
for(Sint32 iY = 0; iY < iMapH; iY++)
for(Sint32 iX = 0; iX < iMapW; iX++)
pLayerFog->setCellValue(iY, iX, g_pState->iFogFirstGID + 1); // Opaque
if(!g_pState->pScene->setLayerFeatures(4, SLF_RENDER | SLF_UPDATE, true))
{
mLog.msg(LL_ERROR, "Failed to enable the fog layer.\n");
return false;
}
return true;
}
// Close resources in ownership order, including partially completed setup.
static void closeTutorial()
{
Main& mC64 = Main::instance();
mC64.sceneMgr().close(0);
mC64.spriteMgr().close(0);
mC64.tileMgr().close(0);
mC64.fontMgr().close(0);
mC64.imageMgr().close(0);
}
// Run the tutorial application and release its resources.
int main(int argc, char* argv[])
{
if(!runParameterReadRender(argc, argv, eRenderer)) return 1;
// 1. Initialization
atexit(CMem::destroy);
Main& mC64 = Main::instance();
TutorialState state;
g_pState = &state;
Player player = {};
g_pState->pPlayer = &player;
Log& mLog = *Main::instance().logMgr().get();
mLog.init("Tutorial_16_Scene_WarGame", LL_DEBUG, LM_FILE | LM_STDOUT, OUTPUTDIR"Tutorial_16_Scene_WarGame.log");
if(!mC64.timer().init() || !mC64.timer().setRate(0, iLogicRate))
{
mLog.msg(LL_ERROR, "Failed to initialize tutorial timing.\n");
return -1;
}
Screen* pScreen = mC64.configMgr().get();
pScreen->setSize(iScreenW, iScreenH);
pScreen->setDriver(eRenderer);
pScreen->setTitle("Tutorial 16 Scene - WarGame");
if(pScreen->show() < 0)
{
return -1;
}
if(!pScreen->setRenderCallback(renderWrapper))
{
mLog.msg(LL_ERROR, "Failed to set the render callback.\n");
return -1;
}
// 2. Resource loading
g_pState->idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
g_pState->idDebugFont = mC64.fontMgr().getBuiltin("CourierNew10White");
if(g_pState->idBgImage <= 0 || g_pState->idDebugFont <= 0)
{
mLog.msg(LL_ERROR, "Failed to load the tutorial interface resources.\n");
closeTutorial();
return -1;
}
Sint32 idSprImg = mC64.imageMgr().loadFromFile(BASEDIR"sprite.png");
Image* pImSprite = mC64.imageMgr().get(idSprImg);
if(idSprImg <= 0 || !pImSprite || pImSprite->setColorKey(255, 255, 255) < 0)
{
mLog.msg(LL_ERROR, "Failed to load the player sprite image.\n");
closeTutorial();
return -1;
}
g_pState->pPlayer->iIDSprite = mC64.spriteMgr().create("Player");
Sprite* pPlayerSprite = mC64.spriteMgr().get(g_pState->pPlayer->iIDSprite);
if(!pPlayerSprite || pPlayerSprite->assignImage(idSprImg, 1) < 0)
{
mLog.msg(LL_ERROR, "Failed to create the player sprite.\n");
closeTutorial();
return -1;
}
setupSpriteAnimations(g_pState->pPlayer->iIDSprite);
// 3. Scene setup
Sint32 idScene = mC64.sceneMgr().create("WarMap");
g_pState->pScene = mC64.sceneMgr().get(idScene);
if(idScene < 0 || !g_pState->pScene)
{
mLog.msg(LL_ERROR, "Failed to create the War Game Scene.\n");
closeTutorial();
return -1;
}
// Prepare all layers and resources
if(!setupTutorial(BASEDIR"map-war1", 32))
{
mLog.msg(LL_CRITICAL, "Failed to setup War Game layers.\n");
closeTutorial();
return -1;
}
// Initialize player position
SceneLayerTile* pL1 = g_pState->pScene->accessLayerTile(1);
if(!pL1)
{
mLog.msg(LL_ERROR, "Failed to access the background layer.\n");
closeTutorial();
return -1;
}
// Convert pixel center to grid center
g_pState->pPlayer->iX = (Sint32)((pL1->getTotalWidth() / 2.0f) / g_pState->iTileSize);
g_pState->pPlayer->iY = (Sint32)((pL1->getTotalHeight() / 2.0f) / g_pState->iTileSize);
g_pState->pPlayer->fOffX = 0.0f;
g_pState->pPlayer->fOffY = 0.0f;
g_pState->pPlayer->iTargetX = g_pState->pPlayer->iX;
g_pState->pPlayer->iTargetY = g_pState->pPlayer->iY;
g_pState->pPlayer->bIsMoving = false;
// Set initial position in sprite map
updateSpriteMap(-1, -1, g_pState->pPlayer->iX, g_pState->pPlayer->iY);
// 4. Viewport & Camera Initialization
// We set the Viewport first to define the visible window on the screen.
// The engine uses this rect to correctly clamp camera movements and calculate
// coordinate transformations (Screen -> World).
SDL_Rect rView = { iViewX, iViewY, iViewW, iViewH };
if(!g_pState->pScene->setViewport(&rView))
{
mLog.msg(LL_ERROR, "Failed to configure the Scene viewport.\n");
closeTutorial();
return -1;
}
// Position the camera (Layer 1) at the center of the map.
// Since all layers are added with a parallax of 1.0 (default), they stay synced.
if(g_pState->pScene->setLayerPosition(1, Position(PH_CENTER), Position(PH_CENTER)) < 0)
{
mLog.msg(LL_ERROR, "Failed to center the Scene camera.\n");
closeTutorial();
return -1;
}
// Initialize fog based on where the player is starting
updateFog(g_pState->pPlayer->iX, g_pState->pPlayer->iY);
mLog.msg(LL_INFO, "Initialization complete. Starting loop.\n");
mLog.msg(LL_INFO, "\nCONTROLS:\n [Right-Click] Move\n [Cursors] Scroll\n [F1] Toggle heightmap\n [F2] Toggle fog of war\n [Q/Esc] Quit\n\n");
// 5. Game loop
SDL_Event ev;
bool bDone = false;
bool bShowHeightMap = false;
bool bVisibleFog = true;
// Accumulator for smooth movement at fixed logic rate
float fMoveAccum = 0.0f;
while(!bDone)
{
while(mC64.update(&ev))
{
switch(ev.type)
{
case SDL_EVENT_QUIT:
bDone = true;
break;
case SDL_EVENT_KEY_DOWN:
if(ev.key.key == SDLK_ESCAPE || ev.key.key == SDLK_Q)
{
bDone = true;
}
else if(ev.key.key == SDLK_F1)
{
bShowHeightMap = !bShowHeightMap;
g_pState->pScene->setDebugOverlay(SDO_TILE_CELLGRID, bShowHeightMap);
g_pState->pScene->setLayerFeatures(2, SLF_RENDER, bShowHeightMap);
mLog.msg(LL_INFO, "Heightmap: %s\n", bShowHeightMap ? "ON" : "OFF");
}
else if(ev.key.key == SDLK_F2)
{
bVisibleFog = !bVisibleFog;
g_pState->pScene->setLayerFeatures(4, SLF_RENDER | SLF_UPDATE, bVisibleFog);
mLog.msg(LL_INFO, "Fog of War: %s\n", bVisibleFog ? "ON" : "OFF");
}
break;
}
}
if(bDone) break;
// Scroll layer 1 as all the others are linked (parallax 1.0)
float fSpeed = fScrollSpeed * fLogicStep;
float fDX = 0, fDY = 0;
if(mC64.getKeyState(SDLK_LEFT)) fDX = -fSpeed;
if(mC64.getKeyState(SDLK_RIGHT)) fDX = fSpeed;
if(mC64.getKeyState(SDLK_UP)) fDY = -fSpeed;
if(mC64.getKeyState(SDLK_DOWN)) fDY = fSpeed;
if(fDX != 0.0f || fDY != 0.0f)
{
float fX = 0.0f, fY = 0.0f;
if(g_pState->pScene->getLayerPosition(1, &fX, &fY) >= 0)
{
g_pState->pScene->setLayerPosition(1, Position(fX + fDX), Position(fY + fDY));
}
}
const Sint32 iMouseButtons = mC64.cursorMgr().getButtons();
const Sint32 iMouseButtonsPrev = mC64.cursorMgr().getButtonsPrev();
const bool bRightPressed =
((iMouseButtons & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) != 0) &&
((iMouseButtonsPrev & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) == 0);
if(bRightPressed) // Right-click edge handling
{
Sint32 iTX = 0;
Sint32 iTY = 0;
if(g_pState->pScene->mouseToCell(&iTX, &iTY, 1))
{
// Only log if target changes
if(iTX != g_pState->pPlayer->iTargetX || iTY != g_pState->pPlayer->iTargetY)
{
mLog.msg(LL_INFO, "Moving to grid cell: (%d, %d)\n", iTX, iTY);
}
// Pathfinding logic: trigger movement only if target changes
if(iTX != g_pState->pPlayer->iX || iTY != g_pState->pPlayer->iY) {
g_pState->pPlayer->vPath = findPath(g_pState->pPlayer->iX, g_pState->pPlayer->iY, iTX, iTY);
if(!g_pState->pPlayer->vPath.empty()) {
g_pState->pPlayer->iCurrentPathIndex = 0;
g_pState->pPlayer->bIsMoving = true;
}
}
}
}
// Process movement based on path or direct target
if(g_pState->pPlayer->bIsMoving)
{
// If we have a path, the immediate target is the first step
if(g_pState->pPlayer->iCurrentPathIndex < (int)g_pState->pPlayer->vPath.size())
{
g_pState->pPlayer->iTargetX = g_pState->pPlayer->vPath[g_pState->pPlayer->iCurrentPathIndex].x;
g_pState->pPlayer->iTargetY = g_pState->pPlayer->vPath[g_pState->pPlayer->iCurrentPathIndex].y;
}
fMoveAccum += fPlayerSpeed * fLogicStep; // pixels accumulated per logic tick
// Process movement step-by-step
while(fMoveAccum >= 1.0f)
{
// Check if current target is reached (offsets must be zero)
if(g_pState->pPlayer->iX == g_pState->pPlayer->iTargetX && g_pState->pPlayer->iY == g_pState->pPlayer->iTargetY && g_pState->pPlayer->fOffX == 0.0f && g_pState->pPlayer->fOffY == 0.0f)
{
// Reached intermediate step
if(g_pState->pPlayer->iCurrentPathIndex < (int)g_pState->pPlayer->vPath.size())
{
g_pState->pPlayer->iCurrentPathIndex++; // Advance to next node
if(g_pState->pPlayer->iCurrentPathIndex >= (int)g_pState->pPlayer->vPath.size())
{
g_pState->pPlayer->bIsMoving = false;
g_pState->pPlayer->vPath.clear();
g_pState->pPlayer->iCurrentPathIndex = 0;
fMoveAccum = 0.0f;
Sprite* pS = mC64.spriteMgr().get(g_pState->pPlayer->iIDSprite);
break;
}
else
{
// Set next target
g_pState->pPlayer->iTargetX = g_pState->pPlayer->vPath[g_pState->pPlayer->iCurrentPathIndex].x;
g_pState->pPlayer->iTargetY = g_pState->pPlayer->vPath[g_pState->pPlayer->iCurrentPathIndex].y;
}
}
else
{
g_pState->pPlayer->bIsMoving = false;
fMoveAccum = 0.0f;
break;
}
}
// Determine direction vector based on grid target
float fDiffX = (float)(g_pState->pPlayer->iTargetX - g_pState->pPlayer->iX);
float fDiffY = (float)(g_pState->pPlayer->iTargetY - g_pState->pPlayer->iY);
float fMoveX = 0.0f;
float fMoveY = 0.0f;
if(fDiffX > 0) fMoveX = 1.0f;
else if(fDiffX < 0) fMoveX = -1.0f;
else if(g_pState->pPlayer->fOffX > 0) fMoveX = -1.0f;
else if(g_pState->pPlayer->fOffX < 0) fMoveX = 1.0f;
if(fDiffY > 0) fMoveY = 1.0f;
else if(fDiffY < 0) fMoveY = -1.0f;
else if(g_pState->pPlayer->fOffY > 0) fMoveY = -1.0f;
else if(g_pState->pPlayer->fOffY < 0) fMoveY = 1.0f;
// Apply movement to offset
g_pState->pPlayer->fOffX += fMoveX;
g_pState->pPlayer->fOffY += fMoveY;
// Check thresholds to switch cells (cross half-tile boundary)
Sint32 iHalfTile = (g_pState->iTileSize / 2) + 1;
Sint32 iOldX = g_pState->pPlayer->iX;
Sint32 iOldY = g_pState->pPlayer->iY;
bool bCellChanged = false;
if(g_pState->pPlayer->fOffX >= iHalfTile)
{
g_pState->pPlayer->iX++;
g_pState->pPlayer->fOffX -= g_pState->iTileSize;
bCellChanged = true;
}
else if(g_pState->pPlayer->fOffX <= -iHalfTile)
{
g_pState->pPlayer->iX--;
g_pState->pPlayer->fOffX += g_pState->iTileSize;
bCellChanged = true;
}
if(g_pState->pPlayer->fOffY >= iHalfTile)
{
g_pState->pPlayer->iY++;
g_pState->pPlayer->fOffY -= g_pState->iTileSize;
bCellChanged = true;
}
else if(g_pState->pPlayer->fOffY <= -iHalfTile)
{
g_pState->pPlayer->iY--;
g_pState->pPlayer->fOffY += g_pState->iTileSize;
bCellChanged = true;
}
// Snap to center logic (prevent jitter at destination)
if(g_pState->pPlayer->iX == g_pState->pPlayer->iTargetX && g_pState->pPlayer->iY == g_pState->pPlayer->iTargetY)
{
if(abs(g_pState->pPlayer->fOffX) < 1.0f) g_pState->pPlayer->fOffX = 0.0f;
if(abs(g_pState->pPlayer->fOffY) < 1.0f) g_pState->pPlayer->fOffY = 0.0f;
}
if(bCellChanged)
{
updateSpriteMap(iOldX, iOldY, g_pState->pPlayer->iX, g_pState->pPlayer->iY);
updateFog(g_pState->pPlayer->iX, g_pState->pPlayer->iY);
}
// Animation update
Sint32 iNextState = SPR_STATE_NORMAL;
if(fMoveX > 0 && fMoveY == 0) iNextState = SPR_STATE_RIGHT;
else if(fMoveX < 0 && fMoveY == 0) iNextState = SPR_STATE_LEFT;
else if(fMoveX == 0 && fMoveY > 0) iNextState = SPR_STATE_DOWN;
else if(fMoveX == 0 && fMoveY < 0) iNextState = SPR_STATE_UP;
else if(fMoveX > 0 && fMoveY > 0) iNextState = SPR_STATE_DOWNRIGHT;
else if(fMoveX < 0 && fMoveY > 0) iNextState = SPR_STATE_DOWNLEFT;
else if(fMoveX > 0 && fMoveY < 0) iNextState = SPR_STATE_UPRIGHT;
else if(fMoveX < 0 && fMoveY < 0) iNextState = SPR_STATE_UPLEFT;
Sprite* pS = mC64.spriteMgr().get(g_pState->pPlayer->iIDSprite);
if(pS) pS->selectAnim(iNextState);
fMoveAccum -= 1.0f;
}
}
g_pState->pScene->update();
}
if(g_pState->pScene->save(OUTPUTDIR"wargame.tmx", "", "wargame.cdc", true) < 0)
{
mLog.msg(LL_ERROR, "Failed to save the War Game Scene.\n");
closeTutorial();
return -1;
}
mC64.sceneMgr().info();
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
Sint32 getButtonsPrev() const
Get the previous mouse buttons.
Definition GraphicsCursor.cpp:1053
Sint32 getButtons() const
Get the mouse buttons.
Definition GraphicsCursor.cpp:1047
Sint32 getHeight() const
Get the height of this font (in pixels).
Definition GraphicsFont.cpp:1025
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 getWidth(const string &sText, Sint32 iCurPos=-1) const
Get the width of the given string (in pixels).
Definition GraphicsFont.cpp:987
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
Image Object class.
Definition CRM64Pro.h:3356
SDL_Surface * getSurface()
Get the raw source surface of this image.
Definition GraphicsImage.cpp:1827
Sint32 assignSurface(SDL_Surface *pSurface, Sint32 iOwnership=0)
Assign a SDL_Surface to this image.
Definition GraphicsImage.cpp:1360
Sint32 render(Sint32 idRes=0, SDL_FRect *pSrc=nullptr, SDL_FRect *pDst=nullptr)
Render the image.
Definition GraphicsImage.cpp:1575
Image Manager class.
Definition CRM64Pro.h:3570
Image * get(Sint32 idImg)
Get a pointer to the image using its handle.
Definition GraphicsImage.cpp:2279
Sint32 loadFromFile(const string &sFile, const string &sName="")
Load an image from an external file (BMP, PNG, JPG, PCX, TGA or WebP formats).
Definition GraphicsImage.cpp:2434
Sint32 load(const string &sCDCFile, const string &sName)
Load an image stored in a CDC file.
Definition GraphicsImage.cpp:2476
Sint32 create(const string &sName, Uint32 iVersion=10) override
Creates a new image.
Definition GraphicsImage.cpp:2136
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
TileLayer class for tile-based map layers.
Definition CRM64Pro.h:10368
Uint32 ** iCellMap
Direct mutable 2D cell map.
Definition CRM64Pro.h:10571
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
Sprite Object class.
Definition CRM64Pro.h:4262
Tile Object class.
Definition CRM64Pro.h:3998
Tile Manager class.
Definition CRM64Pro.h:4118
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 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_CRITICAL
Fatal errors, application crash, or assertions.
Definition CRM64Pro.h:761
@ LL_ERROR
Runtime errors that do not require immediate termination.
Definition CRM64Pro.h:762
TileMgr & tileMgr()
Get reference to Tile Manager.
Definition CoreMain.cpp:766
SpriteMgr & spriteMgr()
Get reference to Sprite Manager.
Definition CoreMain.cpp:772
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
CursorMgr & cursorMgr()
Get reference to Cursor Manager.
Definition CoreMain.cpp:760
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
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
@ PH_CENTER
Cursor::setHotSpot(): set hotspot to cursor_size/2. Sprite::setAnimHotSpot(): set hotspot to frame_si...
Definition CRM64Pro.h:336
@ SLF_RENDER
Enable the rendering feature. Enabling this flag also enables SLF_UPDATE. Disabling render does not d...
Definition CRM64Pro.h:10079
@ SLF_UPDATE
Enable the updating feature. Check update() for further details.
Definition CRM64Pro.h:10078
@ SDO_TILE_CELLGRID
Draw tile cell grid overlays for rendered tile layers.
Definition CRM64Pro.h:10097
@ SLT_TILE
Layer contains tile data (SceneLayerTile).
Definition CRM64Pro.h:10151
float getTotalHeight() const override
Get layer total height in pixels.
Definition SceneLayerTile.cpp:112
bool setCellHeight(Sint32 iSize)
Set cell height.
Definition SceneLayerTile.cpp:126
float getTotalWidth() const override
Get layer total width in pixels.
Definition SceneLayerTile.cpp:106
Sint32 getHeight() const
Get map height.
Definition SceneLayerTile.cpp:101
bool setCellWidth(Sint32 iSize)
Set cell width.
Definition SceneLayerTile.cpp:118
Sint32 getCellWidth() const
Get cell width.
Definition SceneLayerTile.cpp:92
bool setCellValue(Sint32 iY, Sint32 iX, Uint32 iValue)
Set arbitrary values on 2D array of cells.
Definition SceneLayerTile.cpp:160
Sint32 getWidth() const
Get map width.
Definition SceneLayerTile.cpp:98
Sint32 close(Sint32 idScene) override
Close and destroy a Scene.
Definition Scene.cpp:2298
bool info(Sint32 iMode=0) override
Request Scene Manager information.
Definition Scene.cpp:2215
Sint32 create(const string &sName, Uint32 iNumLayers=3) override
Create a new Scene.
Definition Scene.cpp:2256
Scene * get(Sint32 idScene)
Get a pointer to the Scene using its handle.
Definition Scene.cpp:2355
#define SPR_STATE_DOWNRIGHT
Definition CRM64Pro.h:4254
Sint32 assignImage(Sint32 idImage, Sint32 iOwnership=0)
Assign an Image to this sprite.
Definition GraphicsSprite.cpp:494
#define SPR_STATE_UPRIGHT
Definition CRM64Pro.h:4250
Sint32 render(Sint32 idRes=0)
Render the sprite.
Definition GraphicsSprite.cpp:1395
#define SPR_STATE_RIGHT
Definition CRM64Pro.h:4246
bool setAnimStateIndex(Sint32 iIndex, Uint16 iState)
Set the animation state by index.
Definition GraphicsSprite.cpp:927
bool selectAnim(Uint16 iState)
Select an animation by state.
Definition GraphicsSprite.cpp:794
#define SPR_STATE_UPLEFT
Definition CRM64Pro.h:4248
#define SPR_STATE_NORMAL
Definition CRM64Pro.h:4238
#define SPR_STATE_DOWN
Definition CRM64Pro.h:4242
bool setPosition(float fX, float fY, bool bInterpolate=false)
Set the sprite position.
Definition GraphicsSprite.cpp:1356
#define SPR_STATE_DOWNLEFT
Definition CRM64Pro.h:4252
Sint32 create(const string &sName, Uint32 iVersion=10) override
Create a new sprite.
Definition GraphicsSprite.cpp:1691
bool setOffset(Sint32 iX, Sint32 iY)
Set the sprite offsets for creating the animations and frames.
Definition GraphicsSprite.cpp:640
#define SPR_STATE_UP
Definition CRM64Pro.h:4240
Sprite * get(Sint32 idSprite)
Get a pointer to the sprite using its handle.
Definition GraphicsSprite.cpp:1843
#define SPR_STATE_LEFT
Definition CRM64Pro.h:4244
bool setFrameTimeIndex(Sint32 iIndex, Sint32 iFrame, Uint16 iTime)
Set the frame time duration by index.
Definition GraphicsSprite.cpp:1284
bool setAnimHotSpotIndex(Sint32 iIndex, Sint32 iHotX, Sint32 iHotY)
Set the animation hotspot by index.
Definition GraphicsSprite.cpp:1046
Sint32 close(Sint32 idSprite) override
Close and destroy a sprite.
Definition GraphicsSprite.cpp:1739
Tile * get(Sint32 idTile)
Get a pointer to the tile using its handle.
Definition GraphicsTile.cpp:850
bool setOffset(Sint32 iTSMode, Sint32 iX, Sint32 iY)
Set tileset mode and offset.
Definition GraphicsTile.cpp:351
Sint32 close(Sint32 idTile) override
Close and destroy a tile.
Definition GraphicsTile.cpp:785
Sint32 create(const string &sName, Uint32 iVersion=10) override
Create a new tile.
Definition GraphicsTile.cpp:744
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
Layer Context structure.
Definition CRM64Pro.h:10223
float fScrollY
Current Logic/Render Y scroll position.
Definition CRM64Pro.h:10228
SDL_Rect rViewport
The effective viewport used for this frame.
Definition CRM64Pro.h:10226
SDL_FPoint worldToScreen(float fWorldX, float fWorldY, bool bQuantize=true) const
Convert world coordinates to screen coordinates using the canonical render camera.
Definition CRM64Pro.h:10273
float fScrollX
Current Logic/Render X scroll position.
Definition CRM64Pro.h:10227

Navigation

Previous: Tutorial 16.4 - Scene Shooter

Back to the tutorials index

Next: Tutorial 16.6 - Scene Lighting