Java实现贪吃蛇小游戏

01-Swing相关

  • Swing 包下的类、方法—>GUI开发---->图形用户界面
  • 用到三个类JFrame、JPanel、JButtom
  • JFrame:顶层容器,所有的元素都放在他的里面
  • JPanel:中层容器

02-SnakeFrame

  • 容器分成两个部分(属性):SnakePanel(放蛇的容器)、ButtonPanel(放按钮的容器)
  • SnakeFrame顶层容器进行两个操作(方法):initFrame()(初始化)、addComponent()(添加元素)
  • 属性和方法在初始化的时候就应该被调用,初始化方法、添加元素方法和组件显示方法可以放在构造方法内部(程序运一开始运行的时候顶层容器、放蛇的容器、放按钮的容器就应该都显示和添加好)
  • initFrame()—初始化方法
    //设置顶层容器的标题
    this.setTitle(“贪吃蛇”);
    //设置顶层容器的大小
    this.setBounds(int初始位置x, 初始位置y, 706, 500);
    //设置顶层容器大小无法更改
    this.setResizable(false);
    //设置顶层容器关闭界面后关闭进程
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  • addComponent()—添加元素组件
    //添加组件
    this.add(snakePanel);
    this.add(buttonPanel);
  • SnakeFrame()—重写无参构造方法
    //初始化
    initFrame();
    //添加组件
    addComponent();
    //设置顶层容器内容可见
    this.setVisible(true);

03-SnakePanel

  • 中层容器里面包含Snake(蛇)、Food(食物)、SnakeThread(多线程)
  • SnakePanel中层容器进行两个操作(方法):initPanel()(初始化)、addComponent()(添加元素)、paint()(前期画上网格线)
  • 初始化方法和添加组件方法在对象被创建的时候就应该被调用,将这两个方法放在初始化方法中
  • paint(Graphics g)—画线拼网格
    重写父类实现的方法:
  • g.setColor(Color.gray);//改变画笔的颜色
  • for (int i = 0; i < Config.ROWS; i++) {
    g.drawLine(0, Config.SPANi, Config.SPANConfig.COLS, Config.SPAN*i);
    }
    这个地方画线的控制搞不清楚,只需要记住:
  • ROWS-行
  • COLS-列
    画横线必须小于行 i<Config.ROWS
    画竖线必须小于列 i<Config.COLS
  • drawLine(x1,y1,x2,y2);
    如果不会想就看第二条线(横竖都是)
  • initpanel()—初始化操作
    //设置大小
    this.setSize(700,440);
    //设置背景染色
    this.setBackground(Color.black);
  • SnakePanel()—重写无参构造方法
    initPanel();
    addCompoent();
    //启动线程
    snakeThread.start();
  • paint(Graphics g)—重写父类的画线方法
    super.paint(g);
    //画笔
    g.setColor(Color.gray);
    //划线 横线 竖线
    for (int i = 0; i < Config.ROWS; i++) {
    g.drawLine(0, Config.SPANi, Config.SPANConfig.COLS, Config.SPANi);
    }
    for (int i = 0; i < Config.COLS; i++) {
    g.drawLine(Config.SPAN
    i, 0, Config.SPANi, Config.SPANConfig.ROWS);
    }

04-Config

  • 常量配置
    (1)行、列、间距
    (2)上、下、左、右四个方向
    (3)死亡检测的判断
    (4)暂停继续的控制
    (5)速度的设置
    (6)得分
    (7)valid(暂时还没用到)
public static final int ROWS = 22;//行
	public static final int COLS = 35;//列
	public static final int SPAN = 20;//间距
	
	public static final String U = "u";//方向-上
	public static final String D = "d";//方向-下
	public static final String L = "l";//方向-左
	public static final String R = "r";//方向-右
	
	public static boolean islive = true;//死亡检测的标识
	public static int speed = 400;//速度,间隔多少毫秒移动一次
	public static boolean valid = false;//当前的 单位时间内 有无 有效按键事件false代表没有
	public static boolean isgone = true;//暂停 继续
	public static int score = 0;//得分

//	public static User user;//当前登陆人
	
	//控制条件初始化
	public static void reload() {
		islive = true;
		valid = false;
		score = 0;
		speed = 400;
		isgone = true;
	}

