Is there any particular reason? Implementing a simple, smooth scrolling tile engine is relatively straightforward:
Code: Select all
#include once "fbgfx.bi"
type as ulong color_t
enum MapTerrains
TERRAIN_WATER
TERRAIN_LAND
TERRAIN_WOODS
TERRAIN_MOUNTAINS
end enum
private function max overload( a as integer, b as integer ) as integer
return( iif( a > b, a, b ) )
end function
private function min overload( a as integer, b as integer ) as integer
return( iif( a < b, a, b ) )
end function
private function fClamp( v as single, a as single, b as single ) as single
return( iif( a > v, a, iif( v < b, v, b ) ) )
end function
private function noise2( x as integer, y as integer ) as single
dim as integer _
n = x + y * 57
n = ( n shl 13 ) xor n
return( ( 1.0f - ( ( n * ( n * n * 15731 + 789221 ) + _
1376312589 ) and &h7fffffff ) / 1073741824.0f ) )
end function
private function remap overload( v as single ) as single
return( fClamp( v * 0.5f + 0.5f, 0.0f, 1.0f ) )
end function
private function remap( _
x as single, start1 as single, end1 as single, start2 as single, end2 as single ) as single
return( ( x - start1 ) * ( end2 - start2 ) / ( end1 - start1 ) + start2 )
end function
private function smoothedNoise( x as single, y as single ) as single
dim as single _
fractX = x - int( x ), _
fractY = y - int( y )
dim as integer _
x1 = int( x ), _
y1 = int( y ), _
x2 = int( x - 1.0f ), _
y2 = int( y - 1.0f )
return( _
fractX * fractY * noise2( x1, y1 ) + _
fractX * ( 1.0f - fractY ) * noise2( x1, y2 ) + _
( 1.0f - fractX ) * fractY * noise2( x2, y1 ) + _
( 1.0f - fractX ) * ( 1.0f - fractY ) * noise2( x2, y2 ) )
end function
private function turbulence( x as single, y as single, size as single ) as single
dim as single _
value = 0.0f, _
initialSize = size
do while( size >= 1.0f )
value += smoothedNoise( x / size, y / size ) * size
size *= 0.5f
loop
value /= initialSize
return( fClamp( value, -1.0, 1.0 ) )
end function
type Vec2
declare constructor()
declare constructor( as single, as single )
as single x, y
end type
constructor Vec2() : end constructor
constructor Vec2( nX as single, ny as single )
x = nX : y = nY
end constructor
type Region
declare constructor()
declare constructor( _
as single, as single, as single, as single )
declare destructor()
declare function centerAt( as single, as single ) byref as Region
declare function outsideAmount( byref as Region ) as Vec2
as single x, y, w, h
end type
constructor Region() : end constructor
constructor Region( aX as single, aY as single, aW as single, aH as single )
x = aX : y = aY
w = aW : h = aH
end constructor
destructor Region() : end destructor
function Region.centerAt( aX as single, aY as single ) byref as Region
dim as single _
hw = w * 0.5f, hh = h * 0.5f
x = aX - hw : y = aY - hh
return( this )
end function
private function Region.outsideAmount( byref r as Region ) as Vec2
return( Vec2( _
iif( x < r.x, r.x - x, iif( x + w - 1 > r.x + r.w - 1, _
( r.x + r.w - 1 ) - ( x + w - 1 ), 0 ) ), _
iif( y < r.y, r.y - y, iif( y + h - 1 > r.y + r.h - 1, _
( r.y + r.h - 1 ) - ( y + h - 1 ), 0 ) ) ) )
end function
type MapCell
as long terrain
end type
type Map
declare constructor()
declare constructor( as long, as long )
declare destructor()
declare function getRegionFor( as long ) as Region
as MapCell cell( any, any )
as long w, h
end type
constructor Map()
constructor( 128, 128 )
end constructor
constructor Map( aW as long, aH as long )
w = iif( aW < 1, 1, aW )
h = iif( aH < 1, 1, aH )
redim cell( 0 to w - 1, 0 to h - 1 )
end constructor
destructor Map() : end destructor
'' Gets a region that describes the map size in pixels
private function Map.getRegionFor( tileSize as long ) as Region
return( Region( 0, 0, w * tileSize, h * tileSize ) )
end function
type Resolution
as long w, h
end type
type GameAssets
as long ptr terrains
as long terrainCount
as Fb.Image ptr _miniMap
as long tileSize
end type
type GameCamera
as Region reg
as single speed
end type
type Unit
as Vec2 pos
as long vision
end type
type PlayerState
as Unit units( any )
as long unitCount
as GameCamera cam
as color_t color
end type
type GameState
as GameAssets ptr assets
as Resolution ptr _res
as Map ptr _map
end type
'' Creates a random map
sub createMap_noise( _
byref gs as GameState, threshold as single = 0.3f, seed as ulong = 0 )
for y as integer = 0 to gs._map->h - 1
for x as integer = 0 to gs._map->w - 1
dim as single t = remap( turbulence( x + seed, y - seed, 64 ) )
with gs._map->cell( x, y )
if( t > threshold ) then
.terrain = gs.assets->terrains[ int( remap( _
t, threshold, 1.0f, 1, gs.assets->terrainCount ) ) ]
else
.terrain = gs.assets->terrains[ 0 ]
end if
end with
next
next
end sub
function init( w as integer, h as integer ) as Resolution
screenRes( w, h, 32, 2, , Fb.GFX_ALPHA_PRIMITIVES )
screenSet( 0, 1 )
return( type <Resolution>( w, h ) )
end function
'' Gets which tile the render function has to draw. In a real implementation,
'' you'll look up which tile corresponds to the specified cell coordinates
'' and return that. Here, I just return a different color for each terrain.
function getTile( byref m as Map, x as long, y as long ) as color_t
select case as const( m.cell( x, y ).terrain )
case TERRAIN_WATER
return( rgb( 128, 128, 255 ) )
case TERRAIN_LAND
return( rgb( 0, 164, 0 ) )
case TERRAIN_WOODS
return( rgb( 0, 128, 0 ) )
case TERRAIN_MOUNTAINS
return( rgb( 214, 214, 214 ) )
end select
'' For 'tile not found'; it should not happen
return( rgb( 255, 0, 255 ) )
end function
'' Updates the minimap with the map contents. In a real implementation,
'' you wouldn't be using 'pset' since it's slow.
sub updateMinimap( mm as Fb.Image ptr, byref m as Map )
for y as integer = 0 to m.h - 1
for x as integer = 0 to m.w - 1
pset mm, ( x, y ), getTile( m, x, y )
next
next
end sub
/'
Main update function. Make it return false if you want to end the
outer loop.
'/
function update( _
dt as double, byref gs as GameState, byref cam as GameCamera ) as boolean
dim as string k = lcase( inkey() )
with cam.reg
'' Move the camera
if( multiKey( Fb.SC_UP ) ) then
.y -= cam.speed * dt
end if
if( multiKey( Fb.SC_DOWN ) ) then
.y += cam.speed * dt
end if
if( multiKey( Fb.SC_LEFT ) ) then
.x -= cam.speed * dt
end if
if( multiKey( Fb.SC_RIGHT ) ) then
.x += cam.speed * dt
end if
'' Keep the camera on bounds
with cam.reg.outsideAmount( gs._map->getRegionFor( gs.assets->tileSize ) )
cam.reg.x += .x
cam.reg.y += .y
end with
end with
'' End by pressing the 'ESC' key
if( multiKey( Fb.SC_ESCAPE ) ) then
return( true )
end if
'' Generates another map
if( k = "r" ) then
createMap_noise( gs, 0.3f, rnd() * 12345 )
updateMinimap( gs.assets->_miniMap, *gs._map )
end if
return( false )
end function
'' Renders the minimap at the specified coordinates
sub renderMinimap( _
gs as GameState, byref cam as GameCamera, x as long, y as long )
put( x, y ), gs.assets->_miniMap, alpha, 128
'' Map width and height, in pixels
dim as long _
mw = gs._map->w * gs.assets->tileSize, _
mh = gs._map->h * gs.assets->tileSize
'' Compute the corners of the view rectangle
with cam.reg
dim as long _
x1 = remap( .x, 0, mw, 0, gs.assets->_miniMap->width ), _
y1 = remap( .y, 0, mh, 0, gs.assets->_miniMap->height ), _
x2 = remap( .x + .w, 0, mw, 0, gs.assets->_miniMap->width ), _
y2 = remap( .y + .h, 0, mh, 0, gs.assets->_miniMap->height )
'' And render it over the minimap
line( x + x1, y + y1 ) - ( x + x2, y + y2 ), rgb( 255, 0, 0 ), b
end with
end sub
/'
Main render function
'/
sub render overload( _
dt as double, byref gs as GameState, byref cam as GameCamera )
cls()
'' Shorthand for the tile size
dim as long ts = gs.assets->tileSize
'' Compute how many rows and columns we need to display,
'' based on the tile size and the camera extents.
dim as long _
cols = ( cam.reg.w \ ts ) + abs( cam.reg.w mod ts <> 0 ), _
rows = ( cam.reg.h \ ts ) + abs( cam.reg.h mod ts <> 0 )
'' Compute the tile (in map cell coordinates) for the
'' upper left corner of the view. This will be the
'' starting tile from where we need to start drawing.
dim as long _
startX = cam.reg.x \ ts, startY = cam.reg.y \ ts
'' Compute how many pixels we need to offset the starting
'' tile for smooth scrolling.
dim as long _
offsetX = cam.reg.x mod ts, offsetY = cam.reg.y mod ts
'' Then we render. Careful not to try and access a tile outside
'' the map boundaries.
with *gs._map
for y as integer = 0 to min( rows, .h - startY - 1 )
for x as integer = 0 to min( cols, .w - startX - 1 )
'' Select each tile appropriately. Here I just assign a color to
'' each terrain index.
dim as color_t clr = getTile( *gs._map, startX + x, startY + y )
'' Tile position, in screen coordinates
dim as long _
tx = x * ts, ty = y * ts
'' Render the 'tile'
line( tx - offsetX, ty - offsetY ) - _
( tx - offsetX + ts - 1, ty - offsetY + ts - 1 ), clr, bf
next
next
end with
renderMinimap( gs, cam, 10, gs._res->h - gs.assets->_miniMap->height - 10 )
sleep( 1, 1 )
flip()
end sub
/'
Main code
'/
var res = init( 800, 600 )
'' Four types of terrain
dim as long terrains( ... ) = { _
TERRAIN_WATER, TERRAIN_LAND, TERRAIN_WOODS, TERRAIN_MOUNTAINS }
randomize()
'' Create a 256x256 map
var m = Map( 256, 256 )
var miniMap = imageCreate( m.w, m.h )
'' Create and initialize the global state and assets. Global is to be
'' understood here as 'state common to all functions', not *shared* state.
dim as GameState gs
'' Create assets
dim assets as GameAssets
with assets
.terrains = @terrains( 0 )
.terrainCount = ubound( terrains ) + 1
._miniMap = minimap
.tileSize = 32
end with
with gs
._res = @res
._map = @m
.assets = @assets
end with
createMap_noise( gs, 0.3f, rnd() * 12345 )
updateMinimap( miniMap, m )
'' The camera will be centered in the middle of the map at startup
dim as GameCamera cam
with cam
.reg = Region( 0, 0, res.w, res.h ).centerAt( _
( m.w * gs.assets->tileSize ) / 2, ( m.h * gs.assets->tileSize ) / 2 )
.speed = 500.0f
end with
dim as boolean finished = false
dim as double dt
do
finished = update( dt, gs, cam )
dt = timer()
render( dt, gs, cam )
dt = timer() - dt
loop until( finished )
imageDestroy( miniMap )
The code generates a random map and then lets you navigate it with the arrow keys. If you press 'R', a new map will be generated. The scroll is a bit slow, but that's kind of the point so you see how you can implement a tile based, smooth scrolling engine...