Java实现动态贪吃蛇_java实现贪吃蛇小游戏

本文实例为大家分享了java实现贪吃蛇小游戏的具体代码,供大家参考,具体内容如下

aac036e4673e4a60417b17005d26b7f5.png

这是MVC模式的完整Java项目,编译运行SnakeApp.java即可开始游戏。

可扩展功能:

1、积分功能:可以创建得分规则的类(模型类的一部分), 在GameController的run()方法中计算得分

2、变速功能:比如加速功能,减速功能,可以在GameController的keyPressed()方法中针对特定的按键设置每一次移动之间的时间间隔,将Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);替换为动态的时间间隔即可

3、更漂亮的游戏界面:修改GameView中的drawXXX方法,比如可以将食物渲染为一张图片,Graphics有drawImage方法

View

SnakeApp.java

/*

* 属于View,用来根据相应的类展示出对应的游戏主界面,也是接收控制信息的第一线。

*/

public class SnakeApp {

public void init() {

//创建游戏窗体

JFrame window = new JFrame("一只长不大的蛇");

//初始化500X500的棋盘,用来维持各种游戏元素的状态,游戏的主要逻辑部分

Grid grid = new Grid(50*Settings.DEFAULT_NODE_SIZE,50*Settings.DEFAULT_NODE_SIZE);

//传入grid参数,新建界面元素对象

GameView gameView = new GameView(grid);//绘制游戏元素的对象

//初始化面板

gameView.initCanvas();

//根据棋盘信息建立控制器对象

GameController gameController = new GameController(grid);

//设置窗口大小

window.setPreferredSize(new Dimension(526,548));

//往窗口中添加元素,面板对象被加入到窗口时,自动调用其中的paintComponent方法。

window.add(gameView.getCanvas(),BorderLayout.CENTER);

//画出蛇和棋盘和食物

GameView.draw();

//注册窗口监听器

window.addKeyListener((KeyListener)gameController);

//启动线程

new Thread(gameController).start();

//窗口关闭的行为

window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//设置窗口大小不可变化

window.setResizable(false);

//渲染和显示窗口

window.pack();

window.setVisible(true);

}

//可以忽略,以后每个类中都有这么一个测试模块

public static void main(String[] args) {

SnakeApp snakeApp = new SnakeApp();

snakeApp.init();

}

}

GameView.java

/*

* 属于View,用于绘制地图、蛇、食物

*/

/* Graphics 相当于一个画笔。对象封装了 Java 支持的基本呈现操作所需的状态信息。此状态信息包括以下属性:

要在其上绘制的 Component 对象。

呈现和剪贴坐标的转换原点。

当前剪贴区。

当前颜色。

当前字体。

当前逻辑像素操作函数(XOR 或 Paint)。

当前 XOR 交替颜色

*/

/* java.awt.Component的repaint()方法

作用:更新组件。

如果此组件不是轻量级组件,则为了响应对 repaint 的调用,AWT 调用 update 方法。可以假定未清除背景。

Component 的 update 方法调用此组件的 paint 方法来重绘此组件。为响应对 repaint 的调用而需要其他工作的子类通常重写此方法。重写此方法的 Component 子类应该调用 super.update(g),或者直接从其 update 方法中调用 paint(g)。

图形上下文的原点,即它的(0,0)坐标点是此组件的左上角。图形上下文的剪贴区域是此组件的边界矩形。

*/

