Processing(Java)游戏开发:线条绘制与点放置的高效实现问询
Processing像素级曲线绘制游戏解决方案
核心问题解答
a) 避免线条自交叉/与已有线条交叉
你之前用brightness(pixels[i])检查单个像素的方式不可靠——因为设置了strokeWeight(3),line()函数会覆盖多个像素,但你只校验了其中一个,导致交叉处漏判。
高效解决方式:
- 用二维布尔数组记录所有已占用像素(屏幕尺寸固定,数组索引访问是O(1),比ArrayList/HashSet性能更优)。
- 绘制曲线时,先通过算法生成当前鼠标拖动路径上的所有像素点,逐个检查这些点是否未被占用,只有全部点都可用时才绘制并标记为已占用。
b) 仅允许在最后一条线上放置点位
必须为每条线条单独维护像素集合。可以用ArrayList<ArrayList<PVector>>存储所有已完成的线条,每次新线条绘制完成后,将其像素集合添加到这个全局列表中。放置点位时,只需检查当前鼠标坐标是否存在于全局列表的最后一个元素(即最后一条线的像素集合)中即可。
高效实现方案
数据结构选择
- 全局占用标记:
boolean[][] occupied,直接通过occupied[y][x]判断坐标(x,y)是否已被线条占用,性能最优。 - 线条像素存储:
ArrayList<PVector>存储单条线的所有像素,ArrayList<ArrayList<PVector>>存储所有线条,便于后续点位校验和管理。
关键算法:Bresenham直线生成算法
替代Processing自带的line()函数,手动生成两点之间的所有像素点,精确控制每个像素的检查与绘制,彻底解决交叉漏洞问题。
修复后的代码示例
boolean[][] occupied; ArrayList<PVector> currentLine; ArrayList<ArrayList<PVector>> allLines; boolean isDrawing = false; void setup() { size(600, 400); background(0); stroke(255); strokeWeight(1); // 改为1,如需粗线可扩展Bresenham算法支持 // 初始化数据结构 occupied = new boolean[height][width]; allLines = new ArrayList<>(); currentLine = new ArrayList<>(); } void draw() { // 重绘所有线条 background(0); // 绘制已完成的线条 for (ArrayList<PVector> line : allLines) { for (PVector p : line) { point(p.x, p.y); } } // 绘制当前正在绘制的线条 for (PVector p : currentLine) { point(p.x, p.y); } } void mousePressed() { // 检查点击位置是否未被占用,开启新线条 if (mouseX >= 0 && mouseX < width && mouseY >=0 && mouseY < height) { if (!occupied[mouseY][mouseX]) { isDrawing = true; PVector start = new PVector(mouseX, mouseY); currentLine.add(start); occupied[mouseY][mouseX] = true; } } } void mouseDragged() { if (!isDrawing) return; // 获取上一个点和当前点 PVector lastPoint = currentLine.get(currentLine.size() - 1); ArrayList<PVector> pathPixels = getLinePixels(lastPoint.x, lastPoint.y, mouseX, mouseY); // 校验路径上所有像素是否未被占用 boolean canDraw = true; for (PVector p : pathPixels) { if (p.x < 0 || p.x >= width || p.y <0 || p.y >= height || occupied[(int)p.y][(int)p.x]) { canDraw = false; break; } } // 允许绘制则添加像素并标记占用 if (canDraw) { currentLine.addAll(pathPixels); for (PVector p : pathPixels) { occupied[(int)p.y][(int)p.x] = true; } } } void mouseReleased() { if (isDrawing && currentLine.size() > 1) { allLines.add(currentLine); } // 重置当前线条状态 currentLine = new ArrayList<>(); isDrawing = false; } // 点击放置点位(仅在最后一条线上有效) void mouseClicked() { if (allLines.isEmpty()) return; ArrayList<PVector> lastLine = allLines.get(allLines.size() - 1); PVector clickPos = new PVector(mouseX, mouseY); // 允许1像素误差,提升操作容错性 for (PVector p : lastLine) { if (dist(p.x, p.y, clickPos.x, clickPos.y) <= 1) { fill(255, 0, 0); noStroke(); ellipse(p.x, p.y, 5, 5); stroke(255); fill(0); break; } } } // Bresenham算法生成两点之间的所有像素 ArrayList<PVector> getLinePixels(int x1, int y1, int x2, int y2) { ArrayList<PVector> pixels = new ArrayList<>(); int dx = abs(x2 - x1); int dy = abs(y2 - y1); int sx = (x1 < x2) ? 1 : -1; int sy = (y1 < y2) ? 1 : -1; int err = dx - dy; int x = x1; int y = y1; while (true) { // 跳过起点(已添加到currentLine) if (!(x == x1 && y == y1)) { pixels.add(new PVector(x, y)); } if (x == x2 && y == y2) break; int e2 = 2 * err; if (e2 > -dy) { err -= dy; x += sx; } if (e2 < dx) { err += dx; y += sy; } } return pixels; }
代码说明
- 用
Bresenham算法精确生成鼠标拖动路径上的所有像素,确保每个像素都经过占用检查,彻底避免交叉漏洞。 - 分
currentLine(当前绘制中)和allLines(已完成线条)两个集合,清晰管理最后一条线的点位放置逻辑。 - 点击放置点位时加入1像素误差判断,提升用户操作的容错性。
内容的提问来源于stack exchange,提问作者Ingo
相关产品推荐
相关产品推荐