05-Food

  • 食物类
  • 食物是一个方格所以他也是由坐标构成
    定义两个属性:col—列、row—行
  • 食物可以进行操作:draw(Graphics g)(画食物)、reGen()(重新生成食物)
  • 食物在被创建的时候就需要生成,同样是沿用之前的重写无参构造方法
  • draw(Graphics g)—画食物
    //设置食物为红颜色
    g.setColor(Color.red);

    //画填充颜色的矩形
    g.fillRect(colConfig.SPAN, rowConfig.SPAN, Config.SPAN, Config.SPAN);
    //画矩形框
    g.drawRect(0, 0, Config.SPAN, Config.SPAN);

  • void reGen()—重新生成食物坐标
    this.col=new Random().nextInt(Config.COLS);
    this.row = new Random().nextInt(Config.ROWS);
  • Food()—重写无参构造方法
    this.reGen();
  • 在SnakePanel中画线的地方调用画食物方法
  • 蛇吃食物需要确定食物所在的位置
    Rectangle getFoodRctangle()—获取食物所在的矩形—直接返回食物所在的矩形
    return new Rectangle(
    this.colConfig.SPAN,
    this.row
    Config.SPAN,
    Config.SPAN,
    Config.SPAN);
    }

06-Snake

  • 蛇类
  • 蛇有自己的头、躯干、尾巴对应属性
    Node head(头)、Node body(躯干)、Node tail;(尾巴)
  • 蛇在初始化的时侯定义为头、身、尾三段,并且还要让三个节点关联起来
  • 关联好点之后要将三个节点画出来也就是画蛇
    draw(Graphics g)
  • 蛇要移动起来
    addHead()(添加头)、remove()(去掉尾)
  • Snake()—蛇的初始化
    this.head = new Node(17, 11, Config.R);
    this.body = new Node(16, 11, Config.R);
    this.tail = new Node(15, 11, Config.R);

    //让三个节点关联,创建一个双向链表
    head.pre=null;
    head.next=body;
    body.pre=head;
    body.next=tail;
    tail.pre=body;
    tail.next=null;

  • draw(Graphics g)—画蛇
    //画蛇调用了节点的draw();方法
  • //画每个节点
    for(Node n = head; n!=null; n=n.next){
    n.draw(g);
    }
  • 在SnakePanel中调用画蛇的方法
  • addHead()—添加头
    //判断蛇当前往哪个方向走,然后在头相应的位置添加一个元素
    Node node = null;
    //根据蛇当前移动的方向,创建要添加的节点
    switch (this.head.getDir()) {
    case Config.U:
    node = new Node(this.head.getCol(), this.head.getRow()-1, this.head.getDir());
    break;
    case Config.D:
    node = new Node(this.head.getCol(), this.head.getRow()+1, this.head.getDir());
    break;
    case Config.L:
    node = new Node(this.head.getCol()-1, this.head.getRow(), this.head.getDir());
    break;
    case Config.R:
    node = new Node(this.head.getCol()+1, this.head.getRow(), this.head.getDir());
    break;
    default:
    break;
    }
    node.pre=null;
    node.next=head;
    head.pre=node;
    head=node;
  • 注意:
    向上和向下走的时候不会改变节点所在的列
    向左和向右走的时候不会改变焦点所在的行
  • 要将新的头节点和之前的头节点关联起来

removeTali()—去掉尾
//保存下来最后一个节点
Node temp = tail;
tail = tail.pre;
tail.next = null;
temp.pre = null;

  • void move()—蛇移动的方法
    //添加头
    addHead();
    //删去尾
    removeTail();
  • 在画蛇的地方调用蛇移动的方法
  • 因为让蛇移动的方法是不断地画蛇
    所以需要开启线程
    SnakePanel里边
    //多线程(2种实现方式)
    //内部类 可以访问外部类的所有成员变量和方法
    class SnakeThread extends Thread{
    @Override
    public void run() {
    super.run();
    while(Config.islive){
    try {
    Thread.sleep(400);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //System.out.println("****************");
    //重新绘制SnakePanel
    repaint();
    }
    }
    }

*蛇类需要被键盘控制移动的方向
dirControl(keyEvent e)—键盘控制蛇移动的方向
需要注意的是,蛇在运动的时候指定与现在运动相反的方向是无效的

  • dirControl(keyEvent e)—键盘控制蛇移动的方向
    //获取键盘按键的编码
    switch (e.getKeyCode()) {
	case KeyEvent.VK_W:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.D)){
			//设置他的方向
			this.head.setDir(Config.U);
		}
		break;
		
	case KeyEvent.VK_S:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.U)){
			//设置他的方向
			this.head.setDir(Config.D);
		}
		break;
		
	case KeyEvent.VK_A:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.R)){
			//设置他的方向
			this.head.setDir(Config.L);
		}
		break;
		
	case KeyEvent.VK_D:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.L)){
			//设置他的方向
			this.head.setDir(Config.R);
		}
		break;
	
	
	//上下左右键
	case KeyEvent.VK_UP:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.D)){
			//设置他的方向
			this.head.setDir(Config.U);
		}
		break;
		
	case KeyEvent.VK_DOWN:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.U)){
			//设置他的方向
			this.head.setDir(Config.D);
		}
		break;
		
	case KeyEvent.VK_LEFT:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.R)){
			//设置他的方向
			this.head.setDir(Config.L);
		}
		break;
		
	case KeyEvent.VK_RIGHT:
		//不能向相反的方向转换方向
		if(!this.head.getDir().equals(Config.L)){
			//设置他的方向
			this.head.setDir(Config.R);
		}
		break;
		
	default:
		break;
	}
  • e.getKeyCode()—该方法可以获取键盘按键的编码
  • KeyEvent.VK_*—可以获取键盘上的按键
  • 设置好之后蛇还是不能通过键盘进行控制,因为需要Snakepanel绑定键盘监听器:
    this.addKeyListener(this);
  • 这个时候需要SnakePanel实现一个接口KeyListener
  • KeyEvent类是表示键盘点击事件
  • 实现了KeyListener接口之后实现方法
    @Override
    public void keyTyped(KeyEvent e) {
    // TODO Auto-generated method stub

}

