【Java】贪吃蛇小游戏设计

您所在的位置:网站首页 贪吃蛇游戏设计总结与反思 【Java】贪吃蛇小游戏设计

【Java】贪吃蛇小游戏设计

2024-07-12 06:27| 来源: 网络整理| 查看: 265

一、相关背景介绍 1、贪吃蛇游戏介绍:

        贪吃蛇小游戏是一个深受人们喜欢的游戏。一条蛇在密闭的围墙内,在围墙内随机出现一个食物,通过键盘上的四个光标键控制蛇向上下左右四个方向移动,蛇头撞到食物,则表示食物被吃掉,这时蛇的身体长一节,同时加1分;接着又出现食物,等待被蛇吃掉,如果蛇在移动过程中,撞到墙壁或身体交叉(蛇头撞到自己的身体),则游戏结束。游戏结束时输出相应得分。

2、JavaFX相关知识

        JavaFX是开发JavaGUI程序的新框架。JavaFX API是演示如何应用面向对象原则的优秀范例。JavaFX平台取代了Swing和AWT,融入了现代GUI应用,为支持触摸设备提供多点触控支持,具有内建的2D、3D、动画支持,以及视频和音频的回放功能。使用第三方软件,可以开发JavaFX程序并部署在运行IOS或者安卓的设备上。

图 1 JavaFX面板组成

        JavaFX提供了Canvas画布,即时模式样式的渲染API。 在包javafx.scene.canvas中,它包含一组用于canvas的类,可以使用它们直接在JavaFX场景的一个区域内绘制。JavaFX还在javafx.print包中提供用于打印目的的类。

        Graphics Context是图形上下文,可以将其理解为一块画布,我们可以在上面进行绘画操作,绘制完成后,将画布放到我们的view中显示即可,view看作是一个画框。

        JavaFX有三类动画实现方式:Transition,TimeLine和AnimationTimer。

        AnimationTimer看起来像是一个计时器,其实他更适合叫做心跳循环。JavaFX绘图的每一帧都会自动条用AnimationTimer. AnimationTimer是一个抽象类。实现该类需要实现一个函数handle,其参数为调用时的nanoTime()。

        Java中对事件处理的方式是:其控制事件源到事件监听器的传递过程,并将任何对象指派给事件监听器。

        鼠标事件:在图形用户界面中,用户会经常使用鼠标来进行选择、切换界面等操作,这些操作被定义为鼠标事件,其中包括鼠标按下、鼠标松开、鼠标单击等。JDK中提供了一个MouseEvent类用于表示鼠标事件,几乎所有的组件都可以产生鼠标事件。

        键盘事件:键盘操作也是最常用的用户交互方式,例如键盘按下、释放等,这些操作被定义为键盘事件。JDK中提供了一个KeyEvent类表示键盘事件,处理KeyEvent事件的监听器对象需要实现KeyListener接口或者继承KeyAdapter类。

二、算法分析和代码实现 算法流程图:

游戏主要模块

snakePane类实现功能

代码实现

导入相应的包:

import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.input.KeyCode; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.stage.Stage; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import java.io.File; import java.util.Arrays; import java.util.Random;

播放音乐类

class musicStuff { void playMusic(String musicLocation) { try { File musicPath = new File(musicLocation); if(musicPath.exists()) { AudioInputStream audioInput = AudioSystem.getAudioInputStream(musicPath); Clip clip = AudioSystem.getClip(); clip.open(audioInput); clip.start(); clip.loop(Clip.LOOP_CONTINUOUSLY); } } catch(Exception ex) { ex.printStackTrace(); } } }

点坐标

