Issue #2
March 10th, 2001

Welcome to part two of my series! Last issue we went over how to create the basic engine, which of course, included such things as movement, collision detectiom, and drawing the map. This time we'll delve deeper into the world of NPCs. We'll create the beginnings of an object system, that will allow us to incorporate enemies with very small modification. And it will allow us to make the player shoot with also very small modification. So before I get too ahead of myself, let's begin!

Before we start coding the object handler to move our NPCs we need to plan out the data structure to hold the required information. What will we need to include in this? Well, you should definitly include things like the NPCs x and y position, the direction its moving, and how much distance it has left to move before it should start moving again. Another thing which'll come in handy later is whether it's active or not. This allows us to 'kill' NPCs. These are just some of the things to include. You might even go so far as to track separate variables to see what items the NPC has so you could trade with them. Something I don't like doing is keeping the text the NPC is going to say in the data structure. This more limits the scripting capabilities of your RPG. But of course we'll get into that later.

So here's out basic data structure:

TYPE NPCType                  'Holds NPC data
 x       AS INTEGER           'X coord
 y       AS INTEGER           'Y coord
 Dir     AS INTEGER           'The direction
 Moving  AS INTEGER           'If its moving and the amount left
 Active  AS INTEGER           'Is it active?
END TYPE
DIM SHARED NPC(50) AS NPCType

I think that's pretty self-explanatory. Add that into the engine we made last month after the Engine Type declaration.

Now then, we need to write a sub to update the NPCs since we don't want 'em to just stand there! We want them to move, run, etc. We're going to implement a random movement system which will check for collision with walls and other NPCs. This is probably the simplest form of handling NPCs. A more advanced one would maybe make them able to activate scripts or make some run around in circles, etc. Here's the subroutine:

SUB UpdateNPCs
FOR i = 0 TO Engine.MaxNPCs            'Update all the NPCs
 IF NPC(i).Moving = 0 THEN             'Is it moving?
  IF RND * 50 = 5 THEN                 'Find out if it "wants" to move
	NPC(i).Moving = 16             'Set Moving to 16 (one tile width)
  ELSE
	GOTO StartIt		       'Goto the next NPC
  END IF
  NPC(i).Dir = RND * 4       'Pick a direction
  SELECT CASE NPC(i).Dir
	CASE North                          'Moving north
	 'Do collision. We don't want 'em moving into walls and such =)
	 tile = map(NPC(i).x \ 16, (NPC(i).y - 1) \ 16).Collision
	 tile2 = map((NPC(i).x + 15) \ 16), (NPC(i).y - 1) \ 16).Collision
	 IF tile <> 2 AND tile2 <> 2 THEN	'Is it passible?
	 ELSE
	  NPC(i).Moving = 0 'If the tile is unwalkable the NPC stops
	 END IF
	 FOR sw = 0 TO Engine.MaxNPCs   'We also don't want 'em walking onto NPCs
	  IF NPC(i).x \ 16 = NPC(sw).x \ 16 AND (NPC(i).y \ 16) - 1 = NPC(sw).y \ 16 THEN
		NPC(i).Moving = 0	'If there's a NPC stop.
	  END IF
	 NEXT sw
	CASE South                         'Moving South
	 'Do collision. We don't want 'em moving into walls and such =)
	 tile = map(NPC(i).x \ 16, (NPC(i).y + 16) \ 16).Collision
	 tile2 = map((NPC(i).x + 15) \ 16, (NPC(i).y + 16) \ 16).Collision
	 IF tile <> 2 AND tile2 <> 2 THEN	'Is it passible?
	 ELSE
	  NPC(i).Moving = 0 'If the tile is unwalkable the NPC stops
	 END IF
	 FOR sw = 0 TO Engine.MaxNPCs   'We also don't want 'em walking onto NPCs
	  IF NPC(i).x \ 16 = NPC(sw).x \ 16 AND (NPC(i).y \ 16) + 1 = NPC(sw).y \ 16 THEN
		NPC(i).Moving = 0	'If there's a NPC stop.
	  END IF
	 NEXT sw
	CASE East                          'Moving East
	 'Do collision. We don't want 'em moving into walls and such =)
	 tile = map((NPC(i).x + 16) \ 16, NPC(i).y \ 16).Collision
	 tile2 = map((NPC(i).x + 16) \ 16, (NPC(i).y + 15) \ 16).Collision
	 IF tile <> 2 AND tile2 <> 2 THEN	'Is it passible?
	 ELSE
	  NPC(i).Moving = 0 'If the tile is unwalkable the NPC stops
	 END IF
	 FOR sw = 0 TO Engine.MaxNPCs   'We also don't want 'em walking onto NPCs
	  IF (NPC(i).x \ 16) + 1 = NPC(sw).x \ 16 AND NPC(i).y \ 16 = NPC(sw).y \ 16 THEN
		NPC(i).Moving = 0	'If there's a NPC stop.
	  END IF
	 NEXT sw
	CASE West                          'Moving West
	 'Do collision. We don't want 'em moving into walls and such =)
	 tile = map((NPC(i).x - 1) \ 16, NPC(i).y \ 16).Collision
	 tile2 = map((NPC(i).x - 1) \ 16, (NPC(i).y + 15) \ 16).Collision
	 IF tile <> 2 AND tile2 <> 2 THEN	'Is it passible?
	 ELSE
	  NPC(i).Moving = 0 'If the tile is unwalkable the NPC stops
	 END IF
	 FOR sw = 0 TO Engine.MaxNPCs   'We also don't want 'em walking onto NPCs
	  IF (NPC(i).x \ 16) - 1 = NPC(sw).x \ 16 AND NPC(i).y \ 16 = NPC(sw).y \ 16 THEN
		NPC(i).Moving = 0	'If there's a NPC stop.
	  END IF
	 NEXT sw
  END SELECT
 END IF

 'If the NPC is moving then update the current position its at.
 IF NPC(i).Moving > 0 THEN
  NPC(i).Moving = NPC(i).Moving - 1
  SELECT CASE NPC(i).Dir
	CASE North
	 NPC(i).y = NPC(i).y - 1
	CASE South
	 NPC(i).y = NPC(i).y + 1
	CASE East
	 NPC(i).x = NPC(i).x + 1
	CASE West
	 NPC(i).x = NPC(i).x - 1
  END SELECT
 END IF