@Override
public void keyPressed(KeyEvent e) {
// 在按键点击的时候调用蛇的方向控制方法
this.snake.dirControl(e);

}

@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub

}

  • SnakePanel需要获取焦点
    SnakeFrame中设置SnakePanel获取焦点
    this.setLayout(null);
    this.snakePanel.setFoucusable(true);
    this.snakePanel.requestFoucus();
  • 蛇可以吃食物
    void eatOrNot()—蛇吃食物检测
    思想:蛇头所在的方格和食物所在的方格重合了即是吃到食物了
  • 获取蛇头所在的方格
  • 获取食物所在的方格

Rectangle类可以定义一个矩形

  • Rctangle getHeadRactangle()—获取蛇头所在的矩形—直接返回蛇头所在的矩形
    return new Rectangle(
    this.head.getCol()*Config.SPAN,
    this.head.getRow()*Config.SPAN,
    Config.SPAN,
    Config.SPAN);
  • 这里的蛇头的横坐标是列x间距
  • 这里的蛇头的纵坐标是行x间距
  • eatOrNot()—判断蛇是否吃了食物
    如果蛇吃了食物就添加蛇头、重新生成食物
    方法().contains()判断字符串中是否含有字符串
    if(this.getHeadRctangle().contains(this.food.getFoodRctangle())){
    //添加头
    this.addHead();
    //重新生成食物
    this.food.reGen();
    }
  • 为了比较蛇和食物所在的空格,需要调用食物的获取方格方法
  • Food food在snake初始化的时候就被作为参数传入
  • Snakepanel中调用蛇吃食物的方法
  • 蛇需要进行死亡检测
    蛇死亡了存在一下情况:
    (1)头碰到了边界
    (2)头碰到了身体
    如果蛇死亡了则不再重复进行画蛇
    islive控制画蛇

deadCheck()—死亡检测
//head碰到了边界
if(
this.head.getCol()<0||
this.head.getCol()>Config.COLS ||
this.head.getRow()<0 ||
this.head.getRow()>Config.ROWS){
Config.islive = false;
return;
}
判断头是否超出了边界的框框,四个边,如果发现超出了就将isalive设置为false

  • head碰到了body
    遍历身体上的节点,如果哪个节点和头节点重合了isalive设置为false
    //head碰到了body
    for(Node node = head.next;node!=null;node=node.next){
    Rectangle nodeRect = new Rectangle(
    node.getCol()*Config.SPAN,
    node.getRow()*Config.SPAN,
    Config.SPAN,
    Config.SPAN);
    if(this.getHeadRctangle().contains(nodeRect)){
    Config.islive = false;
    return;
    }
    }

07-Node

  • 节点类
  • 节点用横纵坐标来表示属性:
    col—横坐标
    row—纵坐标
  • 节点具有方向
    dir—方向
  • 节点具有双向指针
    pre—指向前一个元素
    next—指向后一个元素
  • 节点在被创建的时候就应该具有坐标和方向
    Node(int col, int row, String dir)(无参构造方法)
  • 画节点的时候节点要区分头节点和身体节点的颜色
    void draw(Graphics g)
  • Node(int col,int row,String dir)—重写构造方法
    super();
    this.col = col;
    this.row = row;
    this.dir = dir;
  • void draw(Graphics g)
    if(this.pre==null){//判断是否为头节点
    g.setColor(Color.yellow);
    }else{
    g.setColor(Color.blue);
    }
    //该方法可以画填充的圆圈
    g.fillOval(colConfig.SPAN, rowConfig.SPAN, Config.SPAN, Config.SPAN);