class Point{ //点坐标 int x; int y; public Point(int x,int y){ this.x=x; this.y=y; } }

主界面

class snakePane extends Pane{ //定义变量 public static final int NUM = 45; //格子数量/坐标范围 public static final int GRID_SIZE = 14; //格子大小 public static final int WIDTH = NUM * GRID_SIZE; //画布长宽 public static final int HEIGHT = NUM * GRID_SIZE; public static int state=0; //状态 0:暂停 1:游戏中 public static int speed; //移动速度,每秒的帧数 同时记录得分 public static int score=0; //记录最高分 public static Point food = new Point(-1,-1); //食物 public static Point[] snake = new Point[1000]; //蛇,最多长1000节 public static int snakeLength = 0; //现在多少节 enum Direction {UP, LEFT, DOWN, RIGHT} //朝向 public static Direction direction; //方向 public static Random random = new Random(); //随机生成器 public static boolean gameOver; //游戏结束标志 public snakePane() { Canvas canvas = new Canvas(WIDTH, HEIGHT); //画布 this.getChildren().add(canvas); Scene scene = new Scene(this, WIDTH, HEIGHT, Color.BLACK); final GraphicsContext gc = canvas.getGraphicsContext2D(); if (state == 0) { //开始界面 gc.setFill(Color.BLANCHEDALMOND); gc.setFont(new Font(40)); gc.fillText("贪吃蛇小游戏",WIDTH/2-100,HEIGHT/2); gc.setFont(new Font(20)); gc.setFill(Color.WHITESMOKE); gc.fillText("按空格开始游戏...", WIDTH-200, HEIGHT-30); for (int i = 0; i < 4; i++) { Point point = new Point(NUM / 2+10+i, NUM / 2+5); gc.setFill(Color.GREEN); gc.fillRect(point.x * GRID_SIZE , point.y * GRID_SIZE , GRID_SIZE -1, GRID_SIZE - 1); } gc.fillRect((NUM/2+13)* GRID_SIZE , (NUM/2+4)* GRID_SIZE , GRID_SIZE -1, GRID_SIZE - 1); gc.setFill(Color.YELLOW); gc.fillRect((NUM/2+13)* GRID_SIZE , (NUM/2+2)* GRID_SIZE , GRID_SIZE -1, GRID_SIZE - 1); } scene.setOnKeyPressed(e1 -> { if (e1.getCode() == KeyCode.SPACE) //空格响应事件 { state=1; //开始游戏 } }); newGame(); AnimationTimer timer = new AnimationTimer() { long lastTick; //前一个时刻 @Override public void handle(long now) { //更新画布 if (gameOver||state==0) { //游戏结束或暂停中 return; } if (lastTick == 0 || now - lastTick >1e9 / (speed/5+3)) { //随着速度变大,移动变快,画面切换变快 lastTick = now; move(); //移动 paint(gc); //绘图 } } }; timer.start(); scene.setOnKeyPressed(e -> { if(e.getCode()==KeyCode.SPACE) { //按空格暂停或继续 switch (state) { case 0://暂停中 state = 1; break; case 1://游戏中 state = 0; break; } } if (e.getCode() == KeyCode.W || e.getCode() == KeyCode.UP) //向上 { if (direction != Direction.DOWN) direction = Direction.UP; } else if (e.getCode() == KeyCode.A || e.getCode() == KeyCode.LEFT) //向左 { if (direction != Direction.RIGHT) direction = Direction.LEFT; } else if (e.getCode() == KeyCode.S || e.getCode() == KeyCode.DOWN) //向下 { if (direction != Direction.UP) direction = Direction.DOWN; } else if (e.getCode() == KeyCode.D || e.getCode() == KeyCode.RIGHT) //向右 { if (direction != Direction.LEFT) direction = Direction.RIGHT; } else if (e.getCode() == KeyCode.R) //重新开始 if (gameOver) newGame(); }); } //初始化游戏数据 public void newGame() { speed = 3; //蛇的初始有3节 Arrays.fill(snake, null); snakeLength = 0; snake[snakeLength++] = new Point(NUM / 2, NUM / 2); //初始化三个点都在中心 snake[snakeLength++] = new Point(NUM / 2, NUM / 2); snake[snakeLength++] = new Point(NUM / 2, NUM / 2); //生成食物 newFood(); direction = Direction.LEFT; //初始方向向左 //游戏未结束 gameOver = false; } //在地图上随机生成食物 public static void newFood() { //1,不能生成在外面 //2.不能和蛇的身体碰撞 int x, y; do { x = random.nextInt(NUM); //随机生成食物点 y = random.nextInt(NUM); } while (isCollision(x, y)); //当没有碰撞 food.x = x; food.y = y; } //判断坐标重合 public static boolean isCollision(int x, int y) { //遍历蛇 for (int i = 0; i < snakeLength; i++) { Point point = snake[i]; if (point.x == x && point.y == y) { //坐标与蛇坐标重合 return true; } } return false; } //每一帧的动作 public static void move() { //移动身体 for (int i = snakeLength - 1; i >= 1; i--) { //变成前一个坐标 snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } //移动头 Point head = snake[0]; switch (direction) { case UP: //向上 head.y--; break; case DOWN: //向下 head.y++; break; case LEFT: //向左 head.x--; break; case RIGHT: //向右 head.x++; break; } //判断头是否出边界 if (head.x < 0 || head.x >= NUM || head.y < 0 || head.y >= NUM) { gameOver = true; return; } //判断是否碰到身体 for (int i = 1; i < snakeLength; i++) { Point point = snake[i]; if (head.x == point.x && head.y == point.y) { gameOver = true; return; } } //判断是否吃到食物 if (head.x == food.x && head.y == food.y) { // 如果吃到食物 snake[snakeLength++] = new Point(-1,-1); // 1.身体增加1节 newFood(); //2.重新生成食物 speed++; //3. 得分增加 } } //绘制画布 public void paint(GraphicsContext gc){ //1.重新绘制背景 gc.setFill(Color.BLACK); gc.fillRect(0, 0, WIDTH, HEIGHT); //2.蛇的绘制 for (int i = 0; i < snakeLength; i++) { Point point = snake[i]; gc.setFill(Color.GREEN); gc.fillRect(point.x * GRID_SIZE , point.y * GRID_SIZE , GRID_SIZE -1, GRID_SIZE - 1); } //按像素点绘制,需要乘以格子大小,-1让其有一定空隙 //3.食物的绘制 gc.setFill(Color.YELLOW); gc.fillOval(food.x * GRID_SIZE , food.y * GRID_SIZE , GRID_SIZE, GRID_SIZE); //填充范围 //3.进行游戏结束绘制 if (gameOver) { gc.setFill(Color.RED); gc.setFont(new Font(20)); gc.fillText("游戏结束,按R继续", WIDTH/2-70, HEIGHT/2); } //得分 gc.setFill(Color.WHITE); gc.setFont(new Font(15)); gc.fillText("当前得分" + (speed - 3) + "分", 40, 40); if(speed>score) score=speed; gc.fillText("最高分:" + (score - 3) + "分", 40, 60); } }

主程序

public class GameSnake extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { String filepath = "./data/贪吃蛇.wav"; musicStuff musicObject = new musicStuff(); musicObject.playMusic(filepath); //游戏初始化 snakePane p=new snakePane(); primaryStage.setScene(p.getScene()); primaryStage.setTitle("贪吃蛇"); primaryStage.show(); } } 三、运行结果

游戏开始界面

游戏中:显示当前分数与最高分

游戏结束界面:

游戏中始终有音乐播放。

四、总结

