java 面向对象实现贪吃蛇小游戏

业务逻辑梳理和描述
面向对象设计贪吃蛇

1.找类和对象
a.Snake类 蛇类
b.Food类 食物类
c.Ground类 障碍物类
d.GamePanel 面板类 显示蛇、食物、障碍物。
2.找类的方法(把想到的先写出来)
2.1 Snake类
move()移动
eatFood(Food food)//吃食物
changeDirection();//改变方向
drawMe();//画出自己
蛇是否碰到自己、是否碰到食物、是否碰到障碍物
2.2 Food类
isEatBySnake(Snake snake)
drawMe()
2.3 Ground类
isEatBySnake(Snake snake)
drawMe()
2.4 GamePanel 显示蛇、食物和障碍物
display()方法
重写paintComponent()

3.控制器Controller
3.1其实贪吃蛇游戏就是控制Snake,Food,GroundPanel
专门用控制器来控制这四个对象---->作为成员并通过构造函数初始化
3.2控制器要能够控制蛇的移动主要是方向的改变
我们在操作的时候主要是通过键盘事件来控制
键盘事件由键盘监听器控制,那么我们的控制器首先需要是键盘的监听器
所以我们让Controller继承KeyAdapter(适配器),重写keyPressed方法
3.3蛇每次移动都应该判断蛇是否碰到了自己、障碍物,食物等
我们可以自己写一个监听器来监听这三个事件
写一个接口SnakeListener
蛇就需要有添加这个监听器的方法,接收这个监听器的成员
给Snake添加成员和方法
3.4 控制器也要是蛇的监听器,能够监听蛇是否碰到了自己、食物、障碍物
所以让Controller实现该接口
4.组装游戏
4.1创建所有类的对象
4.2控制器可以控制游戏的开始
添加startGame();—>就是让蛇start()跑起来
4.3在snake中添加start方法启动蛇的移动
start方法的实现启动一个线程让蛇不停的移动
4.4创建一个线程SnakeDriver
4.5给蛇添加监听器/面板添加键盘事件监听器
4.6创建窗体,添加面板,启动游戏,显示界面

总结:通过前面的4步,把架子已经搭好,面向对象的设计已经基本完成
采用的是mvc的方案,接下来具体实现
5.具体实现
5.1 用什么数据结构来存储蛇身
Snake最重要的动作是蛇的移动
蛇如何移动(火车拉的方式、去尾加头的方式)
所以数据结构选择LinkedList
那么LinkedList容纳什么类型呢,是矩形好呢还是点好
(因为蛇头走过的地方,蛇身都会经过)
所以用点Point(其实就是矩形的左上角坐标,每个方块的宽度、高度固定。)
将来也方便判断是否碰到食物、障碍物、自己
给蛇添加成员
5.2 贪吃蛇完成后,蛇身就是几个方块组成的
我们可以直接把面板划分成 行列各多少个格子组成即可
定义辅助类Global
格子划分
(4,2)它的真正坐标为(4格子的宽度,2格子的高度)。注:起始坐标为左上角坐标
(x格子,y格子)-- ->真的坐标是
(x格子的宽度,y格子的高度)
注: 图形化界面和数学坐标轴不同之处在于图形化界面为向右向下为增
5.3 初始化蛇身
格子中间开始横向三个格子作为初始蛇身
Snake类的init()方法中完成
画出蛇身 完成drawMe方法
5.4 完成蛇的移动move方法
去尾巴加头的方式
去完成方向的操作
修正反方向的问题
边界的问题(穿透)
5.5 完成食物操作
食物就是一个格子,格子的宽度和高度已经定义完成是固定的
这样的话就由左上角坐标来决定
左上角坐标就是一个点
可以让食物类直接继承Point类
食物出现的位置
控制器而言,游戏开始时,不但要让蛇移动,还应该出现食物
吃食物、把去掉的尾巴加回来即可
什么时候吃食物–>当蛇头碰到食物的时候
5.6 障碍物

相关代码实现以及运行效果

控制器类:

package org.yb.control;

import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JOptionPane;

