【纯java语言做RPG游戏】4.用XML导入NPC并与NPC对话_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 【纯java语言做RPG游戏】4.用XML导入NPC并与NPC对话

【纯java语言做RPG游戏】4.用XML导入NPC并与NPC对话

 2014/11/8 22:07:55  Y_1746119035  程序员俱乐部  我要评论(0)
  • 摘要:上次搞定了角色的行走以及角色与地图元素的碰撞检测问题,这次就在这个地图中加入第一个NPC吧..首先,前面做地图的时候用的是三层的数组,第一层用来存放角色脚下的素材,第二层是和角色同一层次的素材,而第三层本来是准备用来存放角色上方的素材想云朵之类的,但是想了一下,还是算了,没必要做得那么的麻烦,那么第三层就用来存放NPC吧...在游戏中加入一个NPC,可以用JAVA面向对象的思想,创建一个NPC类,游戏中的每一个npc都是这个类的一个对象,我们游戏对npc的操作便可以转化成为对这个类的操作了
  • 标签:Java 对话 游戏 XML

上次搞定了角色的行走以及角色与地图元素的碰撞检测问题,这次就在这个地图中加入第一个NPC吧..

?

首先,前面做地图的时候用的是三层的数组,第一层用来存放角色脚下的素材,第二层是和角色同一层次的素材,而第三层本来是准备用来存放角色上方的素材想云朵之类的,但是想了一下,还是算了,没必要做得那么的麻烦,那么第三层就用来存放NPC吧...

?

在游戏中加入一个NPC,可以用JAVA面向对象的思想,创建一个NPC类,游戏中的每一个npc都是这个类的一个对象,我们游戏对npc的操作便可以转化成为对这个类的操作了.但是如果是在游戏的代码中去实例化这个类的话,一旦npc对象多了代码就会变得特别的长,不方便我们进行后续的调试,所以最好是用一种文件格式将一个个的npc属性事先存放好,在游戏进行的过程中一旦遇到一个npc再从这个事先保存好的npc文件中读取其中的属性,这样我们要修改npc的属性也比较方便...

?

用什么格式好呢,一开始我想过直接用txt文件,不过txt好像有点难控制的样子,正好前段时间学过XML解析,这里我选择了用XML来保存npc的信息,而java中解析xml的方法采用dom解析的方式,因为这样既可以读又可以写...

?

思路就这么多,下面开始做准备工作..

