java之贪吃蛇
素材资源
代码实现
-
逻辑思路
- 使用图片素材
- 创建游戏主界面
- 游戏状态设置,开始态和死亡态和暂停态
- 事件绑定
- 定时器使用
- 得分信息和小蛇长度
-
主要技术
- Java swing
- Java awt
-
代码部分
图片素材部分
public class Data { public static void main(String[] args) { new Data(); } static URL up=Data.class.getResource("上.png"); static URL down=Data.class.getResource("下.png"); static URL right=Data.class.getResource("右.png"); static URL left=Data.class.getResource("左.png"); static URL body=Data.class.getResource("身体.png"); static URL food=Data.class.getResource("食物.png"); static URL header=Data.class.getResource("header.png"); static ImageIcon iconUp = new ImageIcon(up); static ImageIcon iconDown = new ImageIcon(down); static ImageIcon iconRight = new ImageIcon(right); static ImageIcon iconLeft = new ImageIcon(left); static ImageIcon iconBody = new ImageIcon(body); static ImageIcon iconFood = new ImageIcon(food); static ImageIcon iconHeader = new ImageIcon(header); public Data() { } }
类名的class的getResource方法可以获取文件资源,返回值为URL类型,再通过图片图标的构造方法创建图片图标
启动类
public class StartGame { public static void main(String[] args) { new frame(); } }
主界面设计
class frame extends JFrame{ void load(){ setTitle("贪吃蛇"); setResizable(false); //界面大小不可改变 setLayout(new BorderLayout()); setBounds(100,100,918,720); setVisible(true); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); Container contentPane = getContentPane(); //contentPane.setBackground(Color.black); contentPane.add(new GamePanel()); } public frame(){ load(); }
游戏面板
public class GamePanel extends JPanel implements KeyListener, ActionListener { public GamePanel() { init(); //此语句不能重复执行,既不能写到init里 addKeyListener(this); } void init(){ score=0; isFail=false; timer.start(); //监听事件只能是ActionListener,需要调用start开启 setFocusable(true); //设置聚焦属性 direct="R"; len=3; snakex[0]=100;snakey[0]=100; snakex[1]=75;snakey[1]=100; snakex[2]=50;snakey[2]=100; foodx=25*(random.nextInt(33)+1); foody=25*(random.nextInt(23)+3); } private int score; private Timer timer = new Timer(100,this); //定时器,每隔一定时间执行监听事件,此处是this private boolean isStart=false; //游戏状态 private String direct="R"; private int len; //小蛇长度 private int []snakex=new int[500]; //小蛇坐标存储 private int[]snakey=new int[500]; private int foodx,foody; //食物坐标 private Random random = new Random(); private boolean isFail=false; //游戏结束判定 @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.fillRect(25,75,850,600); Data.iconFood.paintIcon(this,g,foodx,foody); //食物绘制 Data.iconHeader.paintIcon(this,g,25,10); //头部绘制 g.setColor(Color.WHITE); g.setFont(new Font("微软雅黑",24,20)); g.drawString("长度"+" "+len,650,31); //长度显示 g.drawString("积分"+" "+score,650,55); //积分显示 this.setBackground(Color.darkGray); //面板背景颜色 g.setColor(Color.BLACK); //画笔颜色 //头部绘制 switch (direct){ case "R": Data.iconRight.paintIcon(this,g,snakex[0],snakey[0]); break; case "L": Data.iconLeft.paintIcon(this,g,snakex[0],snakey[0]); break; case "U": Data.iconUp.paintIcon(this,g,snakex[0],snakey[0]); break; case "D": Data.iconDown.paintIcon(this,g,snakex[0],snakey[0]); break; default: Data.iconRight.paintIcon(this,g,snakex[0],snakey[0]); } for (int i=1;i<len;i++) //身体绘制 { Data.iconBody.paintIcon(this,g,snakex[i],snakey[i]); } //游戏状态 if (isFail) { g.setColor(Color.red); g.setFont(new Font("微软雅黑",24,50)); //设置字体 g.drawString("游戏失败",300,350); } else { if (!isStart) { g.setColor(Color.WHITE); g.setFont(new Font("微软雅黑",24,50)); //设置字体 g.drawString("按下空格继续游戏",300,350); } } } @Override public void keyPressed(KeyEvent e) { int keycode=e.getKeyCode(); if (keycode==KeyEvent.VK_SPACE) { //判断游戏状态 if (isFail) { init(); isStart=false; this.repaint(); } else { isStart=!isStart; this.repaint();//改变游戏状态 } } if (isStart) { if (keycode==KeyEvent.VK_UP) direct="U"; else if (keycode==KeyEvent.VK_DOWN) direct="D"; else if (keycode==KeyEvent.VK_LEFT) direct="L"; else if (keycode==KeyEvent.VK_RIGHT) direct="R"; } } @Override //事件监听 public void actionPerformed(ActionEvent e) { //判断小蛇是否吃到食物 if (snakex[0]==foodx&&snakey[0]==foody) { len++; score+=10; foodx=25*(random.nextInt(33)+1); foody=25*(random.nextInt(23)+3); } if (!isFail) { for (int i=1;i<len;i++) { if (snakex[0]==snakex[i]&&snakey[0]==snakey[i]) { isFail=true; isStart=false; //让游戏停止 } } } if (isStart) { if (direct.equals("R")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakex[0]+=25; if (snakex[0]>850) snakex[0]=25; }if (direct.equals("L")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakex[0]-=25; if (snakex[0]<25) snakex[0]=850; }if (direct.equals("U")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakey[0]-=25; if (snakey[0]<75) snakey[0]=650; }if (direct.equals("D")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakey[0]+=25; if (snakey[0]>650) snakey[0]=75; } } //每隔一段时间进行窗口重绘 this.repaint(); } //无影响代码 @Override public void keyReleased(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } }
难点分析
-
小蛇头部朝向和食物显示重叠,只需更改小蛇头部绘制与食物绘制代码的先后顺序即可。
Data.iconFood.paintIcon(this,g,foodx,foody); //食物绘制 Data.iconHeader.paintIcon(this,g,25,10); //头部绘制
-
键盘监听更改游戏状态
public void keyPressed(KeyEvent e) { int keycode=e.getKeyCode(); if (keycode==KeyEvent.VK_SPACE) { //判断游戏状态 if (isFail) { init(); isStart=false; this.repaint(); } else { isStart=!isStart; this.repaint();//改变游戏状态 } } if (isStart) { if (keycode==KeyEvent.VK_UP) direct="U"; else if (keycode==KeyEvent.VK_DOWN) direct="D"; else if (keycode==KeyEvent.VK_LEFT) direct="L"; else if (keycode==KeyEvent.VK_RIGHT) direct="R"; } }
键盘监听判断按下键位是否是空格,当游戏正在start时,更改游戏状态为暂停状,反之亦然更改为开始态。如果是死亡态,则更改游戏为暂停态并进行游戏初始化。
repaint方法为重新绘制界面,仅仅为界面信息,并不会初始化游戏信息。
-
游戏结束判定
if (!isFail) { for (int i=1;i<len;i++) { if (snakex[0]==snakex[i]&&snakey[0]==snakey[i]) { isFail=true; isStart=false; //让游戏停止 } } }
当小蛇头部与身体部位重合则游戏结束
-
小蛇移动,坐标信息更改
if (isStart) { if (direct.equals("R")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakex[0]+=25; if (snakex[0]>850) snakex[0]=25; }if (direct.equals("L")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakex[0]-=25; if (snakex[0]<25) snakex[0]=850; }if (direct.equals("U")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakey[0]-=25; if (snakey[0]<75) snakey[0]=650; }if (direct.equals("D")) { for (int i=len-1;i>0;i--) { snakex[i]=snakex[i-1]; snakey[i]=snakey[i-1]; } snakey[0]+=25; if (snakey[0]>650) snakey[0]=75; } }
该部分主要难点就是,小蛇移动后,小蛇后一个身体部分坐标等于前一个身体,该部分赋值需要有后向前。
-
小蛇吃到食物,食物随机坐标显示
if (snakex[0]==foodx&&snakey[0]==foody) { len++; score+=10; foodx=25*(random.nextInt(33)+1); foody=25*(random.nextInt(23)+3); }
当小蛇头部坐标与食物坐标重合即为吃到食物,同时食物更新存储,通过random方法。
-
通过定时器制作小蛇移动效果
private Timer timer = new Timer(100,this); //定时器,每隔一定时间执行监听事件,此处是this timer.start();
创建定时器参数为执行时延和执行事件,需要调用start方法来开启
{ len++; score+=10; foodx=25*(random.nextInt(33)+1); foody=25*(random.nextInt(23)+3); }
> 当小蛇头部坐标与食物坐标重合即为吃到食物,同时食物更新存储,通过random方法。
-
通过定时器制作小蛇移动效果
private Timer timer = new Timer(100,this); //定时器,每隔一定时间执行监听事件,此处是this timer.start();
创建定时器参数为执行时延和执行事件,需要调用start方法来开启