public class GameView {

private final Grid grid;

private static JPanel canvas;//画板,用于在这上面制作画面,然后返回。

public GameView(Grid grid) {

this.grid = grid;

}

//重新绘制游戏界面元素,不断重新调用paintComponent方法覆盖原本的面板。

public static void draw() {

canvas.repaint();

}

//获取画板对象的接口

public JPanel getCanvas() {

return canvas;

}

//对画板进行初始化

public void initCanvas() {

canvas = new JPanel() {

//指向一个方法被覆盖的新面板子类对象

//paintComponent()绘制此容器中的每个组件,Swing会在合适的时机去调用这个方法,展示出合适的界面,这就是典型的回调(callback)的概念。

public void paintComponent(Graphics graphics) {

super.paintComponent(graphics); //这里必须调用一下父类 也就是 container的重绘方法,否则表现为之前的绘图不会覆盖

drawGridBackground(graphics);//画出背景网格线

drawSnake(graphics, grid.getSnake());//画蛇

drawFood(graphics, grid.getFood());//画食物

}

};

}

//画蛇

public void drawSnake(Graphics graphics, Snake snake) {

for(Iterator i = snake.body.iterator();i.hasNext();) {

Node bodyNode = (Node)i.next();

drawSquare(graphics, bodyNode,Color.BLUE);

}

}

//画食物

public void drawFood(Graphics graphics, Node food) {

drawCircle(graphics,food,Color.ORANGE);

}

//画格子背景,方便定位Snake运动轨迹,横竖各以10为单位的50个线。

public void drawGridBackground(Graphics graphics) {

graphics.setColor(Color.GRAY);

canvas.setBackground(Color.BLACK);

for(int i=0 ; i < 50 ; i++) {

graphics.drawLine(0, i*Settings.DEFAULT_NODE_SIZE, this.grid.getWidth(), i*Settings.DEFAULT_NODE_SIZE);

}

for(int i=0 ; i <50 ; i++) {

graphics.drawLine(i*Settings.DEFAULT_NODE_SIZE, 0, i*Settings.DEFAULT_NODE_SIZE , this.grid.getHeight());

}

graphics.setColor(Color.red);

graphics.fillRect(0, 0, this.grid.width, Settings.DEFAULT_NODE_SIZE);

graphics.fillRect(0, 0, Settings.DEFAULT_NODE_SIZE, this.grid.height);

graphics.fillRect(this.grid.width, 0, Settings.DEFAULT_NODE_SIZE,this.grid.height);

graphics.fillRect(0, this.grid.height, this.grid.width+10,Settings.DEFAULT_NODE_SIZE);

}

/*

* public abstract void drawLine(int x1,int y1,int x2,int y2)

在此图形上下文的坐标系中,使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线。

参数:

x1 - 第一个点的 x 坐标。

y1 - 第一个点的 y 坐标。

x2 - 第二个点的 x 坐标。

y2 - 第二个点的 y 坐标。

*/

//提供直接出现游戏结束的选项框的功能。

public static void showGameOverMessage() {

JOptionPane.showMessageDialog(null,"游戏结束","短暂的蛇生到此结束", JOptionPane.INFORMATION_MESSAGE);

}

//画图形的具体方法:

private void drawSquare(Graphics graphics, Node squareArea, Color color) {

graphics.setColor(color);

int size = Settings.DEFAULT_NODE_SIZE;

graphics.fillRect(squareArea.getX(), squareArea.getY(), size - 1, size - 1);

}

private void drawCircle(Graphics graphics, Node squareArea, Color color) {

graphics.setColor(color);

int size = Settings.DEFAULT_NODE_SIZE;

graphics.fillOval(squareArea.getX(), squareArea.getY(), size, size);

}

}

Controller

GameController

/*

* 接收窗体SnakeApp传递过来的有意义的事件,然后传递给Grid,让Grid即时的更新状态。

* 同时根据最新状态渲染出游戏界面让SnakeApp显示

*

*/

public class GameController implements KeyListener, Runnable{

private Grid grid;

private boolean running;

public GameController(Grid grid){

this.grid = grid;

this.running = true;

}

@Override

public void keyPressed(KeyEvent e) {

int keyCode = e.getKeyCode();

switch(keyCode) {

case KeyEvent.VK_UP:

grid.changeDirection(Direction.UP);

break;

case KeyEvent.VK_DOWN:

grid.changeDirection(Direction.DOWN);

break;

case KeyEvent.VK_LEFT:

grid.changeDirection(Direction.LEFT);

break;

case KeyEvent.VK_RIGHT:

grid.changeDirection(Direction.RIGHT);

break;

}

isOver(grid.nextRound());

GameView.draw();

}

private void isOver(boolean flag) {

if(!flag) {//如果下一步更新棋盘时,出现游戏结束返回值(如果flag为假)则

this.running = false;

GameView.showGameOverMessage();

System.exit(0);

}

}

@Override

/*run()函数中的核心逻辑是典型的控制器(Controller)逻辑:

修改模型(Model):调用Grid的方法使游戏进入下一步

更新视图(View):调用GameView的方法刷新页面*/

public void run() {

while(running) {

try {

Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);

isOver(grid.nextRound());

GameView.draw();

} catch (InterruptedException e) {

break;

}

// 进入游戏下一步

// 如果结束,则退出游戏

// 如果继续,则绘制新的游戏页面

}

running = false;

}

@Override

public void keyTyped(KeyEvent e) {

}

@Override

public void keyReleased(KeyEvent e) {

}

}

Model

Grid

/*

* 随机生成食物,维持贪吃蛇的状态,根据SnakeApp中的用户交互来控制游戏状态。

*/