        基于原始贪吃蛇小游戏的原理,设置方格为蛇的身体,一节一节的身体连起来就有了像素风格,随机出现的食物设置为黄色的圆形,整个游戏界面设置为黑色。贪吃蛇小游戏的核心在于蛇的每一步移动,即如何让方块动起来,理解了运动的原理和实质也就简单了。实现移动的动画效果其实就是在极短时间间隔内画布的重绘,肉眼看上去就是动画了。画布重绘的依据是蛇的每节身体的变化,即每节身体变换到前一节的位置,而蛇头需要单独变化,根据用户输入的方向键判断上下左右的移动。

        由大的框架分工到每个小的模块上来做就容易实现得多,程序中为了让主程序尽可能简单,首先需要定义一个Point类用来记录点坐标,其次我还定义了一个snakePane窗体继承于Pane,里面定义了不同的方法用于实现不同的功能。snakePane类中首先定义了所有变量使之成为属性,为了让所有方法体均能访问到。其中,snakePane方法用于控制画面的主要变换,包含键盘的事件驱动,实现初始时提示用户按空格进入游戏,游戏中通过方向键控制蛇的方向改变,同时按空格键可以暂停游戏。newGame方法用于初始化蛇,将蛇用Point类型的数组存储,每一节身体占用一个数组单位,初始时有3节身体,且均置于画面中心。newFood方法用于在地图上随机生成食物,食物也是Point类型,使用random函数随机生成坐标点,需要注意点不能生成在外面,而且不能和蛇的而身体重合。isCollision函数用于判断蛇头是否与身体的其他点重合。move函数实现蛇的移动,实质是点坐标的改变,其中需要判断头出界或碰到身体则游戏结束,吃到食物则生成新的一节身体。paint函数用于将蛇和食物的坐标形式转变到画布中,实现可视化。主函数中调用音乐类,播放音乐,再实例化一个snakePane在舞台中就完成了这个贪吃蛇小游戏。

        在完成大致框架的情况下,还需要进行一些细节的描绘,美化界面。在进入游戏前显示了一些提示词,在游戏中除了记录每次的得分,还记录了一个最高分,该次游戏超过最高分时会进行更新;当游戏结束时提示用户游戏结束,按R键重新开始。

        本实验的难点在于对帧动画的理解,我没有用小球实验中TimeLine的方法,而是AnimationTimer,二者的原理类似,但后者还可以控制不同的画面更新时间,实现随分数增大蛇移动变快的效果。调用handle()方法重写需要更新的内容,通过start()和stop()来激活和停用它。本实验的重点在于键盘按键的驱动事件,通过键盘的按键操作,让游戏充满了灵魂,实现了游戏多样化。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3