JAVA实现连连看——植物大战僵尸主题

Xanthe ·
更新时间:2024-09-21
· 771 次阅读

说明:本篇博客主要讲述练练看游戏的设计与实现。前半部分为分析与类和属性的说明,后半部分为程序的实现与程序代码。第一次写小游戏,仍存在许多问题,也借鉴了CSDN前辈的代码想法,如有不妥,还望多批评指正。

(一)需求分析

已经实现的部分:

1.游戏开始界面 2.游戏方块消除功能 3.游戏时间限制功能 4.方块刷新重拍功能 5.在游戏胜利失败时提示并结束游戏 6.炸弹功能 7.游戏中鼠标移动和点击的动态效果 8.游戏分数记录功能

未实现部分:

1.游戏音效 2.游戏分数排行榜记录功能 (二)游戏功能演示

游戏开始界面
游戏主界面

(三)游戏总体设计和类图

游戏窗口总共分为四类,第一个是游戏初始窗口,提供了开始游戏选项和更多选项,第二是游戏主窗口,提供了游玩游戏的界面,第三类是结束窗口,在游戏胜利或者失败时弹出,第四个是更多信息窗口。这四类窗口都继承了JFrame,其中其初始窗口和主窗口实现了MouseListener的接口,为了对按键添加监听。
玩家通过点击开始游戏打开一个游戏主窗口,关闭当前初始窗口,或者点击更多打开更多信息窗口。游戏主窗口中通过对游戏结束的判定打开结束窗口。
在这里插入图片描述

(四)项目代码和注解

为了方便学习,我几乎对每一行关键代码进行了注解,有些代码,如刷新、炸弹功能代码,可更改性比较大,而且不是特别完美,所有我没有添加注解,关于最关键的方块消除算法,下文会有详细解答

游戏初始窗口类代码(MainWindow.java)