import org.yb.entity.Food;
import org.yb.entity.Ground;
import org.yb.entity.Snake;
import org.yb.listener.SnakeListener;
import org.yb.view.GamePanel;
/**
 * 控制器类
 * @author yb
 *
 */
public class Controller extends KeyAdapter implements SnakeListener{
	private Snake snake;
	private Food food;
	private Ground ground;
	private GamePanel gamePanel;
	public Controller(Snake snake, Food food, Ground ground, GamePanel gamePanel) {
		super();
		this.snake = snake;
		this.food = food;
		this.ground = ground;
		this.gamePanel = gamePanel;
	}
	@Override
	public void keyPressed(KeyEvent e) {
		// TODO Auto-generated method stub
		// 返回这个事件中和键相关的整数键
		int keycode = e.getKeyCode();
		switch (keycode) {
		case KeyEvent.VK_UP:
			snake.changeDirection(Snake.UP);
			break;
		case KeyEvent.VK_DOWN:
			snake.changeDirection(Snake.DOWN);
			break;
		case KeyEvent.VK_LEFT:
			snake.changeDirection(Snake.LEFT);
			break;
		case KeyEvent.VK_RIGHT:
			snake.changeDirection(Snake.RIGHT);
			break;			
		}
	}
	@Override
	public void snakeMoved(Snake snake) {
		System.out.println("判断蛇是否碰到身体、是否碰到食物、障碍物");
		if(food.isEatBySnake(snake)){
			snake.eatFood(food);
			//食物被吃掉了,就应该有新的食物产生
			food.addFood(ground.getPoint(snake));
		}
		if(ground.isEatBySnake(snake)|| snake.isEatSelf()){
			//吃到后弹出一个对话框
			snake.setLife(false);
			JOptionPane.showConfirmDialog(null, "GameOver");
			System.exit(0);
		}
		//显示身体、食物、障碍物
		gamePanel.display(snake, food, ground);
	}
	/**
	 * 游戏开始
	 */
	public void startGame(){
		snake.start();
		food.addFood(ground.getPoint(snake));
	}

}

食物类:

package org.yb.entity;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;

import org.yb.util.Global;

/**
 * 食物
 * @author yb
 *
 */
public class Food extends Point{
   public void drawMe(Graphics g){
	   g.setColor(Color.red);
	   System.out.println("食物正在画出自己.....");
	   g.fill3DRect(x*Global.CELL_SIZE, y*Global.CELL_SIZE, Global.CELL_SIZE, Global.CELL_SIZE, true);
   }
   /**
    * 蛇是否碰到食物
    * 只要判断蛇头的点是否和食物的位置重合
    * 要得到蛇头
    * @param snake
    * @return
    */
   public boolean isEatBySnake(Snake snake){
	   System.out.println("判断蛇是否碰到了食物");
	   Point head = snake.getHead();
	   if(this.equals(head))
		   return true;
	return false;
   }
   public void addFood(Point p){
	   this.x = p.x;
	   this.y = p.y;
   }
}

障碍物类:

package org.yb.entity;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;

import org.yb.util.Global;

/**
 * 障碍物
 * @author yb
 *
 */
