背景
最近爆火的消消游戏 大家应该都玩过,
有没有想过用java自己开发一个呢,我们玩的小程序是客户端,
首先加载地图才能玩,地图是在服务端编辑的,
所以地图是第一步,本篇我们就是开发一个简易版的地图编辑器
地图编辑器效果图
目录结构以及模块
关键模块以及代码分析
MainWindow.java
主窗口,也是程序入口,这里因为只写了地图编辑器代码,所以目前只负责启动地图编辑器,主要是用spring application runner 启动,目的是等所有comment 都被spring 管理之后在启动
@Component public class MainWindowStartRunner implements ApplicationRunner { @Autowired private MainWindow mainWindow; @Override public void run(ApplicationArguments args) throws Exception { mainWindow.init(); } }
DesignPanel.java
地图设计器主入口,将地图编辑器面板,按钮面板和图片选项卡面板放入:
public class DesignPanel extends JPanel { private final DesignMapPanel designMapPanel; private final DesignSpriteGroupPanel spriteGroupPanel; private final DesignButtonPanel buttonPanel; public void init() throws IOException { this.setLayout(new BorderLayout()); this.add(spriteGroupPanel,BorderLayout.WEST); this.add(designMapPanel,BorderLayout.CENTER); this.add(buttonPanel,BorderLayout.NORTH); } }
DesignMapPanel.java 是地图编辑器核心类,负责绘制网格,以及添加图像到地图中,关键代码图下
public DesignMapPanel() throws IOException { //获取总窗体宽高 GraphicsEnvironment ge=GraphicsEnvironment.getLocalGraphicsEnvironment(); Rectangle rect=ge.getMaximumWindowBounds(); int w=rect.width -230; int h=rect.height -104 ; this.setLayout(null); //设置滚动条 jLayeredPane = new JLayeredPane(); jLayeredPane.setBounds(0,0,w,h); jLayeredPane.setLayout(null); this.add(jLayeredPane); //设置初始化时的背景图片大小 windowWidth = jLayeredPane.getBounds().width; windowHeight = jLayeredPane.getBounds().height; bgImg = ImageIO.read(this.getClass().getResourceAsStream("/images/sprites/bg.jpeg")); bgLabel = new JLabel(new ImageIcon(bgImg.getScaledInstance(windowWidth,windowHeight,Image.SCALE_DEFAULT))); bgLabel.setBounds(0,0,windowWidth,windowHeight); this.jLayeredPane.add(bgLabel,JLayeredPane.DEFAULT_LAYER); //设置鼠标点击事件 this.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { addHeros(e.getX(), e.getY()); } }); }
@Override public void paint(Graphics g) { super.paint(g); Graphics2D graphics2D = (Graphics2D) g; graphics2D.setStroke(new BasicStroke(2.0f)); graphics2D.setColor(Color.black); //获取当前窗口宽高大小 windowWidth = this.getBounds().width; windowHeight = this.getBounds().height; //计算宽高间距 cpix = windowWidth / columns - 1; hpix = windowHeight / rows - 1; //绘制列线条 for (int i = 0; i < columns; i++) { graphics2D.drawLine(i * cpix, 0, i * cpix, windowHeight); } //绘制行线条 for (int i = 0; i < rows; i++) { graphics2D.drawLine(0, i * hpix, windowWidth, i * hpix); } }
private void addHeros(int x, int y) { //循环宽高,找到鼠标点击点最靠近的横竖交界点 for (int i = 1; i <= columns; i++) { for (int j = 1; j <= rows; j++) { int pointX = i * cpix; int pointY = j * hpix; //碰撞检测,判断鼠标点击点与哪行哪列最接近,并记录 Rectangle clickPoint = new Rectangle(pointX - cpix / 2, pointY - hpix / 2, cpix, hpix); boolean isIn = clickPoint.contains(x, y); if (isIn) { //找到了最近交界点行号和列号,创建对象并报存 HeroSprite heroSprite = new HeroSprite(i, j, HeroSelectContextHolder.currentHeroType()); HeroView heroView = new HeroView(heroSprite); Border blackline = BorderFactory.createRaisedBevelBorder(); heroView.setBorder(blackline); heroView.setIcon(new ImageIcon(heroView.getImage().getScaledInstance(cpix ,hpix*2,Image.SCALE_DEFAULT))); heroView.setBounds(i * cpix - cpix / 2, j * hpix - hpix, cpix, hpix * 2); heroViews.add(heroView); //将选择的图像放到找到的中心点位置 jLayeredPane.add(heroView,JLayeredPane.DRAG_LAYER); System.out.println("c:" + i + ",r:" + j); this.updateUI(); } } } }
addHeros 比较关键,使用碰撞检测找到对应最近的行号和列表,并将选择的图像放到他们的交界点处
HeroSelectContextHolder.java 主要负责左边图像选择卡和右边地图编辑器关联起来,主要是保存当前选择的是哪个图像
public class HeroSelectContextHolder { private static HeroType heroType = HeroType.HERO_TYPE_1; public static void selectHeroType(HeroType heroType){ HeroSelectContextHolder.heroType = heroType; } public static HeroType currentHeroType(){ return heroType; } }
HeroView.java 图像对象,主要是用于对绘制等一些功能提供方法
public class HeroView extends JLabel{ private static Image s1Img; private static Image s2Img; private static Image s3Img; private static Image s4Img; private static Image s5Img; private HeroSprite heroSprite; public HeroView(HeroSprite heroSprite) { this.heroSprite = heroSprite; initImages(); } private void initImages() { try { if (Objects.isNull(s1Img) || Objects.isNull(s2Img) || Objects.isNull(s3Img) || Objects.isNull(s4Img) ) { s1Img = ImageIO.read(this.getClass().getResourceAsStream("/images/sprites/1.png")); s2Img = ImageIO.read(this.getClass().getResourceAsStream("/images/sprites/2.png")); s3Img = ImageIO.read(this.getClass().getResourceAsStream("/images/sprites/3.png")); s4Img = ImageIO.read(this.getClass().getResourceAsStream("/images/sprites/4.png")); s5Img = ImageIO.read(this.getClass().getResourceAsStream("/images/sprites/5.png")); } } catch (Exception e) { e.printStackTrace(); } } public HeroSprite getHeroSprite() { return heroSprite; } public Image getImage() { if (heroSprite.getHeroType() == HeroType.HERO_TYPE_1) { return s1Img; } if (heroSprite.getHeroType() == HeroType.HERO_TYPE_2) { return s2Img; } if (heroSprite.getHeroType() == HeroType.HERO_TYPE_3) { return s3Img; } if (heroSprite.getHeroType() == HeroType.HERO_TYPE_4) { return s4Img; } if (heroSprite.getHeroType() == HeroType.HERO_TYPE_5) { return s5Img; } return null; } }
HeroSprite.java 图像内部逻辑对象,主要负责保存逻辑内容
@AllArgsConstructor @Getter public class HeroSprite { private int col = 0; private int row = 0; private HeroType heroType; public boolean isIn(int x,int y){ return this.col==x&& row==y; } }
HeroType.java 图像类型,这里具体对应左边某个选择的图像
public enum HeroType { HERO_TYPE_1(1), HERO_TYPE_2(2), HERO_TYPE_3(3), HERO_TYPE_4(4), HERO_TYPE_5(5); private int type ; HeroType(int type ){ this.type = type; } public int getType() { return type; } }
DesignButtonPanel.java 负责上面的按钮操作面板,目前地图选择页面里只有选择某个关卡并保存,将数据和管卡保存起来并生成json文件
this.saveButton.addActionListener(e -> { //选择哪个关卡 Integer selectLevel = Integer.parseInt(this.levels.getSelectedItem().toString()); //获取地图数据 HeroMapData heroMapData = HeroMapData.builder().heroSprites(designMapPanel.heroSprites()) .level(selectLevel).build(); String mapJson = JSONUtil.toJsonStr(heroMapData); //获取当前项目目录 String usrDir = System.getProperty("user.dir"); //按照关卡将地图数据保存 File mapFile = new File(usrDir+File.separator+"map"+File.separator+selectLevel+".json"); FileUtil.writeBytes(mapJson.getBytes(),mapFile); });
点击保存后可以到当前项目目录下找到map/#{选择的管卡}.json文件,如下:
我地图编辑后选择了管卡1,所有可以看见1.json 内容如上
注意事项
第一次运行或者 reources 下增删改图片记得都要maven clean ,然后compile ,不然会遇见图片空指针问题
由于开发时间较短,大概用了不到一上午时间,所以还有很多需要优化重构的地方,也当成练习题大家可以继续扩展优化,本篇主要也是抛砖引玉,有问题可以多留言交流