上次搞定了角色的行走以及角色与地图元素的碰撞检测问题,这次就在这个地图中加入第一个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、
?
?