台球游戏
概述
实现一个桌球游戏。桌上有6个球袋,球可以落入其中并计分。游戏开始时,所有球放在桌上的某个位置,其中有一个白球,玩家只能击打白球,并让白球撞击其他颜色的球。如果球撞击桌子边缘,则反弹。球可以撞击其他球,并传递动量。该游戏为单人游戏,胜利条件为除白球外的所有球均已入袋,失败条件为白球入袋。
需求
- 使用工厂模式读取并处理配置文件的各部分
- 使用建造者模式创建球
- 使用策略模式控制球落入袋之后的行为
- 球桌的尺寸,颜色和摩擦力可配置
- 球的颜色,初始位置/速度,质量可配置
- 实现球在碰撞其他球和桌面边缘的物理效果
- 实现球在桌面上由于摩擦力而减速
- 实现胜利和失败条件
- 击球控制:可以使用鼠标控制击打白球,点击白球并拖拽以控制方向和力度,释放鼠标以击球。只有在白球静止时可以击球。击球控制需有相应的图形指示。
- 当前仅考虑红球和蓝球,红球入袋后即消失,蓝球第一次入袋后回到初始位置,第二次入袋后消失
- 代码需要有良好的注释
工厂模式处理配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class ConfigReaderFactory { public ConfigReader createConfigReader(String fileType) { if ("ball".equalsIgnoreCase(fileType)) { return new BallConfigReader(); } else if ("table".equalsIgnoreCase(fileType)) { return new TableConfigReader(); } return null; } }
public interface ConfigReader<T> { T readConfig(String filePath); }
public class BallConfigReader implements ConfigReader<BallConfig> {
public BallConfig readConfig(String filePath) { BallConfig ballconfig = new BallConfig(); try{ String content = new String(Files.readAllBytes(Paths.get(filePath))); JSONObject json = new JSONObject(content); ballconfig.setCueBallColor(JsonUtils.getVector3D(json,"cueBallColor")); ballconfig.setCueBallPos(JsonUtils.getVector2D(json,"cueBallPos")); ballconfig.setMass(json.getDouble("mass")); ballconfig.setBlueBallPos(JsonUtils.getVector2DArray(json,"blueBallPos")); ballconfig.setRedBallPos(JsonUtils.getVector2DArray(json,"redBallPos")); } catch (Exception e) { e.printStackTrace(); } return ballconfig; } }
public class TableConfigReader implements ConfigReader<TableConfig> {
public TableConfig readConfig(String filePath) { TableConfig tableConfig = new TableConfig(); try{ String content = new String(Files.readAllBytes(Paths.get(filePath))); JSONObject json = new JSONObject(content); tableConfig.setColor(JsonUtils.getVector3D(json,"color")); tableConfig.setSize(JsonUtils.getVector2D(json,"size")); tableConfig.setFriction(json.getDouble("friction")); } catch (Exception e) { e.printStackTrace(); } return tableConfig; } }
|
建造者模式创建球
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public class BallBuilder { private BallConfig ballConfig; public BallBuilder(BallConfig ballconfig) { this.ballConfig = ballconfig; }
public Ball buildCueBall(Group root) { Ball ball = new Ball(); ball.setColor(ballConfig.getCueBallColor().toColor()); ball.setPos(ballConfig.getCueBallPos()); ball.setID(0); ball.setHP(1); root.getChildren().add(ball.getCircle()); return ball; }
public Ball buildVirtualBall(Group root) { Ball ball = new Ball(); ball.getCircle().setFill(Color.TRANSPARENT); ball.getCircle().setStroke(Color.BLACK); ball.getCircle().setVisible(false); root.getChildren().add(ball.getCircle()); return ball; }
public ArrayList<Ball> initRedBalls(Group root) { ArrayList<Ball> balls = new ArrayList<>(); int blueNum = ballConfig.getBlueBallPos().size(); int redNum = ballConfig.getRedBallPos().size(); for(int i=blueNum+1; i<=1+blueNum+redNum-1; i++) { Ball ball = buildRedBall(root, i); balls.add(ball); } return balls; }
public ArrayList<Ball> initBlueBalls(Group root) { ArrayList<Ball> balls = new ArrayList<>(); int blueNum = ballConfig.getBlueBallPos().size(); for(int i=1; i<=1+blueNum-1; i++) { Ball ball = buildBlueBall(root, i); balls.add(ball); } return balls; } public Ball buildBlueBall(Group root, int num) { Ball ball = new Ball(); ball.setColor(Color.BLUE); ball.setPos(ballConfig.getBlueBallPos().get(num-1)); ball.setID(num); ball.setHP(1); root.getChildren().add(ball.getCircle()); return ball; } public Ball buildRedBall(Group root, int num) { Ball ball = new Ball(); ball.setColor(Color.RED); ball.setPos(ballConfig.getRedBallPos().get(num-3)); ball.setID(num); root.getChildren().add(ball.getCircle()); return ball; } }
|
策略者模式控制球入袋后行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public interface BallBehaviorStrategy { void handleBallInPocket(Group root, Ball ball, BallConfig ballConfig); }
public class RemoveBallStrategy implements BallBehaviorStrategy{ @Override public void handleBallInPocket(Group root, Ball ball, BallConfig ballConfig) { root.getChildren().remove(ball.getCircle()); } }
public class ResetBallStrategy implements BallBehaviorStrategy{ @Override public void handleBallInPocket(Group root, Ball ball, BallConfig ballConfig) { int id = ball.getID(); if(id == 0) { ball.setPos(ballConfig.getCueBallPos()); ball.setVelocity(new Vector2D(0, 0)); ball.setHP(ball.getHP()-1); } else { Vector2D pos = ballConfig.getBlueBallPos().get(id-1); ball.setPos(pos); ball.setVelocity(new Vector2D(0, 0)); ball.setHP(ball.getHP()-1); } } }
|
碰撞物理效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class CollisionPhysics {
public static Pair<Vector2D, Vector2D> calculateBallCollision(Vector2D positionA, Vector2D velocityA, double massA, Vector2D positionB, Vector2D velocityB, double massB) { Vector2D collisionVector = positionA.subtract(positionB); collisionVector = collisionVector.normalize(); double vA = collisionVector.dotProduct(velocityA); double vB = collisionVector.dotProduct(velocityB); if (vB <= 0 && vA >= 0) { return new Pair<>(velocityA, velocityB); } double optimizedP = (2.0 * (vA - vB)) / (massA + massB); Vector2D velAPrime = velocityA.subtract(collisionVector.multiply(optimizedP).multiply(massB)); Vector2D velBPrime = velocityB.add(collisionVector.multiply(optimizedP).multiply(massA)); return new Pair<>(velAPrime, velBPrime); }
public static Vector2D calculateEdgeCollision(Ball ball, Table table) { Circle circle = ball.getCircle(); Vector2D v = ball.getVelocity(); double r = circle.getRadius(); Vector2D pos = ball.getPos(); if (circle.getCenterX() - r < 0) { v = new Vector2D(-ball.getVelocity().x, ball.getVelocity().y); ball.setPos(new Vector2D(r, pos.y)); } if (circle.getCenterX() + r > table.getSize().x) { v = new Vector2D(-ball.getVelocity().x, ball.getVelocity().y); ball.setPos(new Vector2D(table.getSize().x - r, pos.y)); } if (circle.getCenterY() - r < 0) { v = new Vector2D(ball.getVelocity().x, -ball.getVelocity().y); ball.setPos(new Vector2D(pos.x, r)); } if (circle.getCenterY() + r > table.getSize().y) { v = new Vector2D(ball.getVelocity().x, -ball.getVelocity().y); ball.setPos(new Vector2D(pos.x, table.getSize().y - r)); } return v; } }
|
摩擦力效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class FrictionPhysics {
public static Vector2D calculateVelocity(Ball ball, double friction) { Vector2D velocity = ball.getVelocity(); double v = ball.calculateVelocity(); if(v > 0.05) { double unitX = velocity.x / v; double unitY = velocity.y / v; velocity.x -= friction * unitX; velocity.y -= friction * unitY; } else { velocity.x = 0; velocity.y = 0; } return velocity; } }
|
胜利失败条件
母球HP=2,蓝球HP=2,红球HP=1,入袋后HP-1,HP=0则清除。
胜利:非母球全部清除
失败:母球清除
重置:按下空格重开游戏
击球控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| private void handleMouseDragged(MouseEvent event) { if (isDragging && selectedBall != null) { double tmpX = event.getX(); double tmpY = event.getY(); virtualBall.getCircle().setVisible(true); virtualBall.setPos(new Vector2D(tmpX, tmpY)); } } private void handleMousePressed(MouseEvent event) { mouseX = event.getX(); mouseY = event.getY(); Ball ball = cueBall;
if (ball.contains(new Vector2D(mouseX, mouseY)) && ball.getVelocity().equals(new Vector2D(0, 0))) { mouseX = ball.getPos().x; mouseY = ball.getPos().y; selectedBall = ball; isDragging = true; return; }
}
private void handleMouseReleased(MouseEvent event) { if (isDragging && selectedBall != null) { virtualBall.getCircle().setVisible(false);
double deltaX = mouseX - event.getX(); double deltaY = mouseY - event.getY();
double speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY) * BALL_SPEED; double angle = Math.atan2(deltaY, deltaX);
selectedBall.setVelocity(new Vector2D(speed * Math.cos(angle), speed * Math.sin(angle)));
isDragging = false; selectedBall = null; } }
|
项目源码
PoolGame
游戏截图