8-ButtonPanel

  • 按钮容器
    为了实现监听按钮需要实现一个接口
    ActionListener
    按钮容器需要有四个按钮(属性):
    btn_pause(暂停游戏)
    btn_continue(继续游戏)
    btn_restart(重新开始)
    btn_rank(游戏排行)
  • 按钮容器的动作(方法)
  • void init()—初始化方法
    设置大小
    添加按钮监听
  • void addComponent()—添加元素方法
    添加四个按钮元素
  • ButtonPanel(SnakePanel)—重写初始化方法
    this.snakePanel = snakePanel;
    init();
    addComponent();
  • init()—初始化方法
    this.setBounds(0, 435, 706, 60);
    btn_continue.addActionListener(this);
    btn_pause.addActionListener(this);
    btn_rank.addActionListener(this);
    btn_restart.addActionListener(this);
  • addComponent()—添加元素
    this.add(btn_pause);
    this.add(btn_continue);
    this.add(btn_restart);
    this.add(btn_rank);
  • void pauseGame()—暂停游戏方法
    设置一个标识符控制调用重新画蛇的方法
    isgone—控制画蛇标识符
    Config.isgone = false;

void contiueGame()—继续游戏方法
Config.isgone = true;

  • void actionPerformed(ActionEvent e)实现接口的方法
    //暂停
    if(e.getSource() == btn_pause){
    pauseGame();
    }

    //继续
    if(e.getSource()== btn_continue){
    this.setLayout(null);
    this.snakePanel.setFocusable(true);
    this.snakePanel.requestFocus();
    continueGame();
    }

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以运行! (以下代码只是其中的一个类) package chy.snake.entities; import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import chy.snake.listener.SnakeListener; import chy.snake.util.Global; public class Snake { public static final int up = 1; public static final int down = -1; public static final int left = -2; public static final int right = 2; private int oldDirection,newDirection; //newDirection:一次时间 间隔内输入的最后方向 private Point oldTail; private boolean life; //life 为 true或者false,初始为true, 用于118行 private LinkedList<Point> body = new LinkedList<Point> (); //需要经常访问蛇的第一个和最后一个节点,使用链表LinkedList存放蛇的身体节点,因为它有getFirst(),getLast(),removeLast(),方法 private Set<SnakeListener> listeners = new HashSet<SnakeListener>(); public Snake(){ init(); } public void init(){ //初始化 int x = Global.WIDTH/2; int y = Global.HEIGHT/2; for(int i=0;i<3;i++){ //初始长度3 body.addLast(new Point(x-i,y)); //是addLast } oldDirection = newDirection = right; //初始方向 右 life = true; } public void die(){ life = false; } public void move(){ System.out.println("Snake's move"); if (!(oldDirection + newDirection == 0)){ oldDirection = newDirection; } //1.去尾 oldTail = body.removeLast(); int x = body.getFirst().x; int y = body.getFirst().y; //蛇头的x,y坐标 switch(oldDirection){ case up: y--; break; case down: y++; break; case left: x--; break; case right: x++; break; } Point newHead = new Point(x,y); //2.加头 body.addFirst(newHead); } public void changeDirection(int direction){ /*无效方向:在蛇的这一次移动之后和下一次移动之前的 这个时间间隔内输入了多个方向,只有最后一个方向 是 有效方向,其余的都为无效方向*/ System.out.println("Snake's changeDirection"); newDirection = direction; //将一个时间间隔内按得最后方向,赋给 newDirection } public void eatFood(){ System.out.println("Snake's eatFood"); body.addLast(oldTail); //后面的节点不去掉 } public boolean isEatFood(){ System.out.println("Snake's isEatFood"); return false; } public boolean isEatBody(Snake snake){ //比较蛇是否吃到身体 System.out.println("snake's isEatBody"); for(int i= 1;i<body.size();i++){ //i 从蛇头结点的下一个节点开始,排除蛇头结点 if(body.get(i).equals(this.getHead())){ //如果i 的节点 和 头结点 相同 return true; } } return false; } public void drawMe(Graphics g){ System.out.println("Snake's drawMe"); g.setColor(Color.GREEN); //设置蛇的颜色 for(Point p : body){ g.fill3DRect(p.x * Global.CELL_SIZE, p.y * Global.CELL_SIZE, Global.CELL_SIZE, Global.CELL_SIZE, true); } } public Point getHead(){ //得到蛇头节点,判断吃食物 return body.getFirst(); } private class SnakeDriver implements Runnable{ //线程,不停的调用move方法 @Override public void run() { // TODO 自动生成的方法存根 while(life){ // 42和46行,life为true 或者false move(); for(SnakeListener l : listeners){ l.snakeMoved(Snake.this); //循环,依次调用SnakeMoved方法 } try { Thread.sleep(300); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } } public void start(){ new Thread(new SnakeDriver()).start(); //启动线程的方法 } public void addSnakeListener(SnakeListener l){ if(l != null){ this.listeners.add(l); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值