CHRONICLES OF A VIDEOGAME IN JAVA – 6. NPCs

Previously on the Chronicles of a Videogame in Java: Collision Detection for Dummies.

The code discussed below is already available on my GitHub page at theBoyWithNoName (“npcs” branch). Please feel free to download it to take a better look. Ask me anything about it.

dsads
Welcome back to the journey of “The Boy With No Name”, a 2D platform game that I’m coding from scratch in Java. Today I’m going to talk a bit about NPCs, acronym that stands for Non-Playable Character. For those of you who may not know, NPCs in videogames are basically characters that you meet but can’t directly control. In my experience, there can be three types of NPC in a videogame:

The unavoidable one is a part of the story. The main character is forced to interact with him in order to move forward in the game. (example: Professor Oak in the Pokémon series)

The helpful one helps the main character through his journey giving him wise words or even objects to overcome adversities. (example: the merchant from Resident Evil 4)

The useless one is really just a piece of furniture. He may even speak two or three words in a row but there’s no purpose in those words. They don’t help you finish the game. (example: the citizens in Zanarkand at the beginning of Final Fantasy X)

The kind of NPCs that I’m adopting for “The Boy With No Name” is the second one, the helpful kinda guy. Here’s how it works: all the secondary characters in the game will wait idle in a specific place of a specific level and, only if talked to, they will suggest something or maybe give something to the main character. They will not be unavoidable though: the player will be perfectly able to just walk past them and go on with his adventure. Pretty simple.

npcs

At this point we have delined the standard behaviour of the NPCs in the game. Now it’s time to put this thoughts to code.

From an architectural point of view, I found inconvenient to use some sort of map or bidimensional array for the positioning of the secondary characters in the stage. Do you remember the tiled map we used to position blocks of terrain? Well we could have used the same concept to store current NPCs, but to me that’s a crazy thing to do if you don’t plan to put hundreds of secondary little characters in the game.

So yeah, the NPCManger class stores all of the stage’s Non-Playable Characters in a simple array of objects. Informations about where to put the NPC and what the NPC has to say is stored in text files that the NPCManager consults accordingly to the level currently played by the user. So here’s what the class looks like:

public class NPCManager {
	public NPCManager(int currentLevel){
		this.currentLevel=currentLevel;
		currentNPCs=new ArrayList<NPC>();
		loadInformations();
	}
	
	public void initializeStage(int currentLevel) {
		currentNPCs.clear();
		this.currentLevel=currentLevel;
		loadInformations();
	}
	