public class Grid {

private Snake snake;

int width;

int height;

Node food;

private Direction snakeDirection =Direction.LEFT;

public Grid(int length, int high) {

super();

this.width = length;

this.height = high;

initSnake();

food = creatFood();

}

//在棋盘上初始化一个蛇

private void initSnake() {

snake = new Snake();

int x = width/2;

int y = height/2;

for(int i = 0;i<5;i++) {

snake.addTail(new Node(x, y));

x = x+Settings.DEFAULT_NODE_SIZE;

}

}

//棋盘上随机制造食物的功能。

//一直循环获取随机值,直到三个条件都不满足。

private Node creatFood() {

int x,y;

do {

x =(int)(Math.random()*100)+10;

y =(int)(Math.random()*100)+10;

System.out.println(x);

System.out.println(y);

System.out.println(this.width);

System.out.println(this.height);

}while(x>=this.width-10 || y>=this.height-10 || snake.hasNode(new Node(x,y)));

food = new Node(x,y);

return food;

}

//提供下一步更新棋盘的功能,移动后更新游戏和蛇的状态。

public boolean nextRound() {

Node trail = snake.move(snakeDirection);

Node snakeHead = snake.getBody().removeFirst();//将头部暂时去掉,拿出来判断是否身体和头部有重合的点

if(snakeHead.getX()<=width-10 && snakeHead.getX()>=10

&& snakeHead.getY()<=height-10 && snakeHead.getY()>=10

&& !snake.hasNode(snakeHead)) {//判断吃到自己和撞到边界

if(snakeHead.equals(food)) {

//原本头部是食物的话,将move操作删除的尾部添加回来

snake.addTail(trail);

food = creatFood();

}

snake.getBody().addFirst(snakeHead);

return true;//更新棋盘状态并返回游戏是否结束的标志

}

return false;

}

public Node getFood() {

return food;

}

public Snake getSnake() {

return snake;

}

public int getWidth() {

return width;

}

public int getHeight() {

return height;

}

//提供一个更改贪吃蛇前进方向的方法

public void changeDirection(Direction newDirection){

snakeDirection = newDirection;

}

}

Snake

/*

* 蛇类,实现了自身数据结构,以及移动的功能

*/

public class Snake implements Cloneable{

public LinkedList body = new LinkedList<>();

public Node move(Direction direction) {

//根据方向更新贪吃蛇的body

//返回移动之前的尾部Node(为了吃到时候后增加尾部长度做准备)

Node n;//临时存储新头部移动方向的结点

switch (direction) {

case UP:

n = new Node(this.getHead().getX(),this.getHead().getY()-Settings.DEFAULT_NODE_SIZE);

break;

case DOWN:

n = new Node(this.getHead().getX(),this.getHead().getY()+Settings.DEFAULT_NODE_SIZE);

break;

case RIGHT:

n = new Node(this.getHead().getX()+Settings.DEFAULT_NODE_SIZE,this.getHead().getY());

break;

default:

n = new Node(this.getHead().getX()-Settings.DEFAULT_NODE_SIZE,this.getHead().getY());

}

Node temp = this.body.getLast();

this.body.addFirst(n);

this.body.removeLast();

return temp;

}

public Node getHead() {

return body.getFirst();

}

public Node getTail() {

return body.getLast();

}

public Node addTail(Node area) {

this.body.addLast(area);

return area;

}

public LinkedList getBody(){

return body;

}

//判断参数结点是否在蛇身上

public boolean hasNode(Node node) {

Iterator it = body.iterator();

Node n = new Node(0,0);

while(it.hasNext()) {

n = it.next();

if(n.getX() == node.getX() && n.getY() == node.getY()) {

return true;

}

}

return false;

}

}

Direction

/*

* 用来控制蛇的移动方向

*/

public enum Direction {

UP(0),

DOWN(1),

LEFT(2),

RIGHT(3); //调用构造方法对方向枚举实例进行代码初始化

//成员变量

private final int directionCode;

//成员方法

public int directionCode() {

return directionCode;

}

Direction(int directionCode){

this.directionCode = directionCode;

}

}

Node

public class Node {

private int x;

private int y;

public Node(int x, int y) {

this.x = ((int)(x/10))*10;

this.y = ((int)(y/10))*10;

}//使用这种方法可以使得节点坐标不会出现个位数

public int getX() {

return x;

}

public int getY() {

return y;

}

@Override

//判断两个Node是否相同

public boolean equals(Object n) {

Node temp;

if(n instanceof Node) {

temp = (Node)n;

if(temp.getX()==this.getX() && temp.getY()==this.getY())

return true;

}

return false;

}

}

Settings

public class Settings {

public static int DEFAULT_NODE_SIZE = 10;//每一个节点方块的单位

public static int DEFAULT_MOVE_INTERVAL = 200;//蛇移动时间间隔

}

更多有趣的经典小游戏实现专题,分享给大家:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页
评论

打赏作者

国服露娜

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值