1.找到一个npc的图片,我开始找了很久纠结不知道用什么,后面偶然发现一个网站,这里面可以让你自己拼接一个像素的npc图像出来,这简直不能再棒(链接放上:http://www.icongenerators.net/pixelavatar.html)

我拼了一个这样的出来,不过这网站保存的图片背景是白色的,需要自己用PS把背景换成透明的



?

2.接着就是写npc属性的xml文件了,这里先暂时想到这么几个属性,后面用到其他的再加吧..

class="xml"><?xml version="1.0" encoding="utf-8"?>

<npc201>
	<name>逗逼</name>
	<hp>20</hp>
	<level>2</level>
	<exp>10</exp>
	<money>10</money>
	<islive>1</islive>
	<talk>我是大傻逼,哈哈哈哈</talk>
</npc201>

?3.再接下来就是在游戏中通过dom解析xml文件,得到这个npc对象了,得到这个对象之后,游戏程序怎么找到这个对象呢,这里因为一个npc是和第三层地图数组map3里面的值是一一对应的,所有在得到这个对象之后,可以将它与它对应的数组中的值,放入hashmap,这样就建立起了一一对应的关系.

首先写一个npc类:

package yy1020;

public class NPC {
	//NPC的名字
	String name;
	//血量
	int hp;
	//等级
	int level;
	//经验
	int exp;
	//金钱
	int money;
	//是否活着 1活着 0死了
	int islive;
	//对话
	String talk = "我是大傻逼!";
	
	public NPC(String name,int hp,int level,int exp,int money,int islive,String talk) {
		this.name = name;
		this.hp = hp;
		this.level = level;
		this.exp = exp;
		this.money = money;
		this.islive = islive;
		this.talk = talk;
	}
}

?

写一个从xml文件中获取npc对象的类

package yy1020;

import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class GetNPC {
	//用来将npc对象与map3数组中的值一一对应起来的hashmap
	static HashMap<Integer, NPC> map = new HashMap<Integer, NPC>();
	
	public static void getnpc(int num){
		//获得xml文件路径
		String numstr = String.valueOf(num);
		String path = "npc\\npc"+numstr+".xml";
		try{
			//得到解析xml工厂对象
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			//通过工厂对象得到解析器对象
			DocumentBuilder builder = factory.newDocumentBuilder();
			//解析器对象解析xml文件,得到一个doc文件
			Document doc = builder.parse(path);
			setNPC(doc,num);
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}
	
	/**
	 * 解析doc文件,从里面获得npc类
	 * @param doc
	 */
	public static void setNPC(Node doc,int num){
		String name = "";
		int hp=0;
		int level=0;
		int exp=0;
		int money=0;
		int islive=0;
		String talk="";
		Node node = (Node) doc.getFirstChild();
		System.out.println(node.getNodeName());
		NodeList nodes = node.getChildNodes();
		for(int i=0;i<nodes.getLength();i++){
			Node n = nodes.item(i);
			String str = n.getNodeName();
			if(n instanceof Element){
				if(str.equals("name")){
					name = n.getTextContent();
				}else if(str.equals("hp")){
					hp = Integer.parseInt(n.getTextContent());
				}else if(str.equals("level")){
					level = Integer.parseInt(n.getTextContent());
				}else if(str.equals("exp")){
					exp = Integer.parseInt(n.getTextContent());
				}else if(str.equals("money")){
					money = Integer.parseInt(n.getTextContent());
				}else if(str.equals("islive")){
					islive = Integer.parseInt(n.getTextContent());
				}else if(str.equals("talk")){
					talk = n.getTextContent();
				}
				
			}
		}
		NPC npc = new NPC(name, hp, level, exp, money, islive, talk);
		//将生成的这个npc对象和num加入hashmap
		map.put(num, npc);
		System.out.println(npc.name);
	}
}

?4.要实现与npc的对话,必须要重新写一个对话框面板,在游戏中角色面对npc时按下g键,就弹出这个对话框(当然这个对话框是一直都存在的,只是不用的时候大小设置为0,用的时候再给他大小),框中显示npc的名字和npc所说的话。

这里需要对面板的按键监听器做一系列的处理,按下g的时候,首先由hashmap得到该npc对象,获取到npc的属性,然后让刷新线程里面游戏面板停止刷新改为刷新对话框面板,同时让对话框面板的大小正常化,里面显示出npc的名字和对话的内容.

对话暂时只做了和上方的npc对话的判断,其他方向都是同样的处理方式

对话框面板类:

package yy1020;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;

import javax.swing.JPanel;
/**
 * 对话框面板
 * @author yy
 *
 */
public class TalkPanel extends JPanel implements gameConfig{
	static NPC npc;
	
	
	public TalkPanel() {
		init();
	}
	
	public void init(){
		this.setBounds(28, 500, 0, 0);
		this.setLayout(null);
		this.setOpaque(false);//设置面板透明
	}
	
	
	
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		if(npc!=null){
			g.drawImage(talkbox.getImage(), 0, 0, 630, 130, null);
			g.setColor(Color.BLUE);
			Font font = new Font("黑体", 600, 25);
			g.setFont(font);
			g.drawString(npc.name+":", 30, 30);
			g.setColor(Color.GREEN);
			g.drawString(npc.talk, 60, 65);
		}
	}
	
	//显示对话面板
	public void show(){
		this.setPreferredSize(new Dimension(panelX, panelY));
	}
	//隐藏对话面板
	public void hide(){
		this.setPreferredSize(new Dimension(0, 0));
	}
	//得到正在对话的npc
	public static void gettalknpc(int num){
		npc = GetNPC.map.get(num);
	}
	
}

?

?

游戏窗体类(和前面版本基本相同,不过按键监听那里加了很多判断):

package yy1020;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * 游戏主窗体
 * @author yy
 *
 */
public class mainFrame extends JFrame implements gameConfig{
	//游戏状态   1为行走 2为对话
	static int tag = 1;
	//游戏面板
	JPanel panel;
	//对话面板
	JPanel tpanel;
	
	public mainFrame() {
		init();
		
	}
	/**
	 * 设置窗体
	 */
	public void init(){
		this.setTitle(title);
		this.setSize(frameX, frameY);
		this.setLayout(null);
		this.setDefaultCloseOperation(3);
//		//设置窗体无边框
//		this.setUndecorated(true);
		//创建对话框面板
		tpanel = settpanel();
		//创建游戏面板
		panel = setpanel();
		
		this.add(tpanel);
		this.add(panel);
		
		
		this.setVisible(true);
		//安装键盘监听器
		PanelListenner plis = new PanelListenner();
		this.addKeyListener(plis);
		
		//启动人物移动线程
		Player player = new Player();
		player.start();
		
		//启动刷新面板线程
		UpdateThread ut = new UpdateThread(panel,tpanel);
		ut.start();
	}
	
	/**
	 * 设置游戏面板
	 */
	public JPanel setpanel(){
		JPanel panel = new MyPanel();
		panel.setBounds(18, 5, panelX, panelY);
//		panel.setPreferredSize(new Dimension(panelX, panelY));
		panel.setLayout(null);
		panel.setBackground(Color.black);
		
		return panel;
	}
	/**
	 * 设置对话面板
	 * @return
	 */
	public JPanel settpanel(){
		JPanel tpanel = new TalkPanel();
		return tpanel;
	}
	
	/**
	 * 内部游戏按键监听类
	 * @author yy
	 *
	 */
	class PanelListenner extends KeyAdapter{
		//当按键按下
		public void keyPressed(KeyEvent e){
			int code = e.getKeyCode();
			if(tag==1){
				switch (code) {
				case KeyEvent.VK_UP:
					Player.up = true;
					Player.towards = 1;
					break;
				case KeyEvent.VK_DOWN:
					Player.down = true;
					Player.towards = 2;
					break;
				case KeyEvent.VK_LEFT:
					Player.left = true;
					Player.towards = 3;
					break;
				case KeyEvent.VK_RIGHT:
					Player.right = true;
					Player.towards = 4;
					break;
				case KeyEvent.VK_G://按下了对话键
					if(Player.towards==1){//角色朝着上
						int num = ReadMapFile.map3[Player.y/elesize-1][Player.x/elesize];
						if(num!=0){//上方有npc
							if(GetNPC.map.get(num)==null){
								GetNPC.getnpc(num);
								TalkPanel.gettalknpc(num);
							}
							tag = 2;
							tpanel.setBounds(28, 500, 630, 150);
							
							tpanel.repaint();
							System.out.println(1);
						}
					}else if(Player.towards==2){//角色朝着下
						if(ReadMapFile.map3[Player.y/elesize+1][Player.x/elesize]!=0){//下方有npc
							tpanel.setBounds(28, 500, 630, 150);
							tag = 2;//进入对话模式
							tpanel.repaint();
						}
					}else if(Player.towards==3){//角色朝着左
						if(ReadMapFile.map3[Player.y/elesize][Player.x/elesize-1]!=0){//左方有npc
							tpanel.setBounds(28, 500, 630, 150);
							tag = 2;
							tpanel.repaint();
						}
					}else if(Player.towards==4){//角色朝着下
						if(ReadMapFile.map3[Player.y/elesize][Player.x/elesize+1]!=0){//右方有npc
							tpanel.setBounds(28, 500, 630, 150);
							tag = 2;
							tpanel.repaint();
						}
					}
					
					break;

				default:
					break;
				}
			}else if(tag==2){
				if(code==KeyEvent.VK_G){
					tag=1;
					tpanel.setBounds(28, 500, 0, 0);
				}
			}
			
		}
		//当按键释放
		public void keyReleased(KeyEvent e){
			if(tag==1){
				int code = e.getKeyCode();
				switch (code) {
				case KeyEvent.VK_UP:
					Player.up = false;
					Player.up1 = 0;
					break;
				case KeyEvent.VK_DOWN:
					Player.down = false;
					Player.down1 = 0;
					break;
				case KeyEvent.VK_LEFT:
					Player.left = false;
					Player.left1 = 0;
					break;
				case KeyEvent.VK_RIGHT:
					Player.right = false;
					Player.right1 = 0;
					break;

				default:
					break;
				}
			}
		}
	}
	/**
	 * 自定义内部游戏面板类
	 * @author yy
	 *
	 */
	class MyPanel extends JPanel{
		@Override
		public void paint(Graphics g) {
			super.paint(g);
			//找到角色旁边的素材,上下左右各5格
			for(int i=Player.getI()-6;i<=Player.getI()+6;i++){
				for(int j=Player.getJ()-6;j<=Player.getJ()+6;j++){
					//如果这一格没有超界
					if(i>=0&&j>=0&&i<ReadMapFile.map1.length&&j<ReadMapFile.map1[0].length){
						//画第一层元素
						ImageIcon icon = GetMap.int2icon(ReadMapFile.map1[i][j]);
						g.drawImage(icon.getImage(), (Player.px-elesize/2)+((j-Player.getJ())*elesize)-(Player.mx%elesize), (Player.py-elesize/2)+((i-Player.getI())*elesize)-(Player.my%elesize), elesize, elesize, null);
						//第二层
						if(ReadMapFile.map2[i][j]!=0){
							ImageIcon icon2 = GetMap.int2icon(ReadMapFile.map2[i][j]);
							g.drawImage(icon2.getImage(), (Player.px-elesize/2)+((j-Player.getJ())*elesize)-(Player.mx%elesize), (Player.py-elesize/2)+((i-Player.getI())*elesize)-(Player.my%elesize), elesize, elesize, null);
						}
						//第三层
						if(ReadMapFile.map3[i][j]!=0){
							ImageIcon icon3 = GetMap.int2npc(ReadMapFile.map3[i][j]);
							g.drawImage(icon3.getImage(), (Player.px-elesize/2)+((j-Player.getJ())*elesize)-(Player.mx%elesize), (Player.py-elesize/2)+((i-Player.getI())*elesize)-(Player.my%elesize), elesize, elesize, null);
						}
						
					}
				}
			}
//			g.setColor(Color.black);
//			g.fillRect(0, 0, 50, 650);
//			g.fillRect(0, 0, 650, 50);
//			g.fillRect(600, 0, 50, 650);
//			g.fillRect(0, 600, 650, 50);
			
			Player.draw(g);
			
			//npc
//			g.drawImage(npc1.getImage(), 400, 400, 50, 50, null);
			
			
			//做一个黑色的图片,然后中间挖空一个圆,加上模糊效果,来模拟人的视野
			g.drawImage(shadow2.getImage(), 0, 0, 650, 650, null);
		}
	}
}

?然后是面板刷新线程:

package yy1020;

import javax.swing.JPanel;
/**
 * 刷新游戏面板的线程类
 * @author yy
 *
 */
public class UpdateThread extends Thread{
	JPanel panel;
	JPanel tpanel;
	public UpdateThread(JPanel panel,JPanel tpanel) {
		this.panel = panel;
		this.tpanel = tpanel;
	}
	
	@Override
	public void run() {
		while(true){
			if(mainFrame.tag==1){//如果是在走路状态就刷新游戏地图面板
				panel.repaint();
			}else if(mainFrame.tag==2){//如果是在对话的状态就刷新对话框面板
				tpanel.repaint();
			}
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

?

这样npc的引入以及和npc的对话就实现了,后面再多加入几个npc,对话里面加入选项以及任务系统,一个基本的模型就差不多了,额? = =!? ,不对,这特么的玩家背包菜单都还没做,怪物都还没有,战斗系统也没有,存档也没弄,后面还有好多啊? 0 0、学校就要考试了,伤不起啊....

?

总之这次就先做到这了,还是上个图玩玩:



?

然后是画质和帧数惨不忍睹的gif


?

代码放下面了,xml文件用的是相对路径不用改,只需要在test类改一下地图的路径就能运行了吧...

?

做到这里突然想起我的地图数据结构可能选择错了,如果当初选择的不是数组而是图的话,估计后期会好做很多,不过也不想改了,这个游戏就当是探路的吧? 0 0、

?

?

  • 大小: 1.4 KB
  • 大小: 296.3 KB
  • 大小: 767.4 KB
  • map1.rar (1.4 KB)
  • 下载次数: 4
  • 场景移动小游戏.rar (237.5 KB)
  • 下载次数: 5
  • 查看图片附件
发表评论
用户名: 匿名