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. |