背景
最近爆火的消消游戏 大家应该都玩过, 有没有想过用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 ,不然会遇见图片空指针问题
由于开发时间较短,大概用了不到一上午时间,所以还有很多需要优化重构的地方,也当成练习题大家可以继续扩展优化,本篇主要也是抛砖引玉,有问题可以多留言交流