业务逻辑梳理和描述 |
面向对象设计贪吃蛇
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);
}
}
运行效果截图: