Issue #4
Saturday, October 6th, 2001

I actually did have a scripting article written for this issue, but I decided to leave it until next issue since it wasn't very good, and becuase I really wanted to talk more about NPCs. Next issue there'll be a huge article on scripting.

So, last issue we talked about NPCs and how to move them around and display them. Well, that's all fine and dandy but now lets do some cool things with them. What cool things can we do with NPCs you say? Plenty! Today we'll talk about moving NPCs to a specific point on the map, and making an NPC go after the player when the player is within a certain distance of the NPC. This will provde a good basis for a real-time battle engine which we'll get into at a later point. (BTW if you want to see a version of the engine we're making in this series in action download mine. It uses DirectQB and runs pretty darn fast.)

So lets start by adding to our NPC data type:

TYPE NPCType                   'Holds NPC data
 x         AS INTEGER          'X coord
 y         AS INTEGER          'Y coord
 Dir       AS INTEGER          'The direction
 ImgStart  AS INTEGER          'The index into the tileset to display NPC
 Moving    AS INTEGER          'If its moving and the amount left
 Active    AS INTEGER          'Is it active?
 Speed     AS INTEGER          'The NPC's walking speed (in pixels)
 AI        AS INTEGER          'Type of movement
 xTarget   AS INTEGER          'Target position to walk to
 yTarget   AS INTEGER          '(Must not be set to aiRandom for this to be used)
 Range     AS INTEGER          'Amount of pixels before the NPC "sees" the player
END TYPE

'Also add this constants to because they'll be used by the new UpdateNPCs sub
CONST aiRandom = 0
CONST aiTarget = 1
CONST aiRange = 2

Just to go over some things we've added: ImgStart can be used to display NPCs which look different from each other, AI is the type of AI the NPC uses, xTarget and yTarget are the position the NPC is moving to. Range is basically the length in pixels of the NPC's vision.

Ok, now we're going to recode our whole UpdateNPCs sub because the one I gave you last time was horribly sloppy. (Well not horribly...) Just replace your sub with this code and I'll explain it all afterwards:

SUB UpdateNPCs
 'Go through all the NPCs
 FOR i = 0 TO Engine.MaxNPCs
  'Only update if the NPC is active
  IF NPC(i).Active = 0 THEN GOTO StartIt

  'If the NPC isn't currently moving then see if it "wants" to move
  IF NPC(i).Moving = 0 AND NPC(i).AI = aiRandom THEN
	IF INT(RND * 35) + 1 = 5 THEN
	 'Yes it does "want" to move, so move it
	 MoveNPC i, INT(RND * 4) + 1, 16
	ELSE
	 GOTO StartIt
	END IF
 
  'Check and see if it should move after a target
  ELSEIF NPC(i).Moving = 0 AND NPC(i).AI = aiTarget THEN
	'Get the target position for the NPC
	targetX = NPC(i).targetX
	targetY = NPC(i).targetY
	
	'Get distance
	farX = ABS(targetX - NPC(i).x) > 16
	farY = ABS(targetY - NPC(i).y) > 16

	'Decide which direction to move and move it
	IF targetX > NPC(i).x AND farX THEN MoveNPC i, East, 16
	IF targetX < NPC(i).x AND farX THEN MoveNPC i, West, 16
	IF targetY > NPC(i).y AND farY THEN MoveNPC i, South, 16
	IF targetY < NPC(i).y AND farY THEN MoveNPC i, North, 16

  'Check and see if it goes after the player if the player is close by
  ELSEIF NPC(i).Moving = 0 AND NPC(i).AI = aiRange THEN
	'See if the player is within a certain range of the NPC
	IF Engine.x >= (NPC(i).x - NPC(i).Range) AND Engine.x <= (NPC(i).x + NPC(i).Range) AND Engine.y >= (NPC(i).y - NPC(i).Range) AND Engine.y <= (NPC(i).y + NPC(i).Range) THEN

	 'Set a target (the player)
	 targetX = Engine.x
	 targetY = Engine.y
 
	 'Get distance
	 farX = ABS(targetX - NPC(i).x) > 16
	 farY = ABS(targetY - NPC(i).y) > 16
 
	 'Decide which direction to move and move it
	 IF targetX > NPC(i).x AND farX THEN MoveNPC i, East, 16
	 IF targetX < NPC(i).x AND farX THEN MoveNPC i, West, 16
	 IF targetY > NPC(i).y AND farY THEN MoveNPC i, South, 16
	 IF targetY < NPC(i).y AND farY THEN MoveNPC i, North, 16

	'We aren't withing range so do random movement
	ELSE
	 IF INT(RND * 35) + 1 = 5 THEN
	  'Yes it does "want" to move, so move it
	  MoveNPC i, INT(RND * 4) + 1, 16
	 ELSE
	  GOTO StartIt
	 END IF

	END IF
  END IF

  'If the NPC is moving then move the NPC
  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 - NPC(i).Speed
    CASE South
     NPC(i).y = NPC(i).y + NPC(i).Speed
    CASE East
     NPC(i).x = NPC(i).x + NPC(i).Speed
    CASE West
     NPC(i).x = NPC(i).x - NPC(i).Speed
   END SELECT
  END IF
StartIt:
 NEXT i
END SUB

What this does is it updates all the NPCs which are currently active or 'alive'. It then checks what kind of AI we will use for the NPC. If we're doing random movement (aiRandom) then we see if it 'wants' to move and if so we move it 16 pixels in a random direction. If it is doing target based movement (aiTarget), then we must figure out in which direction we need to move in to get closer to the target. Once that is decided we move the NPC 16 pixels in the correct direction. The problem with this is that NPC's can get stuck behind walls when you're on the other side. A solution is to allow the NPCs to move diagonally, then any given NPC can 'slide' along the wall to get to their destination. Another problem we encounter is that this isn't 'smart' movement. i.e. There is not path-finding like in Diablo. The NPC simply goes in a straight line toward its target and doesn't try to avoid obstacles. We might get into that later, but for our purposes now this is not really a huge problem.

The last kind of AI for the NPCs is aiRange. This is where the NPC does random movement until the player is close by. When the player is within a certain amount of pixels the NPC will run after him/her. It uses the same method as aiTarget to go after the player. Keep in mind though, that to obtain maximum speed we are checking if the player is within a rectangle orginating at the NPC in question. A better way is to check the circular around the NPC but since this would involve multiplications and square roots (which are really slow) I've done it this way.

Now you're probably wondering where the heck the MoveNPC sub came from. Well this is a better structured way of moving NPCs than what I showed you last time. This way, we can move any given NPC by any given amount, anytime we want without having to insert lines of code to check for collision. Here is the sub:

SUB MoveNPC (num, Dir, amount)
 'Set the amount to move and the direction
 NPC(num).Moving = amount
 NPC(num).Dir = Dir

 'Now figure out which way to move
 SELECT CASE NPC(num).Dir
  CASE North
	'Check for collisions
	tile = Map(NPC(num).x \ 16, (NPC(num).y - 1) \ 16).collision
	tile2 = Map((NPC(num).x + 15) \ 16, (NPC(num).y - 1) \ 16).collision
	IF tile <> 1 AND tile2 <> 1 THEN
	ELSE
	 NPC(num).Moving = 0
	END IF
  CASE South
	'Check for collisions
	tile = Map(NPC(num).x \ 16, (NPC(num).y + 16) \ 16).collision
	tile2 = Map((NPC(num).x + 15) \ 16, (NPC(num).y + 16) \ 16).collision
	IF tile <> 1 AND tile2 <> 1 THEN
	ELSE
	 NPC(num).Moving = 0
	END IF
  CASE West
	'Check for collisions
	tile = Map((NPC(num).x - 1) \ 16, NPC(num).y \ 16).collision
	tile2 = Map((NPC(num).x - 1) \ 16, (NPC(num).y + 15) \ 16).collision
	IF tile <> 1 AND tile2 <> 1 THEN
	ELSE
	 NPC(num).Moving = 0
	END IF
  CASE East
	'Check for collisions
	tile = Map((NPC(num).x + 16) \ 16, NPC(num).y \ 16).collision
	tile2 = Map((NPC(num).x + 16) \ 16, (NPC(num).y + 15) \ 16).collision
	IF tile <> 1 AND tile2 <> 1 THEN
	ELSE
	 NPC(num).Moving = 0
	END IF
 END SELECT

END SUB

As you can see, it checks for collisions also.

Like I said at the beginning of the article, you can download a working copy of the engine we're building at HyperRealistic Games. It supports everything we've covered, but also allows the player (and NPCs) to fire bullets at each other. It uses DirectQB but could be ported to any other library with minimal effort.

Next issue there will be a mega-article on adding a script interpreter to your RPG engine. We'll create a scripting language which allows you to use variables (integer and string), do IF's, handle NPC conversation, change the map layout, and much, much more. Do not miss it!

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.