如何在JavaFX中无缝绘制由网格方块组成的网格?
迷宫网格无缝拼接问题
我正在编写迷宫生成算法,想用JavaFX(ScalaFX)绘制生成的迷宫。先尝试用小型形状组成简易网格,后续转成迷宫样式。用左上角拐角形状(如┏)搭配GridPane实现,但单元格间出现细微缝隙。代码用Scala编写,参考JavaFX文档开发,想请教:
- 如何实现网格的无缝拼接?
- 使用GridPane的思路是否正确?
用户提供的原始代码:
val lineLength: Int = 30 def makeClosedCell(length: Int = lineLength): Shape = { val wallN = Line(0,0,length,0) val wallW = Line(0,0,0,length) val closedCell: Shape = Shape.union(wallN, wallW) return closedCell } def makeOpenW(length: Int = lineLength): Shape = Line(0,0,length,0) def makeOpenN(length: Int = lineLength): Shape = Line(0,0,0,length) def initialiseGrid(r: GridPane, sizex: Int, sizey: Int): Unit = { for (i <- 0 until sizex) { val colConst = new ColumnConstraints() colConst.setPercentWidth(100.0 / sizex) r.getColumnConstraints().add(colConst) } for (i <- 0 until sizey) { val rowConst = new RowConstraints() rowConst.setPercentHeight(100.0 / sizey) r.getRowConstraints().add(rowConst) } for(j <- sizey-1 to 0 by -1){ for(i <- 0 until sizex){ r.add(makeClosedCell(),i,j) } r.add(makeOpenN(),sizex,j) } for(i <- 0 until sizex){ r.add(makeOpenW(),i,sizey) } }
问题解析与解决方案
1. GridPane的思路是否正确?
GridPane用来布局网格结构是合理的,但你的实现存在两个核心问题:
- 相邻单元格的共享边被重复绘制,叠加后容易因布局精度出现视觉缝隙
- 使用百分比宽高会引入浮点计算误差,导致单元格边界对齐不准
2. 无缝拼接的实现方案
核心调整方向
- 避免重复绘制共享边,每个单元格只负责自己独有的边
- 用固定宽高替代百分比布局,消除浮点精度误差
- 设置线条绘制规则,确保边缘不会超出单元格边界
调整后的ScalaFX代码示例
import scalafx.scene.shape.{Line, Shape, StrokeType} import scalafx.scene.layout.{GridPane, ColumnConstraints, RowConstraints} import scalafx.geometry.Pos val lineLength: Int = 30 // 左上角单元格:绘制左、上两条边 def makeTopLeftCell(length: Int = lineLength): Shape = { val topWall = Line(0, 0, length, 0) val leftWall = Line(0, 0, 0, length) topWall.setStrokeType(StrokeType.INSIDE) leftWall.setStrokeType(StrokeType.INSIDE) Shape.union(topWall, leftWall) } // 第一行非首列单元格:仅绘制上边 def makeTopWall(length: Int = lineLength): Shape = { val wall = Line(0, 0, length, 0) wall.setStrokeType(StrokeType.INSIDE) wall } // 第一列非首行单元格:仅绘制左边 def makeLeftWall(length: Int = lineLength): Shape = { val wall = Line(0, 0, 0, length) wall.setStrokeType(StrokeType.INSIDE) wall } // 内部单元格:绘制右、下两条边(后续可根据迷宫逻辑动态移除) def makeInnerCellWalls(length: Int = lineLength): Shape = { val rightWall = Line(length, 0, length, length) val bottomWall = Line(0, length, length, length) rightWall.setStrokeType(StrokeType.INSIDE) bottomWall.setStrokeType(StrokeType.INSIDE) Shape.union(rightWall, bottomWall) } def initialiseGrid(r: GridPane, sizex: Int, sizey: Int): Unit = { // 清除单元格间距,设置顶左对齐 r.setHgap(0) r.setVgap(0) r.setAlignment(Pos.TOP_LEFT) // 设置固定宽度的列约束 for (_ <- 0 until sizex) { val colConst = new ColumnConstraints() colConst.setPrefWidth(lineLength) r.getColumnConstraints.add(colConst) } // 设置固定高度的行约束 for (_ <- 0 until sizey) { val rowConst = new RowConstraints() rowConst.setPrefHeight(lineLength) r.getRowConstraints.add(rowConst) } // 填充网格单元格 for (j <- 0 until sizey) { for (i <- 0 until sizex) { val cell = (i, j) match { case (0, 0) => makeTopLeftCell() case (0, _) => makeLeftWall() case (_, 0) => makeTopWall() case _ => makeInnerCellWalls() } r.add(cell, i, j) } } // 添加最右侧的垂直边界 for (j <- 0 until sizey) { val wall = Line(0, 0, 0, lineLength) wall.setStrokeType(StrokeType.INSIDE) r.add(wall, sizex, j) } // 添加最底部的水平边界 for (i <- 0 until sizex) { val wall = Line(0, 0, lineLength, 0) wall.setStrokeType(StrokeType.INSIDE) r.add(wall, i, sizey) } }
关键调整说明
- 固定尺寸布局:用
setPrefWidth/setPrefHeight替代百分比,避免浮点计算带来的对齐误差 - StrokeType.INSIDE:让线条在单元格内部绘制,不会超出边界导致视觉缝隙
- 消除重复绘制:每个单元格只负责自己的非共享边,避免线条叠加产生的间隙
- 清除间距:设置
Hgap和Vgap为0,确保单元格完全贴合
内容的提问来源于stack exchange,提问作者user18898152
相关产品推荐
相关产品推荐