public class Ground {
   private int[][] rocks = new int[Global.HEIGHT][Global.WIDTH];
   public Ground(){
	   for(int y = 0; y < Global.HEIGHT;y++){
		   for(int x = 0;x < Global.WIDTH;x++){
			   if(y==0 || y==Global.HEIGHT-1)
				   rocks[y][x] = 1;
//			   if(x==0 || x==Global.WIDTH-1)
//				   rocks[y][x] = 1;
		   }
	   }
   }
   public boolean isEatBySnake(Snake snake){
	   System.out.println("判断蛇是否碰到了障碍物....."); 
	   Point head = snake.getHead();
	   for(int y = 0; y < Global.HEIGHT;y++){
		   for(int x = 0;x < Global.WIDTH;x++){
			   if(rocks[y][x] == 1 && head.x == x && head.y == y)
				   return true;
		   }
	   }
	   return false;
	}
   public void drawMe(Graphics g){
	   //设置画笔颜色
	   g.setColor(Color.yellow);
	   System.out.println("障碍物正在画出自己.....");
	   for(int y = 0; y < Global.HEIGHT;y++){
		   for(int x = 0;x < Global.WIDTH;x++){
			   if(rocks[y][x] == 1){
					g.fill3DRect(x * Global.CELL_SIZE, y * Global.CELL_SIZE,
							Global.CELL_SIZE, Global.CELL_SIZE, true);
			   }	  
		   }
	   }
   }
	/**
	 * 设置食物点的位置
	 * @return
	 */
   public Point getPoint(Snake snake){
	   int x,y;
	   do{
   	 x = new Random().nextInt(Global.WIDTH);
   	 y = new Random().nextInt(Global.HEIGHT); 
	   }while(rocks[y][x] == 1 || isFoodSnake(x,y,snake));
   	return new Point(x,y);
   }
   /**
    * 判断食物是否出现在蛇身上
    */
   public boolean isFoodSnake(int x,int y,Snake snake){
	   System.out.println("执行了吗");
	   for(Point p : snake.getBody()){ 
		   if(p.x == x && p.y == y){
			   return true;
		   }
	   }
	     return false;	   
   }
}

蛇类:

package org.yb.entity;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.LinkedList;

import org.yb.listener.SnakeListener;
import org.yb.util.Global;

/**
 * 蛇
 * @author yb
 *
 */
public class Snake {
	private SnakeListener snakeListener;
	private boolean life = true;
	private LinkedList<Point> body = new LinkedList<Point>();
	public static final int UP = 1;
	public static final int DOWN = -1;
    public static final int LEFT = 3;
    public static final int RIGHT = -3;
//    private int direction;//存储当前方向
    private int oldDirection,newDirection;
    private Point tail;//存储尾巴
	public Snake(){
		init();
	}
	private void init(){
		int x = Global.WIDTH/2;
		int y = Global.HEIGHT/2;
		for(int i=0;i<3;i++){
			body.add(new Point(x-i,y)); 	
		}
//		this.direction = RIGHT;
		this.oldDirection = this.newDirection = RIGHT;
	}
	/**
	 * 蛇移动的方法
	 */
    public void move(){
        //去尾巴
    	tail = body.removeLast();
    	//加头-->得到当前的头部
    	int x = body.getFirst().x;
    	int y = body.getFirst().y;
    	/*
    	 * 获得新的头部
    	 * 要确定方向,才能知道新的头部
    	 * 在初始化构造蛇身的时候默认的方向其实认为是向右的
    	 * 我们定义出所有的方向
    	 * 并完成changeDirection方法
    	 */
    	//this.direction
    	if(this.oldDirection+this.newDirection!=0)
    		this.oldDirection = this.newDirection;
    	switch (this.oldDirection) {
		case UP:
			y--;
			if(y<0) y=Global.HEIGHT-1;
			break;
		case DOWN:
			y++;
			if(y>=Global.HEIGHT)y=0;
			break;
		case LEFT:
			x--;
			if(x<0)x=Global.WIDTH-1;
			break;
		case RIGHT:
			x++;
			if(x>=Global.WIDTH)x=0;
			break;	
		}
    	body.addFirst(new Point(x,y));
    	System.out.println("蛇正在移动.....");
    }
    /**
     * 吃食物
     * 去掉的尾巴加回来即可
     * @param food
     */
    public void eatFood(Food food){
    	body.addLast(tail);
    	System.out.println("蛇正在吃食物.....");	
    }
    /**
     * 改变方向
     */
    public void changeDirection(int direction){
//    	if(this.direction+direction!=0)
//    	this.direction = direction;
    	this.newDirection = direction;
    	System.out.println("蛇正在改变方向.....");	
    }
    public void drawMe(Graphics g){
    	System.out.println("蛇正在画出自己.....");	
    	g.setColor(Color.BLUE);
    	for(Point p : body){
    		//用预定的颜色填充一个突出显示的矩形
			g.fill3DRect(p.x * Global.CELL_SIZE, p.y
					* Global.CELL_SIZE, Global.CELL_SIZE, Global.CELL_SIZE,
					true);
    	}
    }
    /**
     * 是否吃到自己
     * @return
     */
    public boolean isEatSelf(){
    	for(int i = 1;i < body.size();i++){
    		if(body.get(i).equals(getHead())){
    			return true;
    		}
    	}
    	return false;
    }
    public void addSnakeListener(SnakeListener snakeListener){
    	if(snakeListener!=null)
    		this.snakeListener = snakeListener;
    }
    public void start(){
    	new SnakeDriver().start();
    }
    /**
     * 获取蛇头
     * @return
     */
    public Point getHead(){
    	return body.getFirst();
    }
    /**
     * 蛇的存活状态
     * @param life
     */
    public void setLife(boolean life) {
		this.life = life;
	}
    /**
     * 设置公有的get、set方法
     * @author lenovo
     *
     */
    public LinkedList<Point> getBody(){
		return body;  	
    }
    //内部类
    private class SnakeDriver extends Thread{
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		while(life){
    			move();
    			snakeListener.snakeMoved(Snake.this);
    			try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    		}
    	}
    }
    
}