	private void loadInformations() {
		InputStream is=this.getClass().getResourceAsStream("/npc_info/level"+String.valueOf(currentLevel)+".txt");
		if(is==null){
			return;
		}
		BufferedReader reader=new BufferedReader(new InputStreamReader(is));
		String line=null;
		String[] singleNPCInfo;
		try {
			while((line=reader.readLine())!=null){
				singleNPCInfo=line.split(" ");
				currentNPCs.add(new NPC(singleNPCInfo[0],Integer.valueOf(singleNPCInfo[1]),
						Integer.valueOf(singleNPCInfo[2]),Integer.valueOf(singleNPCInfo[3]),currentLevel));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public ArrayList<NPC> getNPCs() {
		return currentNPCs;
	}
	
	//returns the closest enemy within two tiles of distance (which is 
	//the maximum distance allowed for an interaction with a NPC)
	public NPC closestNPC(int boyRow, int boyCol) {
		NPC closestNPC=null;
		int currentDistance;
		for(int i=0; i<currentNPCs.size(); i++){
			if(boyRow!=currentNPCs.get(i).getRow()){
				continue;
			}
			if((Math.abs(currentNPCs.get(i).getCol()-boyCol))<=MAXIMUM_TALKING_DISTANCE){
				currentDistance=Math.abs(currentNPCs.get(i).getCurrentX()-(boyCol*Tile.TILE_SIZE));
				if(closestNPC==null){
					closestNPC=currentNPCs.get(i);
				} else {
					if(currentDistance<Math.abs(closestNPC.getCurrentX()-boyCol+Tile.TILE_SIZE)){
						closestNPC=currentNPCs.get(i);
					}
				}
			}
		}
		
		return closestNPC;
	}	
	
	private static final int MAXIMUM_TALKING_DISTANCE=2;
	private ArrayList<NPC> currentNPCs;
	private int currentLevel;
}

As you may notice there’s a closestNPC() function that, as you can imagine, is called every time you try to interact with a non playable character. This function finds the closest Non-Playable-Character given the protagonist’s position. But the character can’t interact with people further than 2 tiles, and that’s the reason of the control at line 46 based on the constant MAXIMUM_TALKING_DISTANCE.

An instance of the NPCManager class is stored in the GameManager, the main thread of the game which also controls a set of currently pressed keys and reacts to them in the proper way via the manageKeys() function. Here’s what the manageKeys() function will look like if we bound the <enter> key to the interaction with an NPC:

private void manageKeys() {
		[...]
		if(currentKeys.contains(KeyEvent.VK_ENTER)){
			NPC tempNpc;
			//find the closest npc according to the character's position
			if((tempNpc=npcManager.closestNPC(boy.getRow(),boy.getCol()))!=null){
				
				//if the npc is already talking, keep talking...
				if(tempNpc.isTalking()){
					if(!(tempNpc.continueTalking())){
						listening=false;
					}
				
				//otherwise interact with the npc
				} else {
					tempNpc.interact();
					
					//put the character in &amp;amp;amp;lt;idle&amp;amp;amp;gt; status when he's talking
					boy.stop();
					
					//prevent the character from moving when talking
					listening=true;
				}
			}
			currentKeys.remove(KeyEvent.VK_ENTER);
		}
		
	}

Now what remains to be seen is the actual NPC class, but to be honest there’s not much to explain here. The NPC is similar to the Protagonist (class Boy) in the sense that it has a couple of pixel coordinates and a couple of “tile-coordinates” that identify the position of the secondary character in the tiled map. The pixel coordinates are really just the tile-coordinates multiplied by the size of a tile (which in my case is 64) but you know, it’s handy and a lot more polished to have them stored separately.

What really makes the difference in the NPC class are these guys:

	private boolean talking=false;
	private String[] sentences;
	private int currentSentence=0;
	private int numberOfSentences;	

If the talking boolean is false the game will not display any of the speech balloons associated with the NPC. If true it will display a specific speech depending on how many times the player has talked to this NPC. The times you talk to an NPC are stored in the currentSentence variable, that increments each time you press in front of the guy you want to talk to. The currentSentence integer is also used to iterate over the sentences array, which contains all the lines the NPC is supposed to say in that specific spot of that specific level. The PlayPanel (the JPanel that shows the actual game) will draw the speech balloons accordingly to the NPC position and will fill the speech balloon with the words defined by sentences[currentSentence].

So yep, this is probably the easiest approach you can have to build a working NPC system. But it works:

Aaaaaaand that would be all for today’s diary. Once again, remember that this is not a proper tutorial, but just a “how I make things” kind of thing. So if you have suggestions or you don’t understand what I say, please comment below or get in touch with me on twitter. I’m also on tumblr, drawing stuff. See you soon guys!

Advertisements
CHRONICLES OF A VIDEOGAME IN JAVA – 6. NPCs

Chronicles of a Videogame in Java – 5. Collision detection for dummies

Previously on the Chronicles of a Videogame in Java: Bulding a tiled map.

The code discussed below is already available on my GitHub page at theBoyWithNoName (“collisions” branch). Please feel free to download it to take a better look. Ask me anything about it.

Hi guys and welcome back to our beautiful journey through the development of a 2D platform-game in Java Swing. Today we’re gonna talk about collisions, maybe one of the trickiest topics in the making of a bidimensional game.

Before we start I want to make sure that you remember what a Block object is in my project. A Block object has two important features:

  • It is not movable.
  • It doesn’t involve interactions with the player whatsoever.

Pretty basic stuff. So how did I approach collisions with blocks in my code? Well, what we know is that there are essentially three cases in which you can collide with an immovable block in a 2D platform, and those are: you’re running left, running right or jumping/falling in the air. Now, remember those rectangles we built around each and every object of the game? The bounding boxes? Well, those rectangles are the foundation of collision detection. Here’s a simple picture that explains why:

collisionNoCollision

See the red rectangle around our chill protagonist? That’s his bounding box. The block object you see next to the main character in the picture above has its own bounding box, visually represented by a blue outline. If two bounding boxes intersect, a collision happens.

On the left you can see a situation where the character and the block do not collide. On the right side of the picture, instead, they are colliding pretty hard I’d say. And that’s a situation we don’t want to happen in the game. If the Character is running right and faces a Block object, his bounding box must never intersect the Block’s one.

Now let’s put it to code.

	public void checkBlockCollisions(){
		[...]
		//if last direction was right..
		if(last_direction==KeyEvent.VK_RIGHT){
			
			//get the left side of the bounding box
			int footX=(int)boundingBox.getMinX();
			
			//get the tile position (in the tiled map) 
			//relative to the tile in front of the character
			int tileInFrontOfFootRow=((footY-1)/Tile.TILE_SIZE);
			int tileInFrontOfFootCol=(footX/Tile.TILE_SIZE)+1;
			
			if(tileInFrontOfFootCol<World.COLS){
				//if the tile in front of the character contains a block..
				if(World.tiledMap[tileInFrontOfFootRow][tileInFrontOfFootCol] instanceof Block){
					//..and the character's bounding box intersect the block's one
					if(boundingBox.intersects(World.tiledMap[tileInFrontOfFootRow][tileInFrontOfFootCol].getBoundingBox())){
						//push the character away and re-set its position
						currentX-=DISPLACEMENT;
						boundingBox.setLocation(currentX, currentY);
						currentCol=currentX/Tile.TILE_SIZE;
					}
				}
				
				if(World.tiledMap[currentRow][currentCol] instanceof Block){
					//if the tile the character finds himself in contains a block, act like above
					if(boundingBox.intersects(World.tiledMap[currentRow][currentCol].getBoundingBox())){
						currentX-=DISPLACEMENT;
						boundingBox.setLocation(currentX, currentY);
						currentCol=currentX/Tile.TILE_SIZE;
					}
				}
			}
		} else {
			//the same thing you see above happens here for the left direction 
			[...]	
		}
	}

Notice that when the character runs left or right and his bounding box intersects a Block’s one, he’s pushed away of an amount of pixels equals to the number of pixels he covers with a single step (the DISPLACEMENT value). This way the character’s bounding box can never intersect the Block.

The code you see above works perfectly for side collisions, but it doesn’t help much if our little protagonist is jumping and slamming his head on a block that is placed above him.

As for jump collisions I had to rethink the jump mechanic I talked about in one of my previous posts. “How” you say? Well, first thing I did was splitting the ascending phase and the descending phase of the jump. In particular, when the character is going up we have that the jumping boolean is set to true and the falling is set to false. When the character is going down we have the opposite setting of those variables. To have a specific falling state is really helpful even in other situations: for example I can set it to true when the character doesn’t have a Block object under his feet. This way I can make character fall (increment y position) while falling=true. But collision-wise, we have that the character’s head can only collide if he’s in ascending phase. So here’s how I dealt with the problem:

		
		//if the character is jumping, his head must not touch a block;
		//if it touches a block, stop the ascending phase of the jump (start falling)
		if(jumping){
			
			//row position of the cell above the character's head (in the tiled map)
			int upRow=(int)((boundingBox.getMinY()-1)/Tile.TILE_SIZE);
			
			//tile position relative to the upper-left corner of the character's bounding box
			int upLeftCornerCol=(int)(boundingBox.getMinX()/Tile.TILE_SIZE);
			
			//tile position relative to the upper-right corner of the character's bounding box
			int upRightCornerCol=(int)((boundingBox.getMaxX())/Tile.TILE_SIZE);

			if(currentRow>=0){
				if(World.tiledMap[upRow][upLeftCornerCol] instanceof Block){
					//if the upper-left corner stats intersecting a block, stop the jumping phase
					//and start the falling phase, setting the jump_count to 0
					if(World.tiledMap[upRow][upLeftCornerCol].getBoundingBox().intersects(boundingBox)){
						jumping=false;
						jump_count=0;
						falling=true;
						return;
					}
				}
				if(World.tiledMap[upRow][upRightCornerCol] != null){
					//if the upper-right corner stats intersecting a block, stop the jumping phase
					//and start the falling phase, setting the jump_count to 0
					if(World.tiledMap[upRow][upRightCornerCol].getBoundingBox().intersects(boundingBox)){
						jumping=false;
						jump_count=0;
						falling=true;
						return;
					}
				}
			}
		
		}

This chunk of code you see above is still part of the checkBlockCollision() function we saw earlier, so now we have all the stuff we need to make the character move around the screen, jump on blocks, collide with blocks and well, even die…if he falls too deep.

Aaaaaaand that would be all for today’s diary. Once again, remember that this is not a proper tutorial, but just a “how I make things” kind of thing. So if you have suggestions or you don’t understand what I say, please comment below or get in touch with me on twitter. I’m also on tumblr, drawing stuff. See you soon guys!

Chronicles of a Videogame in Java – 5. Collision detection for dummies