该项目是跟着这个b站视频一步一步写出来的,初学java有些地方我看不是很明白,但是讲解很仔细,大家可以看原视频,我没有添加背景音乐和背景图片,做出来的效果也勉勉强强。
代码已经上传到github上了,大家可以去github上直接下载代码,附上链接:点击进入github源码链接
嫌麻烦进不去github的,我直接附上HTTPS链接和SSH,自己git clone就行
HTTPS: git clonehttps://github.com/19138060480/Genshin-elimination-game.git
SSH: git clonegit@github.com:19138060480/Genshin-elimination-game.git
先上效果图:
因为懒得找原图,手边又刚好有原神的七元素图片,所以就用七元素做成了简易版的《原了个原》
当元素在放满七个后会判断游戏失败,每次图片是随机的(但是可以保证能消除完)后面灰色的图片是被遮盖的图片,无法点击(指点击后不会消失),当前面覆盖的图片被点击消失后才会恢复彩色图片(即可点击状态)。
效果图就先到这里,在上源码之前先看一下我创建的包和类,不想修改乱七八糟的东西的宝子们可以跟我设置相同的包名和类名,省的麻烦。(亲测,修改后可能会出现近百个错误);
特别注意的是,imgs里面的图片只要命名正确就可以了,不需要原图(指可以百度),后面加Gray的图片是灰色的,我用ps一个一个把图片拖进去变成灰色你知道有多累吗?(其实也可以通过代码…….)
源码来咯:
首先是BeginGame包里面的Play:
package BeginGame;import Model.Brand;import Model.Cell;import Model.Layer;import Model.Map;import Tool.MapUtil;import javax.swing.*;import java.util.List;//测试渲染一个地图public class Play extends JFrame {public static Map map= MapUtil.build(3);public Play(){/*初始化窗口基本信息*/inti();/*渲染图层*/List list=map.getList();for (int i = 0; i < list.size(); i++) {renderLayer(list.get(i));}map.compareAll(); //判定游戏开始时,所有牌是灰色还是彩色/*自动刷新*/autoRefresh();//自动刷新线程}private void renderLayer(Layer layer){/*渲染图层默认情况下 brand牌的左上角坐标是0,0需要改变牌的坐标设置布局方式,默认swing 添加组件 提供了多种布局方式网格、流线、、、绝对布局 */Cell[][] cells=layer.getCells();layer.showCell();for (int row = 0; row < cells.length; row++) {for (int col = 0; col < cells[row].length; col++) {Brand brands1 = cells[row][col].getBrand();int x=col*50+layer.getOffsetx();//加上了偏移量int y=row*50+layer.getOffsety(); //设置xy坐标brands1.setBounds(x,y,50,50);this.getContentPane().add(brands1);}System.out.println();}}private void autoRefresh(){ //自动刷新方法JFrame main=this;new Thread(new Runnable() {@Overridepublic void run() { //一直执行刷新页面while(true){main.repaint();try {Thread.sleep(40); //40毫秒刷新一次}catch (InterruptedException e){e.printStackTrace();}}}}).start();}private void inti(){this.setTitle("羊了个羊");//标题this.setSize(450,800);//设置窗口大小this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭窗口的同时也关闭进程/*设置绝对布局*/this.setLayout(null);this.setBounds(0,0,450,800);this.setLocationRelativeTo(null); //窗口居中this.setVisible(true);//窗口显示(默认关闭)}public static void main(String[] args) {new Play();}}
然后是Model包
Brand:
package Model;import BeginGame.Play;import java.awt.*;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;/*游戏中的牌*/public class Brand extends Component {//牌类/*牌基础信息*/private String name;//名称private boolean isGray;//判断当前的牌是否是在牌堆下private Image image;//正常图片private Image grayimage; //牌堆下灰色图片/*坐标*/private Integer x;//代表在渲染的时候左上角的坐标private Integer y;/*宽高度*/private Integer width;private Integer height;/**/private Cell cell;EliminateBox eliminateBox =new EliminateBox();public Brand(String name){//含name参数构造this.name=name;/*通过name的值 对应图片目录下的图片前缀*/this.image = Toolkit.getDefaultToolkit().getImage("imgs\\"+name+".png");//通过name找到图片this.grayimage=Toolkit.getDefaultToolkit().getImage("imgs\\"+name+"Gray.png");//通过name找到灰色图片this.isGray=false; //默认不置灰/*设置宽高*/this.width=50;this.height=50;/*设置默认坐标*/this.x=0;this.y=0;this.addMouseListener(new MouseAdapter() {//鼠标监听器@Overridepublic void mouseClicked(MouseEvent e) {//点击鼠标System.out.println("点击鼠标");Brand brand=(Brand) e.getSource();//点击后获取当前组件 强制下转型(默认是object)if (brand.getGray()){//灰色return;}else {/*只在页面中删除了brand对象,但是cell状态中的state和brand没有删除*///brand.getParent().remove(brand);//调用上层容器删除自己一般树形结构使用这样的方式eliminateBox.addBox(brand);/*解决问题关键是纪要删除UI当中的组件,还要删除数据模型中的数据和对应状态*/cell.setState(0);cell.setBrand(null);Play.map.compareAll();//涉及到map对象的共享处理:把map对象设为静态变量}}});}@Overridepublic void paint(Graphics g) { //重写Component的paint方法(绘制函数)if(this.isGray==true){//通过控制isGray来控制图片的灰色和彩色//绘制灰色图片g.drawImage(this.grayimage,this.x,this.y,null);}else {//绘制正常图片g.drawImage(this.image,this.x,this.y,null);}}/* set/get */public String getName() {return name;}public void setName(String name) {this.name = name;}public boolean isGray() {return isGray;}public void setGray(boolean gray) {isGray = gray;}public boolean getGray(){return isGray;}public Image getImage() {return image;}public void setImage(Image image) {this.image = image;}public Image getGrayimage() {return grayimage;}public void setGrayimage(Image grayimage) {this.grayimage = grayimage;}public Cell getCell() {return cell;}public void setCell(Cell cell) {this.cell = cell;}
Cell:
package Model;/*单元格类有两种状态, 0:无牌,1:有牌; */public class Cell {private Integer state;//0、1privateBrand brand;/* get/set方法 */public Integer getState() {return state;}public void setState(Integer state) {this.state = state;}public Brand getBrand() {return brand;}public void setBrand(Brand brand) {this.brand = brand;}}
EliminateBox:
package Model;import javax.swing.*;import java.util.*;import java.util.Map;import java.util.stream.Collectors;public class EliminateBox { //消除区域private static List Box=new ArrayList(); //存放消除牌的数据/*迭代器清空集合方法*/void deleteByBrandName(String name){Iterator iterator = Box.iterator();while (iterator.hasNext()){ //如果有下一个值Brand next=iterator.next(); //获取下一个值if (next.getName().equals(name)){next.getParent().remove(next);iterator.remove();}}}public void addBox(Brand brand){//添加到消除区方法Box.add(brand);/*牌的排序(根据名称)*/Box.sort(Comparator.comparing(Brand::getName));/*消除算法*/Map<String, List> map= Box.stream().collect(Collectors.groupingBy(Brand::getName));//获取牌的名称Set key=map.keySet();for (String s:key){List brands=map.get(s);if (brands.size()==3){deleteByBrandName(s); //调用迭代器消除方法break;}}paint();//调用方法 绘制到消除区over(brand);//调用方法 判断游戏结束}void paint(){ //绘制到消除区for (int i = 0; i =7){ //判断游戏结束JOptionPane.showMessageDialog(brand,"游戏失败");System.exit(0);}}}
Layer:
package Model;import java.util.Random;/*图层类二维表格 */public class Layer {private Integer offsetx;/*设置偏移量*/ //x轴privateInteger offsety; //偏移量 y轴private Integer rowNum; //有多少行(行数)private Integer colNum; //有多少列(列数)private Integer capacity; //当前图层能最多容纳的牌数量 最大容量private Integer size; //图层 目前有多少牌 当牌添加的时候,需要改变值、当牌减少的时候,也需要改变值private Layer parent; //上一层图层对象private Cell[][] cells =null;public Layer(Integer rowNum, Integer colNum) throws Exception{//构造函数,传递参数为行号和列号(因为其他属性都可以通过行号列号知道)this.rowNum = rowNum;this.colNum = colNum;this.capacity= this.rowNum * this.colNum; //容量为行数×列数if(this.capacity%3!=0){throw new Exception("容量不是3的倍数");}this.cells=new Cell[this.rowNum][this.colNum];//因为传递的是行号和列号,所以可以通过行号和列号创建对象.this.size=0;//默认为零this.offsetx=new Random().nextInt(100);this.offsety=new Random().nextInt(100); //用随机数设置偏移量}public void showCell(){for (int row = 0; row < cells.length; row++) {for (int col = 0; col < cells[row].length; col++) {Brand brands1= cells[row][col].getBrand();System.out.print(brands1.getName()+"-");}System.out.println();}}/* get/set方法 */public Layer getParent() {return parent;}public void setParent(Layer parent) {this.parent = parent;}public Integer getOffsety() {return offsety;}public void setOffsety(Integer offsety) {this.offsety = offsety;}public Integer getOffsetx() {return offsetx;}public void setOffsetx(Integer offsetx) {this.offsetx = offsetx;}public Integer getRowNum() {return rowNum;}public void setRowNum(Integer rowNum) {this.rowNum = rowNum;}public Integer getColNum() {return colNum;}public void setColNum(Integer colNum) {this.colNum = colNum;}public Integer getCapacity() {return capacity;}public void setCapacity(Integer capacity) {this.capacity = capacity;}public Integer getSize() {return size;}public void setSize(Integer size) {this.size = size;}public Cell[][] getCells() {return cells;}public void setCells(Cell[][] cells) {this.cells = cells;}}
Map:
package Model;import Tool.MapUtil;import java.util.ArrayList;import java.util.List;//图层public class Map {private Integer floorHeight;//层高有几个图层private List list=new ArrayList(); //存放图层数据/* set/get */public Integer getFloorHeight() {return floorHeight;}public void setFloorHeight(Integer floorHeight) {this.floorHeight = floorHeight;}public List getList() {return list;}public void setList(List list) {this.list = list;}/*判断当前map中所有牌是否置灰*/public void compareAll(){System.out.println("map.compareAll");//i=0是最顶层layer,不需要判断for (int i = 1; i < list.size(); i++) {Layer layer=list.get(i);Cell[][] cells=layer.getCells();for (int row = 0; row < cells.length; row++) {for (int col = 0; col < cells[row].length; col++) {Cell cell=cells[row][col];//先拿到单元格对象if (cell.getState()==1){Brand brand = cell.getBrand();//有牌取牌boolean result=MapUtil.compare(brand,layer.getParent());brand.setGray(result);}}}}}}
然后是Tool工具包:
BrandUtil:
package Tool;import Model.Brand;import java.util.Random;/*工具类提供 创建牌相关的一些公共方法 */public class BrandUtil {public static Random random =new Random();publicstaticString[] brandNames= {"风","岩","雷","草","水","火","冰"}; //牌名数组public static String getBrandName(){//随机获取一个牌的名称int a=random.nextInt(brandNames.length);return brandNames[a];}//创建随机牌public static Brand[] buildBrands(Integer capcity){//需要参数(数组大小)Brand brands[]=new Brand[capcity];for (int i = 0; i < brands.length; i=i+3) {String randomBrandName=getBrandName();//每次循环获取一个随机牌名称Brand brand1=new Brand(randomBrandName); //名称传递,创建新的对象Brand brand2=new Brand(randomBrandName);Brand brand3=new Brand(randomBrandName);//创建三个相同的对象,方便删除牌brands[i]=brand1;brands[i+1]=brand2;brands[i+2]=brand3;}for (int i = 0; i < brands.length; i++) {//当前位置A的变量拿到Brand brandA = brands[i];//随机交换位置int randomIndex=random.nextInt(brands.length);Brand brandB=brands[randomIndex];Brand temp=brandA;brands[i]= brandB;brands[randomIndex]=temp; //交换}return brands;}}
LayerUtil:
package Tool;import Model.Brand;import Model.Cell;import Model.Layer;public class LayerUtil {public static Layer build(Integer rowNum, Integer colNum) {Layer layer = null;try {layer = new Layer(rowNum, colNum); //容量需要为3的倍数,不然下面for循环因为i=i+3时,数组越界会报错} catch (Exception e) {e.printStackTrace();}Brand[] brands=BrandUtil.buildBrands(layer.getCapacity());Cell cells[][] = layer.getCells();int flag=0;for (int row = 0; row < cells.length; row++) {for (int col = 0; col < cells[row].length; col++) {//System.out.println(row+"-"+col);Brand brands1=brands[flag++];Cell cell = new Cell(); //初始化单元格对象cell.setState(1);cell.setBrand(brands1); //单元格对象找到我们牌brands1.setCell(cell);//牌反向找到单元格对象, 互相链式关系cells[row][col] = cell; //把之前空的图层设置了值}}return layer;}}
MapUtil:
package Tool;import Model.Brand;import Model.Cell;import Model.Layer;import Model.Map;import java.awt.*;public class MapUtil {public static Map build(Integer floorHeight){Map map=new Map();map.setFloorHeight(floorHeight);Layer layer1= LayerUtil.build(3,5);Layer layer2= LayerUtil.build(9,4);Layer layer3= LayerUtil.build(6,4);Layer layer4= LayerUtil.build(6,9);Layer layer5= LayerUtil.build(3,3);Layer layer6= LayerUtil.build(10,9);Layer layer7= LayerUtil.build(3,8);Layer layer8= LayerUtil.build(9,6);Layer layer9= LayerUtil.build(6,9);Layer layer10= LayerUtil.build(6,12);Layer layer11= LayerUtil.build(6,5);Layer layer12= LayerUtil.build(4,9);Layer layer13= LayerUtil.build(5,9);Layer layer14= LayerUtil.build(7,9);Layer layer15= LayerUtil.build(9,9);Layer layer16= LayerUtil.build(2,9);Layer layer17= LayerUtil.build(5,9);Layer layer18= LayerUtil.build(4,9);layer1.setParent(null); //parent为null时已经是最后一层了 是循环递归结束的条件layer2.setParent(layer1);layer3.setParent(layer2); //用链式关系把图层锁起来layer4.setParent(layer3);layer5.setParent(layer4);layer6.setParent(layer5);layer7.setParent(layer6);layer8.setParent(layer7);layer9.setParent(layer8);layer10.setParent(layer9);layer11.setParent(layer10);layer12.setParent(layer11);layer13.setParent(layer12);layer14.setParent(layer13);layer15.setParent(layer14);layer16.setParent(layer15);layer17.setParent(layer16);layer18.setParent(layer17);map.getList().add(layer1);map.getList().add(layer2);map.getList().add(layer3);map.getList().add(layer4);map.getList().add(layer5);map.getList().add(layer6);map.getList().add(layer7);map.getList().add(layer8);map.getList().add(layer9);map.getList().add(layer10);map.getList().add(layer11);map.getList().add(layer12);map.getList().add(layer13);map.getList().add(layer14);map.getList().add(layer15);map.getList().add(layer16);map.getList().add(layer17);map.getList().add(layer18);return map;}public static boolean compare(Brand brand, Layer layer){//判断当前牌和某一图层所有牌是否有矩阵交集,ture表示有交集,显示灰色,false表示没有交集,显示正常牌Cell cells[][]=layer.getCells();for (int row = 0; row < cells.length; row++) {for (int col = 0; col < cells[row].length; col++) {//如果当前单元格为空,cell不用比较Cell cell=cells[row][col];if(cell.getState()==1){//单元格有牌,可以比较Rectangle temp=cell.getBrand().getBounds();Rectangle rect=brand.getBounds();boolean result=rect.intersects(temp); //布尔类型判断是否有交集if (result){//有交集说明被上层牌盖住了return result;//判定结束,结束方法}}}System.out.println();}/*如果跳出了上面的循环,说明都没有交集,需要和更上层进行对比*/if (layer.getParent()!=null){return compare(brand,layer.getParent());//递归判定}else{//如果getparent等于null,说明已经到最顶层了return false;}}}
好了只用这些就能完整的运行啦,细心的人可能发现了我没有发Test包里的东西 ,因为那只是用来做测试的!!!不过如果想要的话私聊我,我看看能不能把整个压缩包发给你…….
我这上面注释写的应该还算清楚,应该不难理解吧,不过为了以防万一,我还是说一下一些关键可修改的地方
首先是MapUtil类里面的,这个包是不是看到很多重复的代码块(不是为了凑代码行数!!)
这是为了创建不同的图层,这个游戏可以理解为有很多图层叠在一起,然后这里就是设置图层的个数,每创建一组就多一层图层。
这一行是设置图层的长rowNum和宽colNum的(注意长和宽的乘积一定要是3的倍数,最简单的方法就是其中一个是3或者3的倍数,不然会报错,因为在LayerUtil类里设置了捕获异常,捕获的是BrandUtil类里,因为Brand对象是三个三个创建的,如果不是三的倍数会出现数组越界)
这一行是为了让后一个图层把上一层图层联系起来,层与层之间互相关联,比如这个图就是layer3与layer2锁在一起,让layer3的父层为layer2,如果要新添加图层的话,新添加的图层也需要把上一层设置为父层。
这一条代码是为了让图层显示出来,如果没有这一行,你的图层将不会渲染到游戏里,add括号内写哪一层就渲染哪一层。
这三条语句可以看为一个整体,如果要添加或者删除图层的话,只需要修改对应的这三条语句就可以了。
然后是BrandUtil类里
brandNames数组里是你的图片名称,你可以随意修改(前提是找得到图片),图片在imgs文件夹下,和Total_class是同级的,注意你也要创建一个imgs文件夹来存放图片,不然就进入brand类里面,把这个imgs改成你文件的名字
特别说一下,设置的图层偏移量是在Play类里面,写的有注释,可以自行查找也可以不改,还有就是每个图层的长宽比不要太大,不然会出现你不想看到的结果(指bug)。
其他的有注释,能看懂的应该都能看懂,看不懂的那我也没办法,照抄就好了。