StartIt:			'This is where we go to goto the next NPC.
NEXT i
END SUB

Hopefully you understand that! =) What it does is check if the current NPC is moving, if not then find out if it "wants" to move by picking a random number. If it does "want" to then we pick a random direction, check for collision and set it on its way. If the NPC was already moving then we simply find out which way it was heading then we update the x,y position accordingly. Simple? Yes! (Well I hope 'Yes!')

So now we've got out NPC handler, and that's fine, BUT how do we display them? Well that is actually quite simple! The routine to draw the NPC's is really small and the bonus here is that when we add other objects like enemies and items, etc. this routine will draw them too! Excellent! So here's the code:

SUB DrawNPCs
FOR i = 0 TO Engine.MaxNPCs
 IF NPC(i).Active = False THEN GOTO NextNPC   'Is the NPC Active?
 Xpos = NPC(i).x - CameraX                    'Get the coords to draw the NPC
 Ypos = NPC(i).y - CameraY                    'at on the screen
 NPCpic = (NPC(i).Dir * 2) - 1                'Get the frame the NPC is in
 Put VARSEG(buffer(0)), Xpos, Ypos, PlayerGFX(0, NPCpic)    'Draw it
NextNPC:                                      'Process the next NPC
NEXT i
Xpos = Engine.x - CameraX                     'Now get the player's coords
Ypos = Engine.y - CameraY                     'to draw
PlayerPic = ((Engine.Direction * 2) - 1)      'Get frame
Put VARSEG(buffer(0)), Xpos, Ypos, PlayerGFX(0, PlayerPic)  'Draw it
END SUB

There we go! That will also draw the player onscreen too so you can get rid of the lines which draw the player in the DrawMap sub from last issue and replace it with a call to this routine. Also note that the player is always drawn on top of everything (which will change when we start paying more attention to layers). This is so that you don't get him being below anything and can always see him. What this routine really needs is animation but I forgot the formula to do that, I'll show you it next issue though ;).

Well that just about covers it. But one last thing is probably on your mind: How do I initialize NPCs so they appear on the map? Well this is simple but I'll still show you how to do it:

NPC(i).x = <where-ever-you-want>
NPC(i).y = <where-ever-you-want>
NPC(i).Dir = South
NPC(i).Active = True

'This is something I forgot to give you last issue. It initializes some of the Engine's required variables.
Engine.x = <where-ever-you-want>    'Our starting position
Engine.y = <where-ever-you-want>
Engine.Direction = South            'We'll face south
Engine.MaxNPCs = 10                 'Maximum NPCs is 5
skip = 1                            'Frame skip

The top four lines will initialize one NPC (you have to substitute your NPCs starting co-ordinates). It also sets it to face south and to be active. Also as you can see we've got 5 extra lines which deal with the Engine data structure. Well these setup some variables required by the engine. The 'skip=1' down at the bottom is the frame skip. You can set it so that when you move the players x,y position decrease/increases by skip. This will later allow us to make the player run if you have a multiple keypress handler (all you'd have to do is make skip=2 or something when the player holds the run key down). A word of warning: don't set skip to an odd number. If you do you will eventually skip tiles and mess the collision detection up. Always make it either 2,4,8 or 16.

And that cover's everything I wanted to say about NPCs for this issue. Next month we'll go into scripting because I'm not quite ready for explaining my battle system yet. We'll also give those NPCs something to say! See you next month!

This article was written by: Fling-master - http://www.qbrpgs.com

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