package myplantslink; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; public class MainWindow extends JFrame implements MouseListener { /** * */ private static final long serialVersionUID = 1L; private ImageIcon img,imgstart,imgstart2,imgmore,imgmore2;//背景图片 开始图片 更多图片 private JButton b1,b2;//开始按钮 更多按钮 private int w1,w2,w3,h1,h2,h3;//背景 开始 更多 图片的长宽 private JLabel label;//用于装载按钮的控件 MainWindow() { //图片初始化 img=new ImageIcon("pic//bg1.jpg"); imgstart=new ImageIcon("pic//start.jpg"); imgstart2=new ImageIcon("pic//start 2.jpg"); imgmore=new ImageIcon("pic//more.jpg"); imgmore2=new ImageIcon("pic//more2.jpg"); //获取长宽 w1=img.getIconWidth(); h1=img.getIconHeight(); w2=imgstart.getIconWidth(); h2=imgstart.getIconHeight(); w3=imgmore.getIconWidth(); h3=imgmore.getIconHeight(); //开始 更多按钮初始化 b1=new JButton(); b1.setBounds(183,270,w2,h2); b1.setIcon(imgstart); b1.setBorderPainted(false); b1.addMouseListener(this); b1.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); b2=new JButton(); b2.addMouseListener(this); b2.setIcon(imgmore); b2.setBounds(450,276,w3,h3); b2.setBorderPainted(false); b2.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //控件初始化 label=new JLabel(img); label.add(b2); label.add(b1); label.setBounds(0,0,w1,h1); label.setLayout(null); //主窗口初始化 this.add(label); this.setLayout(null); this.setBounds(300,160,w1+10,h1+30); this.setTitle("植物大战僵尸"); this.setVisible(true); } public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub if(e.getSource()==b1) //点击开始 游戏开始 此窗口消失 { new GameWindow(); this.dispose(); } else if(e.getSource()==b2)//点击更多 显示更多信息窗口 { new MoreWindow(); } } public void mousePressed(MouseEvent e) { // TODO Auto-generated method stub } public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } //按钮鼠标移入的动画效果 public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub if(e.getSource()==b1) b1.setIcon(imgstart2); else if(e.getSource()==b2) b2.setIcon(imgmore2); } public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub if(e.getSource()==b1) b1.setIcon(imgstart); else if(e.getSource()==b2) b2.setIcon(imgmore); } }

游戏主界面类代码(GameWindow.java)

package myplantslink; import java.awt.Color; import java.awt.Cursor; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Random; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /*练练看游戏界面*/ public class GameWindow extends JFrame implements MouseListener{ /** * */ private static final long serialVersionUID = 1L; private ImageIcon [] Pics=new ImageIcon [8]; //植物未被鼠标选中时显示的图片 private ImageIcon [] PaintedPics=new ImageIcon[8]; //植物被鼠标选中后的图片 private ImageIcon Backgroundimg,Timeimg,Refreshimg,Bomimg,Scoreimg;//背景、时间图标、刷新图标、炸弹图标、分数图标 private ImageIcon [] rn=new ImageIcon[4];//数字图标0 1 2 3 private int [] nums={10,10,10,8,8,8,8,8};//每种方块的数量 private int [] cnt=new int [9];//记录每种方块的数量 用于初始化 确保每种方块不超过规定数量 private int Cols=10,Rows=7;//行和列的长度 /*地图数组 用于后面的消除算法 长和宽要比实际方块的要多2*/ private boolean [][] Map=new boolean [Rows+2][Cols+2]; private int w1,h1,wp,hp;//背景图片长宽 植物图片长宽 private JLabel label;//背景图片标签 用于装载背景图 private JButton [][]Blocks=new JButton [Rows+2][Cols+2];//植物方块按钮 private int [][] type=new int [Rows+2][Cols+2];//记录每个位置的植物类型 private JPanel panel1,panel2;//装载植物方块的控件 用于装载刷新按钮 炸弹按钮的控件 private JButton TimeButton,RefreshButton,RefreshNumsButton,BomButton,BomNumsButton,ScoreButton,ScoreNumsButton;//时间 刷新 炸弹 分数 按钮 private final JProgressBar progressBar = new JProgressBar();//时间条 private final int MIN_PROGRESS=0,MAX_PROGRESS=150;//最小时间值 最大时间值 private static int currentProgress = 0,scores=0,currentblocks=70;//当前的时间值 分数 地图上未消除的方块数目 private Point firstblock=new Point(-1,-1),secondblock=new Point(-1,-1);//同时选中的两个植物 最多只能有两个 private Check Judger=new Check();//判定消除类 private static int refreshnums=3,bombnums=3,isend=0;//可用刷新次数 炸弹次数 是否结束标志 GameWindow() { //初始植物图片数组 未选中 for(int i=0;i<8;++i) Pics[i]=new ImageIcon("pic//植物"+(i+1)+".gif"); //初始植物图片数组 选中 for(int i=0;i<8;++i) PaintedPics[i]=new ImageIcon("pic//植物"+(i+1)+"p.gif"); //初始数字图片数组 for(int i=0;i<4;++i) rn[i]=new ImageIcon("pic//"+i+".png"); //各个图片初始化 Backgroundimg=new ImageIcon("pic//背景2.png"); Timeimg=new ImageIcon("pic//时间.png"); Refreshimg=new ImageIcon("pic//刷新.png"); Bomimg=new ImageIcon("pic//炸弹.png"); Scoreimg=new ImageIcon("pic//分数.png"); //获取背景图片 植物图片 长宽 w1=Backgroundimg.getIconWidth(); h1=Backgroundimg.getIconHeight(); wp=Pics[1].getIconWidth(); hp=Pics[1].getIconHeight(); //背景控件 label=new JLabel(); label.setBounds(0,0,w1,h1); label.setLayout(null); label.setIcon(Backgroundimg); //时间按钮 TimeButton=new JButton(); TimeButton.setBounds(220,0,50,50); TimeButton.setIcon(Timeimg); TimeButton.setBorderPainted(false); //初始化刷新按钮 RefreshButton=new JButton(); RefreshButton.setBounds(30,100,50,50); RefreshButton.setIcon(Refreshimg); RefreshButton.setBorderPainted(false); RefreshButton.addMouseListener(this); RefreshButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //刷新剩余次数按钮 RefreshNumsButton=new JButton(); RefreshNumsButton.setBounds(100,100,50,50); RefreshNumsButton.setIcon(rn[refreshnums]); RefreshNumsButton.setBorderPainted(false); //炸弹按钮 BomButton=new JButton(); BomButton.setBounds(30,200,50,50); BomButton.setIcon(Bomimg); BomButton.setBorderPainted(false); BomButton.addMouseListener(this); BomButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //炸弹剩余次数按钮 BomNumsButton=new JButton(); BomNumsButton.setBounds(100,200,50,50); BomNumsButton.setIcon(rn[bombnums]); BomNumsButton.setBorderPainted(false); //分数按钮 ScoreButton=new JButton(); ScoreButton.setBounds(30,300,102,39); ScoreButton.setIcon(Scoreimg); ScoreButton.setBorderPainted(false); //当前分数 ScoreNumsButton=new JButton(); ScoreNumsButton.setBounds(140,300,50,39); ScoreNumsButton.setText(scores+""); ScoreNumsButton.setContentAreaFilled(false); ScoreButton.setBorderPainted(false); //装载所有植物方块的控件 panel1=new JPanel(); panel1.setBounds(220,70,620,430); panel1.setLayout(null); panel1.setOpaque(false); //左侧转载 刷新 炸弹 分数的控件 panel2=new JPanel(); panel2.setBounds(0,0,200,480); panel2.setLayout(null); panel2.setOpaque(false); panel2.add(RefreshButton); panel2.add(RefreshNumsButton); panel2.add(BomButton); panel2.add(BomNumsButton); panel2.add(ScoreButton); panel2.add(ScoreNumsButton); this.add(panel1); this.add(panel2); //初始化植物方块 for(int i=1;i<=Rows;++i) { for(int j=1;j=nums[temp]);//获取符合要求的植物种类 cnt[temp]++; //该种类数量加1 type[i][j]=temp;//记录该位置植物种类 Map[i][j]=true;//该位置存在植物 Blocks[i][j]=new JButton(); Blocks[i][j].setBounds((j-1)*wp,(i-1)*hp,wp,hp);//根据i j 设置植物方块位置 Blocks[i][j].setIcon(Pics[temp]);//根据种类设置图片 Blocks[i][j].setBorderPainted(false); Blocks[i][j].addMouseListener(this); Blocks[i][j].setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));//鼠标移到植物方块上,改变鼠标形状 panel1.add(Blocks[i][j]);//将该植物方块加入控件 } } //时间进度条初始化 progressBar.setMinimum(MIN_PROGRESS); progressBar.setMaximum(MAX_PROGRESS); progressBar.setBounds(300,10,500,20); progressBar.setValue(currentProgress); progressBar.setStringPainted(true); progressBar.setVisible(true); new Timer(500, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { currentProgress++; //如果超过最大规定时间 且游戏未结束 结束游戏 游戏失败 if (currentProgress > MAX_PROGRESS&&isend==0) { new EndWindow(0);//失败窗口 isend=1; } //如果方块全部被消除 且游戏未结束 结束游戏 游戏胜利 else if(currentblocks==0&&isend==0) { new EndWindow(1);//胜利窗口 isend=1; } progressBar.setValue(currentProgress); } }).start(); //游戏窗口各属性设置 this.setBounds(300,160,w1+20,h1+40); this.setLayout(null); this.add(TimeButton); this.add(progressBar); this.add(label); this.setVisible(true); this.setTitle("植物大战僵尸消消乐"); } //刷新功能 private void Refresh() { Point [] arr=new Point [71]; int n=0; for(int i=1;i<=Rows;++i) { for(int j=1;j<=Cols;++j) { if(Map[i][j]==true) { arr[++n]=new Point(i,j); } } } int Left=1,Right=n; while(Left<Right) { int temp=type[arr[Left].x][arr[Left].y]; type[arr[Left].x][arr[Left].y]=type[arr[Right].x][arr[Right].y]; type[arr[Right].x][arr[Right].y]=temp; Blocks[arr[Left].x][arr[Left].y].setIcon(Pics[type[arr[Left].x][arr[Left].y]]); Blocks[arr[Right].x][arr[Right].y].setIcon(Pics[type[arr[Right].x][arr[Right].y]]); Left++; Right--; } } //炸弹功能 public void Bom() { Point [] arr=new Point [71]; int n=0; for(int i=1;i<=Rows;++i) { for(int j=1;j<=Cols;++j) { if(Map[i][j]==true) { arr[++n]=new Point(i,j); } } } for(int i=1;i<=n;++i) { for(int j=i+1;j<=n;++j) { if(type[arr[i].x][arr[i].y]==type[arr[j].x][arr[j].y]) { Blocks[arr[i].x][arr[i].y].setVisible(false); Blocks[arr[j].x][arr[j].y].setVisible(false); Map[arr[i].x][arr[i].y]=false; Map[arr[j].x][arr[j].y]=false; currentblocks-=2; return; } } } } //鼠标监听接口 @Override public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub JButton temp=(JButton)e.getSource(); Point now=temp.getLocation(); int x=now.y/60+1,y=now.x/60+1; if(e.getSource()!=RefreshButton&&e.getSource()!=BomButton)//如果鼠标点击的不是刷新 炸弹按钮 即点击的是植物按钮 { if(firstblock.equals(new Point(-1,-1)))//如果还未选中植物方块 记录第一个选中的 { firstblock.x=x; firstblock.y=y; Blocks[x][y].setIcon(PaintedPics[type[x][y]]); Blocks[x][y].setBorderPainted(true); } else //即已选中一个 记录选中的第二个 { secondblock.x=x; secondblock.y=y; if(type[firstblock.x][firstblock.y]==type[secondblock.x][secondblock.y]&&Judger.Judge(firstblock, secondblock)) //判断选中的两个是否能消除 { //消除 Map[firstblock.x][firstblock.y]=false; Map[secondblock.x][secondblock.y]=false; Blocks[firstblock.x][firstblock.y].setVisible(false); Blocks[secondblock.x][secondblock.y].setVisible(false); //分数增加 方块数减少 scores+=2; currentblocks-=2; //分数显示 ScoreNumsButton.setText(scores+""); } //如果不能消除 清空选中的信息 Blocks[firstblock.x][firstblock.y].setIcon(Pics[type[firstblock.x][firstblock.y]]); Blocks[firstblock.x][firstblock.y].setBorderPainted(false); firstblock.x=firstblock.y=secondblock.x=secondblock.y=-1; } } else if(e.getSource()==RefreshButton&&refreshnums!=0)//如果选中刷新按钮 刷新次数减少 执行刷新操作 { refreshnums--; RefreshNumsButton.setIcon(rn[refreshnums]); Refresh(); } else if(e.getSource()==BomButton&&bombnums!=0)//炸弹 { bombnums--; BomNumsButton.setIcon(rn[bombnums]); Bom(); } } @Override public void mousePressed(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } @Override //鼠标移入方块时 方块边框显示 动画效果 public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub JButton temp=(JButton)e.getSource(); Point now=temp.getLocation(); int x=now.y/60+1,y=now.x/60+1; Blocks[x][y].setBorderPainted(true); } @Override //鼠标移出 效果消失 public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub JButton temp=(JButton)e.getSource(); Point now=temp.getLocation(); int x=now.y/60+1,y=now.x/60+1; Blocks[x][y].setBorderPainted(false); } //类中类 消除算法 class Check { private boolean Horizen(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; if(a.x!=b.x) return false; int bg=Math.min(a.y,b.y),end=Math.max(a.y,b.y); for(int i=bg+1;i<end;++i) if(Map[a.x][i]==true) return false; return true; } private boolean Vertical(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; if(a.y!=b.y) return false; int bg=Math.min(a.x, b.x),end=Math.max(a.x, b.x); for(int i=bg+1;i<end;++i) if(Map[i][a.y]==true) return false; return true; } private boolean TurnOnce(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; Point temp1 = new Point(a.x,b.y),temp2 = new Point(b.x,a.y); if(Map[a.x][b.y]==false&&Horizen(a,temp1)==true&&Vertical(b,temp1)==true) return true; if(Map[b.x][a.y]==false&&Horizen(b,temp2)&&Vertical(a,temp2)) return true; return false; } private boolean TurnTwice(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; for(int i=0;i<Rows+2;++i) { for(int j=0;j<Cols+2;++j) { if((i==a.x&&j==a.y)||(i==b.x&&i==b.y)) continue; if(Map[i][j]==true) continue; if(TurnOnce(a,new Point(i,j))&&(Horizen(b,new Point(i,j))||Vertical(b,new Point(i,j)))) return true; if(TurnOnce(b,new Point(i,j))&&(Horizen(a,new Point(i,j))||Vertical(a,new Point(i,j)))) return true; } } return false; } public boolean Judge(Point a,Point b) { if(Horizen(a,b)==true) return true; if(Vertical(a,b)==true) return true; if(TurnOnce(a,b)==true) return true; if(TurnTwice(a,b)==true) return true; return false; } } }

更多信息窗口类代码(MoreWindow.java)

package myplantslink; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; public class MoreWindow extends JFrame { private static final long serialVersionUID = 1L; private ImageIcon Farmerimg,Authorimg,Dancerimg; private JLabel Morelabel1,Morelabel2,Morelabel3; MoreWindow() { Farmerimg=new ImageIcon("pic//农夫.gif"); Authorimg=new ImageIcon("pic//羊皮纸.png"); Dancerimg=new ImageIcon("pic//跳舞僵尸.gif"); Morelabel1=new JLabel(); Morelabel1.setIcon(Farmerimg); Morelabel1.setBounds(0, 0, 300, 500); Morelabel2=new JLabel(); Morelabel2.setBounds(270,50,300,270); Morelabel2.setIcon(Authorimg); Morelabel3=new JLabel(); Morelabel3.setBounds(350,320,75,150); Morelabel3.setIcon(Dancerimg); this.setBounds(450,150,600,510); this.setLayout(null); this.add(Morelabel1); this.add(Morelabel2); this.add(Morelabel3); this.setTitle("更多信息"); this.setVisible(true); } }

结束窗口类代码(EndWindow.java)

package myplantslink; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; public class EndWindow extends JFrame{ private static final long serialVersionUID = 1L; private ImageIcon Victoryimg,Defeatimg; private JLabel Lastlabel; EndWindow(int n) { if(n==0) { Defeatimg=new ImageIcon("pic//失败.png"); Lastlabel=new JLabel(); Lastlabel.setBounds(0,0,240,200); Lastlabel.setIcon(Defeatimg); this.setBounds(600,300,250,210); this.setLayout(null); this.add(Lastlabel); this.setTitle("游戏失败"); this.setVisible(true); } else if(n==1) { Victoryimg=new ImageIcon("pic//胜利.png"); Lastlabel=new JLabel(); Lastlabel.setBounds(0,0,204,62); Lastlabel.setIcon(Victoryimg); this.setBounds(600,300,224,100); this.setLayout(null); this.add(Lastlabel); this.setTitle("游戏胜利"); this.setVisible(true); } } }

主函数代码

package myplantslink; public class Main { public static void main(String [] argv) { new MainWindow(); } } (五)核心算法——方块消除

以下的示例代码中:

Map[x][y]//表示位置x y处是否有方块

1.水平方向检测
水平检测用来判断两个点的纵坐标是否相等,同时判断两点间有没有障碍物。

在这里插入图片描述
因此直接检测两点间是否有障碍物就可以了,代码如下:

boolean Horizen(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; if(a.x!=b.x) return false; int bg=Math.min(a.y,b.y),end=Math.max(a.y,b.y); for(int i=bg+1;i<end;++i) if(Map[a.x][i]==true) return false; return true; }

2.垂直方向检测
垂直检测用来判断两个点的横坐标是否相等,同时判断两点间有没有障碍物。

2.png

同样地,直接检测两点间是否有障碍物,代码如下:

boolean Vertical(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; if(a.y!=b.y) return false; int bg=Math.min(a.x, b.x),end=Math.max(a.x, b.x); for(int i=bg+1;i<end;++i) if(Map[i][a.y]==true) return false; return true; }

3.一个拐角检测
一个拐角检测可分解为水平检测和垂直检测,当两个同时满足时,便两点可通过一个拐角相连。即:

一个拐角检测 = 水平检测 && 垂直检测

3.png

A 点至 B 点能否连接可转化为满足任意一点:

A 点至 C 点的垂直检测,以及 C 点至 B 点的水平检测;
A 点至 D 点的水平检测,以及 D 点至 B 点的垂直检测。
代码如下:

boolean TurnOnce(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; Point temp1 = new Point(a.x,b.y),temp2 = new Point(b.x,a.y); if(Map[a.x][b.y]==false&&Horizen(a,temp1)==true&&Vertical(b,temp1)==true) return true; if(Map[b.x][a.y]==false&&Horizen(b,temp2)&&Vertical(a,temp2)) return true; return false; }

4.两个挂角检测

两个拐角检测可分解为一个拐角检测和水平检测或垂直检测。即:

两个拐角检测 = 一个拐角检测 && (水平检测 || 垂直检测)

4.png

如图,水平、垂直分别穿过 A B 共有四条直线,扫描直线上所有不包含 A B 的点,看是否存在一点 C ,满足以下任意一项:

A 点至 C 点通过水平或垂直检测,C 点至 B 点可通过一个拐角连接。(图中用 C 表示)
A 点至 C 点可通过一个拐角连接,C 点至 B 点通过水平或垂直连接。(图中用 C 下划线表示)
代码如下:

boolean TurnTwice(Point a,Point b) { if(a.x==b.x&&a.y==b.y) return false; for(int i=0;i<Rows+2;++i) { for(int j=0;j<Cols+2;++j) { if((i==a.x&&j==a.y)||(i==b.x&&i==b.y)) continue; if(Map[i][j]==true) continue; if(TurnOnce(a,new Point(i,j))&&(Horizen(b,new Point(i,j))||Vertical(b,new Point(i,j)))) return true; if(TurnOnce(b,new Point(i,j))&&(Horizen(a,new Point(i,j))||Vertical(a,new Point(i,j)))) return true; } } return false; }

5.最终的检验算法

boolean Judge(Point a,Point b) { if(Horizen(a,b)==true) return true; if(Vertical(a,b)==true) return true; if(TurnOnce(a,b)==true) return true; if(TurnTwice(a,b)==true) return true; return false; }

笔者不才,由于制作时间比较短,而且没有其他同伴的测试,代码中可能会有BUG,代码仅供参考!!

## 如果你觉得此篇文章对您有帮助,麻烦您点个赞嘿嘿,如果你有更多的问题,可以联系我,我很乐意一起解决。

源代码及其图片链接


作者:风萧萧兮易水寒丶



植物 僵尸 植物大战僵尸 JAVA

需要 登录 后方可回复, 如果你还没有账号请 注册新账号