RPG Creation 101

This article was written by Fling-master

Unlike most RPG tutorials I'm gonna skip the long part of creating a story, and characters, etc. Now just because I'm skipping that doesn't mean it's not important! Before going in to create an RPG you should sit down and think about that, maybe jot down a few ideas you have. Next comes planning the engine for your RPG. Here are some things you should take into account:

These are just some ideas to think about. And hopefully this series will cover them all! (Though don't expect it to show you how to setup every thing mentioned above heheh.) This month I'll show you how to create a pixel-by-pixel scrolling engine, which'll use two map layers which can give you some neat effects, such as bridges, etc. We'll also touch on some map format things. Two things I'm going to leave for you to decide is the method you use to load tiles/tilesets, and the palette. These things are both quite programmer specific and is not really that much of a trouble to set up.

So lets get into making an engine which is basically the shell for the RPG. This engine which I'm going to help you build is by no means carved in stone. Feel free to change things around and modify it to your needs. You might even know of a few ways to speed it up/enchance it in some way! (If you do then e-mail me and I'll put it in a future article.) Also the graphics routines used are not actually real. They are just put in so you know what it should be. The graphics routines should be in assembly. Maybe sometime in the future I'll tell you how to make some custom routines.

So the basic subs we'll want for now is:

'$DYNAMIC
DEFINT A-Z

DECLARE SUB DrawMap ()			'Draws our map
DECLARE SUB LoadMap (FileName$)       	'Loads the map and processes it
DECLARE SUB MoveUp () 			'These next subs will handle moving up, down, left, and right
DECLARE SUB MoveDown ()
DECLARE SUB MoveLeft ()
DECLARE SUB MoveRight ()

Those subs are the ones we'll use for this part of the series. We'll add on to them eventually. So next we need to set up our data types and global variables:

TYPE EngineType				 'This'll hold our players info and some other things
 x               AS INTEGER                    'Player X position
 y               AS INTEGER                    'Player Y ...
 MaxX            AS INTEGER                    'Maximum X position in map
 MaxY            AS INTEGER                    'Maximum Y ...
 Direction       AS INTEGER                    'Current direction
 Animate         AS INTEGER                    'Animation frame tracker
 Action          AS INTEGER                    'Our action tracker
 MaxNPCs         AS INTEGER                    'Maximum amount of NPCs
END TYPE

TYPE MapType				       'This'll hold the map data.
 tile            AS INTEGER                    'Tile number in map
 tile2           AS INTEGER                    'Tile number for layer 2
 Collision       AS INTEGER                    'Collision data in map
END TYPE

CONST South = 1, North = 2, West = 3, East = 4 'Constant values for directions
CONST True = -1, False = 0                     'True & false (duh!)
DIM SHARED Engine AS EngineType                'Main engine variable
DIM SHARED map(0 TO 63, 0 TO 63) AS MapType    'Map data.

'Here we're gonna dimension our tileset arrays and our offscreen video buffer.
DIM SHARED tilearray(1, 1), PlayerGFX(1, 1), buffer(31999)

These are the data structures we'll be using for right now. Notice we're gonna use a 64x64 map. If we were using XMS to store the map data we could have a much larger map. Some things like Engine.MaxNPCs we'll leave for now. So now we need to load in the tiles, palettes, maps, and setup the screen mode to SCREEN 13:

'320x200x256 resolution
SCREEN 13
DEF SEG = &HA000                      'Video segment is default segment

LoadPalette "yourpal.pal"             'Load our palette
LoadTiles "tiles.dat", tilearray()    'Load the tiles
LoadTiles "npcs.dat", PlayerGFX()     'Load the NPCs
LoadMap "yourmap.map"                 'Load the map

The routines to load the palette and tiles are not real, they are just shown so you know where to put the call to your routines. You will notice that when I load the tiles it is actually loading a tileset. So now we're ready to draw our first view and let the player move around the map. How are we gonna do that? Well we'll create a loop that'll contiually check if a key is being pressed and we'll update the screen no matter if a key is being pressed or not. This allows us to check the FPS and it'll be easier when we implement NPC's and other things like animations. So here's out main loop:

DrawMap						'Draw the map
VideoCopy VARSEG(buffer(0)), VARPTR(buffer(0))  'Copy it to video memory

DO						'Start the loop
 k = INP(&H60)					'Read keyboard status. Faster than INKEY$
 SELECT CASE k					'Now we're gonna find out what key was pressed
  CASE 72					'The up arrow key
	MoveUp					'Move player up
	Engine.Action = True
  CASE 80					'The down arrow key
	MoveDown				'Move player down
	Engine.Action = True
  CASE 75					'The left arrow key
	MoveLeft				'Move player left
	Engine.Action = True
  CASE 77					'The right arrow key
	MoveRight				'Move player right
	Engine.Action = True
  CASE 1					'The ESC key
	END					'Stop the program
 END SELECT
 IF Engine.Action = True THEN			'If the user moved we'll call the script interpreter
  Engine.Action = False				'The script interpreter will be put in later =)
 END IF

 DrawMap					'Update the screen
 RPGVideoCopy VARSEG(buffer(0)), VARPTR(buffer(0))

 fps = fps + 1					'Update FPS counter
 IF starttime& + 1 < TIMER THEN			'If one second has elapsed then we'll show the FPS
  fps2 = fps
  fps = 0					'Set it back to 0
  starttime& = TIMER
 END IF
 LOCATE 1, 1: PRINT fps2			'Display the frame rate
LOOP

Notice that we have put in a FPS counter. This should be removed when we actually make a game. But we'll leave it in for now. I think that was pretty self-explanatory especially with the comments. The code itself is not to hard too understand I think.

Now then, we want to add the MoveXXXX routines to let the player stretch his legs a bit right? Now this is pretty simple in itself. I think we'll make a pixel-by-pixel scroller with Zelda-style free movement! This is actually pretty easy to do excluding the collision detection. Our collision detection is gonna be "map-grid-perfect" which will make it hard to walk through areas with small openings all around. This is easily fixed once we have a multiple keypress handler. So I'm gonna show you the MoveXXXX routines now. Read the comments if you don't understand it:

SUB MoveDown
Engine.Direction = South			'We're moving south.