蛇的监听器类:

package org.yb.listener;

import org.yb.entity.Snake;
/**
 * 蛇的监听器
 * 主要监听蛇的移动
 * @author lenovo
 *
 */
public interface SnakeListener {
	/**
	 * 该方法去监听蛇是否碰到了自己、食物、障碍物
	 * @param snake
	 */
    public void snakeMoved(Snake snake);
}

操作界面类:

package org.yb.view;

import java.awt.Graphics;

import javax.swing.JPanel;

import org.yb.entity.Food;
import org.yb.entity.Ground;
import org.yb.entity.Snake;

/**
 * 操作界面
 * 
 * @author yb
 * 
 */
public class GamePanel extends JPanel {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private Snake snake;
	private Food food;
	private Ground ground;
	public void display(Snake snake, Food food, Ground ground) {
		System.out.println("面板正在显示.....");
		this.snake = snake;
		this.food = food;
		this.ground = ground;
        repaint();//调repaint()做画的工作,相当于调用了paintComponent(Graphics g)
	}

	// 画的方法
	@Override
	protected void paintComponent(Graphics g) {
		//把上一次面板清空
		super.paintComponent(g);
		if (snake != null && food != null && ground != null) {
			snake.drawMe(g);
			food.drawMe(g);
			ground.drawMe(g);
		}
	}
}

常量类:

package org.yb.util;

public class Global {
	//把面板设置成了如下格局
    public static final int CELL_SIZE = 15;//格子的宽度、高度
    public static final int WIDTH = 20;//横向20个格子
    public static final int HEIGHT = 20;//纵向也20个格子
}

游戏运行入口类:

package org.yb.test;


import javax.swing.JFrame;

import org.yb.control.Controller;
import org.yb.entity.Food;
import org.yb.entity.Ground;
import org.yb.entity.Snake;
import org.yb.util.Global;
import org.yb.view.GamePanel;

public class SnakeGameTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//实体对象的创建
		Snake snake = new Snake();//蛇
		Food food = new Food();//食物
		Ground ground = new Ground();//障碍物

		//视图对象的创建
		GamePanel gamePanel = new GamePanel();//游戏操作界面 
		//控制器的创建-->即是蛇的监听器也是键盘的监听器
		Controller c = new Controller(snake, food, ground, gamePanel);
		snake.addSnakeListener(c);
		//键盘监听事件,当键盘按下去c类将被其监听到,然后调用keyPressed(KeyEvent e)方法
		gamePanel.addKeyListener(c);
		//创建窗体
		JFrame frame = new JFrame("贪吃蛇version1.0");
		//width, height
		frame.setSize(Global.CELL_SIZE*Global.WIDTH+25, Global.CELL_SIZE*Global.HEIGHT+50);
		//关闭的时候自动退出
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//放在屏幕中间
		frame.setLocationRelativeTo(null);
		//让面板获得焦点,注:键盘事件要有效,面板必须获得焦点
		gamePanel.setFocusable(true);
		//添加面板
		frame.add(gamePanel);
		
		//启动游戏
		c.startGame();
		//显示窗体
		frame.setVisible(true);
	}

}

运行效果截图:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_46266503/article/details/105822006