上次搞定了角色的行走以及角色与地图元素的碰撞检测问题,这次就在这个地图中加入第一个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文件了,这里先暂时想到这么几个属性,后面用到其他的再加吧..
<?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、