'What we want to do is get the collision status of two parts of the tile below us. Since our engine deals
'with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT(Engine.x \ 16), INT((Engine.y + 16) \ 16)).Collision
tile2 = map(INT((Engine.x + 15) \ 16), INT((Engine.y + 16) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.y = Engine.y + 1			'Set to something different for faster movement
END IF
END SUB

SUB MoveLeft
Engine.Direction = West				'We're moving west

'What we want to do is get the collision status of two parts of the tile to the west of us. Since our engine
'deals with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT((Engine.x - 1) \ 16), INT((Engine.y) \ 16)).Collision
tile2 = map(INT((Engine.x - 1) \ 16), INT((Engine.y + 15) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.x = Engine.x - 1			'Set to something different for faster movement
END IF
END SUB

SUB MoveRight
Engine.Direction = East

'What we want to do is get the collision status of two parts of the tile to the east of us. Since our engine
'deals with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT((Engine.x + 16) \ 16), INT((Engine.y) \ 16)).Collision
tile2 = map(INT((Engine.x + 16) \ 16), INT((Engine.y + 15) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.x = Engine.x + 1			'Set to something different for faster movement
END IF
END SUB

SUB MoveUp
Engine.Direction = North

'What we want to do is get the collision status of two parts of the tile to above us. Since our engine
'deals with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT(Engine.x \ 16), INT((Engine.y - 1) \ 16)).Collision
tile2 = map(INT((Engine.x + 15) \ 16), INT((Engine.y - 1) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.y = Engine.y - 1			'Set to something different for faster movement
END IF
END SUB

Hopefully that isn't to hard to understand. =) Read the comments, they explain it all. Now for the drawing of the map screen. We're gonna have a camera in our RPG which can also be used for cutscenes. Here's the code:

SUB DrawMap
CameraX = Engine.x - 160          'Get the upper left map position on the
CameraY = Engine.y - 96           'the screen.
xtile = INT(CameraX \ 16)         'The the actual map gird position.
ytile = INT(CameraY \ 16)
IF CameraX < 0 THEN CameraX = 0   'If the Camera pos is less then 0 make it 0
IF CameraY < 0 THEN CameraY = 0
'If the Camera position is greater then the screen boundaries make it equal
'the screen boundaries
IF CameraX > Engine.MaxX - 320 THEN CameraX = Engine.MaxX - 320
IF CameraY > Engine.MaxY - 200 THEN CameraY = Engine.MaxY - 200

FOR x = 0 TO 21                             '22 Columns
 FOR y = 0 TO 13                           '14 Rows
  tile = map(x + xtile, y + ytile).tile    'Get layer 1 tile from map buffer
  tile2 = map(x + xtile, y + ytile).tile2  'layer 2...
  Xpos = CameraX MOD 16                    'Get offset into the screen to
  Ypos = CameraY MOD 16                    'draw at.

  'Now draw layer 1 which is not drawn with transparency
  PutSolid VARSEG(buffer(0)), (x * 16) - Xpos, (y * 16) - Ypos, tilearray(0, tile)

  'Now we're gonna draw the second layer
  IF tile2 <> BlankTileNum THEN      'We don't want to draw a blank tile
	'If the tile isn't blank then draw the second layer tile with transparency
	Put VARSEG(buffer(0)), (x * 16) - Xpos, (y * 16) - Ypos, tilearray(0, tile2)
  END IF
 NEXT y
NEXT x

'Here is where you draw the NPC's including the player.
Xpos = Engine.x - CameraX                     'Now get the player's coords
Ypos = Engine.y - CameraY                     'to draw at on screen
PlayerPic = ((Engine.Direction * 2) - 1)      'Get frame to draw player at
Put VARSEG(buffer(0)), Xpos, Ypos, PlayerGFX(0, PlayerPic)  'Draw it
END SUB

Well that will draw two layers onto the off screen buffer. The routine which copies the buffer to video memory should be an asm routine. For right now to see it work you can do this: change all the VARSEG(buffer(0)) to &HA000, then it'll draw the sprites to video memory. But also you'll notice the put routine used is also from a library! Well, I didn't want to get too library specific so I made up some hypothetical graphics routines. Use DirectQB or Future.Library or something for now...

Lastly I'll show you a map loading routine I use. The map data is stored like this:

Tileset file
Palette file
Starting position
Maximum columns
Maximum rows
Map data (layer 1 tile number, layer 2 tile number, collision number)

It's pretty simple. Throughout this series we'll modify this to accomodate NPC's and other things. Here's my map loading routine:

SUB LoadMap (FileName$)
IF INSTR(FileName$, ".") = 0 THEN     'Add a .MAP extension if there
 FileName$ = FileName$ + ".MAP"       'isn't one
END IF
OPEN UCASE$(FileName$) FOR INPUT AS #1   'Open it
INPUT #1, ImageFile$                     'Read tileset file
INPUT #1, PalName$                       'Palette...
INPUT #1, StartX, StartY                 'Starting coords
INPUT #1, Engine.MaxX, Engine.MaxY       'Map size

'Set maximum row and column values to 1 less, since the map buffer starts at 0 not 1
Engine.MaxY = Engine.MaxY - 1
Engine.MaxX = Engine.MaxX - 1

'Load map data into 2 layers
FOR i = 0 TO Engine.MaxY
 FOR J = 0 TO Engine.MaxX
  INPUT #1, map(J, i).tile, map(J, i).tile2, map(J, i).Collision
 NEXT J
NEXT i

'Calculate the map size in pixels.
Engine.MaxX = Engine.MaxX * 16
Engine.MaxY = Engine.MaxY * 16
CLOSE #1

END SUB

The map data could also have been stored in binary too, but regardless of format type, it works. Notice that you don't have to specify an extension on the filename if it ends in .map =) Well that's it for this part of the tutorial and I'd have to say it's pretty big! Next time I'll cover stuff like NPC's and animation. I also might include a demo program. If you don't understand something here then please e-mail me (address is at the top).

All site content is Copyright 2001, HyperRealistic Games. This excludes content submitted by other people.