最近无聊又用JavaFx实现了一遍贪吃蛇,现在发出来给萌新参考。
思路:主要分为控制器、视图、任务线程三部分。控制器就是控制蛇的,视图就是用来展示蛇的,然后任务线程就是用来一直维持蛇的运动。
文件结构:
首先是一些常量:
package constant;
import entity.SnakeCell;
import java.util.LinkedList;
public interface Const {
LinkedList<SnakeCell> SNAKE_CELL_LIST = new LinkedList<>();
double cellLen = 20;
}
package constant;
public interface Direction {
int L = 0;
int R = 1;
int U = 2;
int D = 3;
}
控制器:
package controller;
import constant.Const;
import constant.Direction;
import javafx.event.EventHandler;
import javafx.scene.control.Alert;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import test.Client;
public class SnakeActionController implements EventHandler<KeyEvent> {
@Override
public void handle(KeyEvent event) {
if (event.getEventType().equals(KeyEvent.KEY_PRESSED)) {
if (event.getCode().getName().equals(KeyCode.LEFT.getName()) && Client.current_direction != Direction.R) {
Client.current_direction = Direction.L;
} else if (event.getCode().getName().equals(KeyCode.RIGHT.getName()) && Client.current_direction != Direction.L) {
Client.current_direction = Direction.R;
} else if (event.getCode().getName().equals(KeyCode.UP.getName()) && Client.current_direction != Direction.D) {
Client.current_direction = Direction.U;
} else if (event.getCode().getName().equals(KeyCode.DOWN.getName()) && Client.current_direction != Direction.U) {
Client.current_direction = Direction.D;
}
}
}
}
蛇的实体类:
package entity;
import javafx.scene.paint.Color;
public class SnakeCell {
private double x;
private double y;
private double cellLen;
private Color color;
private String shade;
public SnakeCell(int x, int y, double cellLen, Color color, String shade){
this.x = x;
this.y = y;
this.cellLen = cellLen;
this.color = color;
this.shade = shade;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getCellLen() {
return cellLen;
}
public void setCellLen(double cellLen) {
this.cellLen = cellLen;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public String getShade() {
return shade;
}
public void setShade(String shade) {
this.shade = shade;
}
@Override
public String toString() {
return "SnakeCell{" +
"x=" + x +
", y=" + y +
", cellLen=" + cellLen +
", color=" + color +
", shade='" + shade + '\'' +
'}';
}
}
视图:
package view;
import constant.Const;
import entity.SnakeCell;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import java.util.List;
public class SnakePane extends Pane {
private SnakeCell food;
private Canvas canvas;
private GraphicsContext context;
private List<SnakeCell> cells = Const.SNAKE_CELL_LIST;
private int score = 0;
private int speed = 200;
public void setSpeed(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
public SnakePane() {
canvas = new Canvas(800, 800);
context = canvas.getGraphicsContext2D();
context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
context.setStroke(Color.BLACK);
context.strokeRect(50, 50, canvas.getWidth() - 60, canvas.getHeight() - 60);
context.strokeText("当前分数为:" + score + " 分",20, 20, 100);
context.strokeText("speed:" + speed + "ms/次",100, 20, 50);
getChildren().add(canvas);
}
public void drawCell(){
cells.forEach(cell->{
context.setFill(cell.getColor());
context.fillOval(cell.getX(), cell.getY(), cell.getCellLen(), cell.getCellLen());
});
}
public void repaint(){
context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
context.setStroke(Color.BLACK);
context.strokeRect(50, 50, canvas.getWidth() - 60, canvas.getHeight() - 60);
context.strokeText("当前分数为:" + score + " 分",50, 20, 200);
context.strokeText("当前speed:" + speed + " ms/次",200, 20, 200);
cells.forEach(cell->{
context.setFill(cell.getColor());
context.fillOval(cell.getX(), cell.getY(), cell.getCellLen(), cell.getCellLen());
});
context.setFill(food.getColor());
context.fillRect(food.getX(), food.getY(), food.getCellLen(), food.getCellLen());
}
public void setScore(int score){
this.score = score;
}
public int getScore(){return score;}
public void setFood(SnakeCell food){
this.food = food;
}
public SnakeCell getFood(){return food;}
}
任务线程::
package task;
import constant.Const;
import constant.Direction;
import entity.SnakeCell;
import javafx.concurrent.Task;
import javafx.scene.paint.Color;
import test.Client;
import view.SnakePane;
import java.util.Random;
public class MoveTask extends Task<Integer> {
private SnakePane pane;
private Random random = new Random();
public MoveTask(SnakePane pane) {
this.pane = pane;
}
public volatile static boolean isForceCancel = false;
public volatile static boolean isOverBound = false;
public volatile static boolean isEatItSelf = false;
private int currentSize = 0;
public static void reDetect() {
isOverBound = false;
isEatItSelf = false;
}
private boolean isEat(SnakeCell cell) {
if (cell.getX() == pane.getFood().getX() && cell.getY() == pane.getFood().getY()) {
pane.setScore(pane.getScore() + 1);
return true;
}
return false;
}
private boolean isEatItSelf() {
SnakeCell first = Const.SNAKE_CELL_LIST.getFirst();
for (int i = 1; i < Const.SNAKE_CELL_LIST.size(); i++) {
if (first.getX() == Const.SNAKE_CELL_LIST.get(i).getX() && first.getY() == Const.SNAKE_CELL_LIST.get(i).getY()) {
return true;
}
}
return false;
}
@Override
protected Integer call() throws Exception {
while (!isCancelled()) {
if (Const.SNAKE_CELL_LIST.getFirst().getX() < 50 || Const.SNAKE_CELL_LIST.getFirst().getY() < 50 ||
Const.SNAKE_CELL_LIST.getFirst().getX() > 770 || Const.SNAKE_CELL_LIST.getFirst().getY() > 770) {
isOverBound = true;
continue;
}
if (Const.SNAKE_CELL_LIST.size() > 3 && isEatItSelf()) {
isEatItSelf = true;
continue;
}
SnakeCell cell;
switch (Client.current_direction) {
case Direction.L:
cell = Const.SNAKE_CELL_LIST.removeLast();
cell.setX(Const.SNAKE_CELL_LIST.getFirst().getX() - 20);
cell.setY(Const.SNAKE_CELL_LIST.getFirst().getY());
Const.SNAKE_CELL_LIST.addFirst(cell);
if (isEat(cell)) {
controlFood(Direction.L);
}
break;
case Direction.R:
cell = Const.SNAKE_CELL_LIST.removeLast();
cell.setX(Const.SNAKE_CELL_LIST.getFirst().getX() + 20);
cell.setY(Const.SNAKE_CELL_LIST.getFirst().getY());
Const.SNAKE_CELL_LIST.addFirst(cell);
if (isEat(cell)) {
controlFood(Direction.R);
}
break;
case Direction.U:
cell = Const.SNAKE_CELL_LIST.removeLast();
cell.setY(Const.SNAKE_CELL_LIST.getFirst().getY() - 20);
cell.setX(Const.SNAKE_CELL_LIST.getFirst().getX());
Const.SNAKE_CELL_LIST.addFirst(cell);
if (isEat(cell)) {
controlFood(Direction.U);
}
break;
case Direction.D:
cell = Const.SNAKE_CELL_LIST.removeLast();
cell.setY(Const.SNAKE_CELL_LIST.getFirst().getY() + 20);
cell.setX(Const.SNAKE_CELL_LIST.getFirst().getX());
Const.SNAKE_CELL_LIST.addFirst(cell);
if (isEat(cell)) {
controlFood(Direction.D);
}
break;
}
if (Const.SNAKE_CELL_LIST.size() != currentSize && Const.SNAKE_CELL_LIST.size() % 6 == 0){
currentSize = Const.SNAKE_CELL_LIST.size();
pane.setSpeed(pane.getSpeed() - 20);
}
pane.repaint();
Thread.sleep(pane.getSpeed());
}
return null;
}
private void controlFood(int dir) {
SnakeCell food = pane.getFood();
switch (dir) {
case Direction.L:
food.setX(Const.SNAKE_CELL_LIST.getFirst().getX() + 20);
break;
case Direction.R:
food.setX(Const.SNAKE_CELL_LIST.getFirst().getX() - 20);
break;
case Direction.D:
food.setY(Const.SNAKE_CELL_LIST.getFirst().getY() - 20);
break;
case Direction.U:
food.setY(Const.SNAKE_CELL_LIST.getFirst().getY() + 20);
break;
}
food.setColor(Color.BLACK);
Const.SNAKE_CELL_LIST.addLast(food);
// 食物
SnakeCell food1 = createFood();
while (checkIsOnSnakeBody(food1)) {
food1 = createFood();
}
pane.setFood(food1);
}
private boolean checkIsOnSnakeBody(SnakeCell food) {
for (SnakeCell cell : Const.SNAKE_CELL_LIST) {
if (cell.getY() == food.getY() && cell.getX() == food.getX())
return true;
}
return false;
}
private SnakeCell createFood() {
int x = random.nextInt(770);
x = x - x % 20;
if (x / 10 % 10 % 2 == 0)
x = x - 10;
if (x < 50)
x = 50;
int y = random.nextInt(770);
y = y - y % 20;
if (y / 10 % 10 % 2 == 0)
y = y - 10;
if (y < 50)
y = 50;
return new SnakeCell(x, y, Const.cellLen, Color.GREEN, "rectangle");
}
}
这是测试类,也就是主函数所在:
package test;
import constant.Const;
import constant.Direction;
import controller.SnakeActionController;
import entity.SnakeCell;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import task.MoveTask;
import view.SnakePane;
import java.util.Optional;
public class Client extends Application {
public static int current_direction = Direction.R;
private volatile static boolean isRequestClose = false;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("贪吃蛇大作战");
SnakePane pane = new SnakePane();
pane.drawCell();
action(primaryStage, pane);
Scene scene = new Scene(pane, 850, 800);
scene.setOnKeyPressed(new SnakeActionController());
primaryStage.setScene(scene);
primaryStage.show();
}
private void action(Stage primaryStage, SnakePane pane){
preStart(pane);
SnakeCell food = new SnakeCell(190, 150, 20, Color.GREEN, "rectangle");
pane.setFood(food);
MoveTask task = new MoveTask(pane);
task.setOnCancelled(event -> {
// 在关闭主窗口会回调这里
if (MoveTask.isForceCancel) {
String msg = "";
if (MoveTask.isOverBound)
msg = "您已出界!!";
if (MoveTask.isEatItSelf)
msg = "您已经不小心把自己给吃了!!";
Alert alert = new Alert(Alert.AlertType.NONE, msg + "\n是否重新开局???",
new ButtonType("重新开局", ButtonBar.ButtonData.YES), new ButtonType("退出游戏", ButtonBar.ButtonData.NO));
alert.setTitle("提示");
Optional<ButtonType> buttonType = alert.showAndWait();
if (buttonType.isPresent()) {
if (buttonType.get().getButtonData().equals(ButtonBar.ButtonData.YES)) {
//todo 重新开局
MoveTask.isForceCancel = false;
MoveTask.reDetect();
action(primaryStage, pane);
} else {
//todo 退出
exit(task);
Platform.exit();
}
}
}
});
new Thread(task).start();
Task<Integer> task1 = new Task<Integer>() {
@Override
protected Integer call() throws Exception {
while (!isRequestClose) {
if (MoveTask.isEatItSelf || MoveTask.isOverBound) {
task.cancel(true);
MoveTask.isForceCancel = true;
break;
}
}
return null;
}
};
new Thread(task1).start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
exit(task);
}
});
}
private synchronized void preStart(SnakePane pane) {
Const.SNAKE_CELL_LIST.clear();
SnakeCell cell = new SnakeCell(50, 50, 20, Color.BLACK, "rectangle");
SnakeCell cell1 = new SnakeCell(70, 50, 20, Color.BLACK, "rectangle");
SnakeCell cell2 = new SnakeCell(90, 50, 20, Color.BLACK, "rectangle");
Const.SNAKE_CELL_LIST.add(cell);
Const.SNAKE_CELL_LIST.add(cell1);
Const.SNAKE_CELL_LIST.add(cell2);
pane.setScore(0);
current_direction = Direction.R;
pane.setSpeed(200);
}
private void exit(MoveTask task) {
isRequestClose = true;
task.cancel();
}
}
因为较为简单,相信自己看看就懂了,,,