JavaFX 过时了吗?你怎么看,闲暇之余实现一版贪吃蛇小游戏,
2022-06-18 本文已影响0人
small_to_large
关于JavaFX技术这里不做过多介绍,简单说一句:
JavaFx平台作为Java gui的替代方案,是一个富客户端平台解决方案,它能够使用应用程序开发人员轻松的创建跨平台的富客户端应用程序。
通过JavaFX实现简单的贪吃蛇游戏,没有小蛇素材,使用老鼠代替😊😊😊
实现说明
- W S A D 代表上下左右方向
- AnchorPane 主面板,锚点类型
- Canvas 绘图对象,获取画笔进行图像显示绘制,设置位置坐标和尺寸
- 音乐播放使用 MediaPlayer
- 食物随机生成,使用子线程和阻塞队列进行生产。
- 使用Timer 实现蛇前进和速度控制
核心对象
- Food: 食物对象
- Snake: 蛇对象
- SnakeHead: 蛇头对象
- SnakePart: 蛇身体一部分
- SnakeRun: 游戏启动类继承了javafx的Application
运行截图
image.png image.png image.png image.png代码实现
Food
package com.example.snake;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import java.util.Random;
public class Food {
private final int x;
private final int y;
private final int size;
private final Color color;
private final GraphicsContext graphics;
public Food(int x, int y, int size, Color color, GraphicsContext graphics) {
this.x = x;
this.y = y;
this.size = size;
this.color = color;
this.graphics = graphics;
}
public Food(int x, int y, GraphicsContext graphics) {
this.x = x;
this.y = y;
this.size = SnakePart.DEF_LEN;
Random random = new Random();
this.color = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
this.graphics = graphics;
}
public Food show() {
graphics.setFill(color);
// graphics.fillRect(x, y, size, size);
graphics.drawImage(Caches.mouseImage2, x, y, size, size);
return this;
}
public void vanish() {
graphics.clearRect(x, y, size, size);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getSize() {
return size;
}
public Color getColor() {
return color;
}
}
SnakePart
package com.example.snake;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import java.util.Random;
/**
* 身体片段
*/
public class SnakePart {
public static final int DEF_LEN = 40;
protected int x;
protected int y;
protected final int len;
protected final Color color;
protected final GraphicsContext graphics;
public SnakePart(int x, int y, GraphicsContext graphics) {
this.x = x;
this.y = y;
this.len = DEF_LEN;
Random random = new Random();
this.color = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
this.graphics = graphics;
}
public SnakePart(int x, int y, Color color, GraphicsContext graphics) {
this.x = x;
this.y = y;
this.len = DEF_LEN;
this.color = color;
this.graphics = graphics;
}
public SnakePart(int x, int y, int len, Color color, GraphicsContext graphics) {
this.x = x;
this.y = y;
this.len = len;
this.color = color;
this.graphics = graphics;
}
public void remove() {
graphics.clearRect(x, y, len, len);
}
public SnakePart move(int dX, int dY) {
x += dX;
y += dY;
graphics.drawImage(Caches.snakeBodyImage, x, y, len, len);
return this;
}
public SnakePart updateLocation(int pX, int pY) {
x = pX;
y = pY;
graphics.drawImage(Caches.snakeBodyImage, x, y, len, len);
return this;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getLen() {
return len;
}
@Override
public String toString() {
return "SnakePart{" +
"x=" + x +
", y=" + y +
", color=" + color +
'}';
}
}
SnakeHead
package com.example.snake;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class SnakeHead extends SnakePart {
public SnakeHead(int x, int y, GraphicsContext graphics) {
super(x, y, graphics);
}
public SnakeHead(int x, int y, Color color, GraphicsContext graphics) {
super(x, y, color, graphics);
}
public SnakeHead(int x, int y, int len, Color color, GraphicsContext graphics) {
super(x, y, len, color, graphics);
}
@Override
public SnakePart move(int dX, int dY) {
x += dX;
y += dY;
// graphics.setFill(color);
graphics.drawImage(Caches.snakeHeadImage, x, y, len, len);
return this;
}
@Override
public SnakePart updateLocation(int pX, int pY) {
x = pX;
y = pY;
// graphics.setFill(color);
graphics.drawImage(Caches.snakeHeadImage, x, y, len, len);
return this;
}
}
Snake
package com.example.snake;
import javafx.application.Platform;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
public class Snake {
private int initX;
private int initY;
private int initLen;
private int speed;
//方向 w s a d -> 上下左右
private KeyCode direction;
private SnakeHead head;
private List<SnakePart> body;
private GraphicsContext graphics;
private Canvas canvas;
private ArrayBlockingQueue<Food> foodQueue;
private Timer timer;
private SnakeEvent snakeEvent;
public Snake(int initX, int initY, int initLen, int speed, Canvas canvas, ArrayBlockingQueue<Food> foodQueue) {
this.initX = initX;
this.initY = initY;
this.initLen = initLen;
this.speed = speed;
this.canvas = canvas;
this.foodQueue = foodQueue;
this.graphics = canvas.getGraphicsContext2D();
}
public void init() {
head = new SnakeHead(initX, initY, graphics);
body = new ArrayList<>();
for (int i = 0; i < initLen; i++) {
body.add(new SnakePart(initX - SnakePart.DEF_LEN * (i + 1), initY, graphics).move(0, 0));
}
timer = new Timer();
direction = KeyCode.D;
}
public void turn(KeyEvent keyEvent) {
KeyCode code = keyEvent.getCode();
if (code != KeyCode.W && code != KeyCode.S && code != KeyCode.A && code != KeyCode.D) {
return;
}
if (code == KeyCode.W && direction == KeyCode.S) {
return;
}
if (code == KeyCode.S && direction == KeyCode.W) {
return;
}
if (code == KeyCode.A && direction == KeyCode.D) {
return;
}
if (code == KeyCode.D && direction == KeyCode.A) {
return;
}
direction = code;
}
public boolean eat() {
Food food = foodQueue.peek();
if (food == null) {
return false;
}
if (head.getX() == food.getX() && head.getY() == food.getY()) {
food = foodQueue.poll();
if (food == null) {
return false;
}
SnakePart tail = body.get(body.size() - 1);
if (direction == KeyCode.W) {
body.add(new SnakePart(tail.getX(), tail.getY() + SnakePart.DEF_LEN, food.getColor(), graphics).move(0, 0));
} else if (direction == KeyCode.S) {
body.add(new SnakePart(tail.getX(), tail.getY() - SnakePart.DEF_LEN, food.getColor(), graphics).move(0, 0));
} else if (direction == KeyCode.A) {
body.add(new SnakePart(tail.getX() + SnakePart.DEF_LEN, tail.getY(), food.getColor(), graphics).move(0, 0));
} else if (direction == KeyCode.D) {
body.add(new SnakePart(tail.getX() - SnakePart.DEF_LEN, tail.getY(), food.getColor(), graphics).move(0, 0));
}
food.vanish();
return true;
}
return false;
}
public void remove() {
for (SnakePart snakePart : body) {
snakePart.remove();
}
}
public void move(int dX, int dY) {
if (dX == 0 && dY == 0) {
return;
}
remove();
for (int i = body.size() - 1; i > 0; i--) {
body.get(i).updateLocation(body.get(i - 1).getX(), body.get(i - 1).getY());
}
int headX = head.getX();
int headY = head.getY();
head.remove();
body.get(0).updateLocation(headX, headY);
head.move(dX, dY);
}
public void run() {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(()->{
die();
eat();
switch (direction) {
case W: {
move(0, -SnakePart.DEF_LEN);
break;
}
case S: {
move(0, SnakePart.DEF_LEN);
break;
}
case A: {
move(-SnakePart.DEF_LEN, 0);
break;
}
case D: {
move(SnakePart.DEF_LEN, 0);
break;
}
}
});
}
}, 0, 2000 / speed);
}
public void die() {
double width = canvas.getWidth();
double height = canvas.getHeight();
if (head.getX() < 0 || head.getY() < 0 ||
(head.getX() + SnakePart.DEF_LEN) > width ||
(head.getY() + SnakePart.DEF_LEN) > height) {
timer.cancel();
EventType die = EventType.DIE;
die.getData().setScore((body.size() - initLen) * 5);
die.getData().setSnake(this);
Platform.runLater(() -> snakeEvent.onEvent(die));
}
}
public void clearAll() {
remove();
head.remove();
body.clear();
Food food;
if ((food = foodQueue.poll()) != null) {
food.vanish();
}
}
public List<SnakePart> getHeadAndBody() {
ArrayList<SnakePart> snakeParts = new ArrayList<>();
snakeParts.add(head);
snakeParts.addAll(body);
return snakeParts;
}
public void setSnakeEvent(SnakeEvent snakeEvent) {
this.snakeEvent = snakeEvent;
}
public interface SnakeEvent {
void onEvent(EventType type);
}
public enum EventType {
DIE(new Data(0));
private final Data data;
EventType(Data data) {
this.data = data;
}
public Data getData() {
return data;
}
}
public static class Data {
private Snake snake;
private int score;
public Data() {
}
public Data(int score) {
this.score = score;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public Snake getSnake() {
return snake;
}
public void setSnake(Snake snake) {
this.snake = snake;
}
}
}
SnakeRun
package com.example.snake;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.layout.*;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
public class SnakeRun extends Application {
private static final int multiple = 22;
private static final int width = SnakePart.DEF_LEN * (multiple + 10);
private static final int height = SnakePart.DEF_LEN * multiple;
private double windowXOffset = 0;
private double windowYOffset = 0;
private final ArrayBlockingQueue<Food> foodQueue = new ArrayBlockingQueue<>(1);
private MediaPlayer mediaPlayer;
private AnchorPane pane;
private Canvas canvas;
private boolean stop;
@Override
public void start(Stage stage) throws Exception {
pane = new AnchorPane();
canvas = new Canvas(width, height);
Snake snake = new Snake(SnakePart.DEF_LEN * 6, SnakePart.DEF_LEN * 6, 5, 10, canvas, foodQueue);
//监听死亡事件
snake.setSnakeEvent(type -> alert(type, stage));
snake.init();
pane.getChildren().add(canvas);
pane.setOnKeyPressed(snake::turn);
pane.setBackground(getBackground());
//鼠标按下 记录位置
pane.setOnMousePressed(event -> {
windowXOffset = event.getSceneX();
windowYOffset = event.getSceneY();
});
//鼠标拖动 移动stage窗口
pane.setOnMouseDragged(event -> {
stage.setX(event.getScreenX() - windowXOffset);
stage.setY(event.getScreenY() - windowYOffset);
});
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.setResizable(false);
//stage.setTitle("贪吃蛇");
//隐藏窗口标题和操作
stage.initStyle(StageStyle.UNDECORATED);
//退出事件
stage.setOnCloseRequest(event -> {
Platform.exit();
System.exit(0);
});
stage.show();
//获取窗口焦点
canvas.requestFocus();
//初始化并播放音乐
initMedia();
//开启食物生成线程
genFood(snake, canvas);
//运行蛇
snake.run();
}
private Background getBackground() {
BackgroundImage bImg = new BackgroundImage(
new Image(this.getClass().getResourceAsStream("/com/example/fx/bj11.jpg")),
BackgroundRepeat.NO_REPEAT,
BackgroundRepeat.NO_REPEAT,
BackgroundPosition.DEFAULT,
new BackgroundSize(-1, -1, true, true, false, true));
return new Background(bImg);
}
private void initMedia() {
Media media = new Media(this.getClass().getResource("/com/example/fx/dizi.mp3").toString());
mediaPlayer = new MediaPlayer(media);
mediaPlayer.setCycleCount(Integer.MAX_VALUE);
mediaPlayer.play();
}
private void genFood(Snake snake, Canvas canvas) {
new Thread(() -> {
Random random = new Random();
while (!stop) {
int x = random.nextInt(multiple - 1);
int y = random.nextInt(multiple - 1);
for (SnakePart snakePart : snake.getHeadAndBody()) {
if (snakePart.getX() == x && snakePart.getY() == y) {
x = random.nextInt(multiple - 1);
y = random.nextInt(multiple - 1);
}
}
try {
Food food = new Food(x * SnakePart.DEF_LEN, y * SnakePart.DEF_LEN, canvas.getGraphicsContext2D());
foodQueue.put(food);
if (!stop) {
Platform.runLater(food::show);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private void alert(Snake.EventType eventType, Stage stage) {
if (eventType == Snake.EventType.DIE) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("死亡确认");
alert.setHeaderText("警报,你已经死亡了!!");
alert.setContentText("当前得分:" + eventType.getData().getScore() + "分");
ButtonType buttonTypeOne = new ButtonType("重新开始");
ButtonType buttonTypeTwo = new ButtonType("关闭游戏");
ButtonType buttonTypeCancel = new ButtonType("取消", ButtonBar.ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeTwo, buttonTypeCancel);
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == buttonTypeOne) {
eventType.getData().getSnake().clearAll();
Snake snake = new Snake(SnakePart.DEF_LEN * 6, SnakePart.DEF_LEN * 6, 5, 10, canvas, foodQueue);
snake.setSnakeEvent(type -> alert(type, stage));
pane.setOnKeyPressed(snake::turn);
snake.init();
snake.run();
} else if (result.get() == buttonTypeTwo) {
stop = true;
mediaPlayer.stop();
eventType.getData().getSnake().clearAll();
stage.close();
} else {
alert.close();
}
}
}
}