首先,写一个需求文档:
一、项目名称: 《天天酷跑》(RunDay)
二、功能介绍: 闯关类游戏,玩家登录后,选择进入游戏,通过键盘控制玩家的上下左右移动,来躲避 障碍物和吃金币,玩家躲避的障碍物越多跑酷距离越远,玩家吃的金币越多,得分越高。
三、功能模块: 1、登录界面 用户名(输入框,明文) 密码(输入框,密文) 登录、取消按钮
2、菜单选择界面 开始游戏按钮(图片按钮) 帮助按钮 退出按钮
3、缓冲加载界面 自动加载进度条,加载完毕之后,跳转到下一界面
4、游戏主界面 移动的背景图片、动态的玩家、五种障碍物持续出现、玩家和障碍物的碰撞、 暂停、继续功能、玩家的移动功能
5、结束界面 获取玩家的得分、跑酷距离。继续游戏、返回主菜单的功能。
四、开发者: Huey
五、版本号: 1.0
六、开发时间: 2020.11.16
开发模式:MVC模式
M:Model(数据层),存储的是实体类。
V:View(显示层),存储的是关于界面的类。
C:Controller(控制层),存储的是相关的逻辑层代码。
企业级项目命名规范:
一、登录界面 界面功能需求图如下:
接下来我们再做一些准备工作:导入相关图片素材。
将天天酷跑的图片(Image)资源解压到桌面后,(Image文件如下图所示:)
复制到Eclipse中,单击src,直接Ctrl+V。
本文将实现cn.sqc.runday.view这一界面内容。
相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 package cn.sqc.runday.view;import java.awt.Font;import java.awt.Graphics;import java.awt.Image;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import javax.swing.BorderFactory;import javax.swing.ImageIcon;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JOptionPane;import javax.swing.JPanel;import javax.swing.JPasswordField;import javax.swing.JTextField;public class LoginFrame extends JFrame { JLabel userLabel; JTextField userField; JLabel userLabel2; JPasswordField userField2; JButton Login,Cancel; public LoginFrame () { userLabel = new JLabel("用户名" ); userLabel.setFont(new Font("微软雅黑" ,Font.BOLD,18 )); userLabel2 = new JLabel("密 码" ); userLabel2.setFont(new Font("微软雅黑" ,Font.BOLD,18 )); userLabel.setBounds(20 , 220 , 100 , 30 ); this .add(userLabel); userLabel2.setBounds(20 , 280 , 100 , 30 ); this .add(userLabel2); userField = new JTextField(); userField.setBounds(80 , 220 , 100 , 30 ); userField.setBorder(BorderFactory.createLoweredBevelBorder()); userField.setOpaque(false ); this .add(userField); userField2 = new JPasswordField(); userField2.setBounds(80 , 280 , 100 , 30 ); userField2.setBorder(BorderFactory.createLoweredBevelBorder()); userField2.setOpaque(false ); this .add(userField2); Login = new JButton("登录" ); Login.setBounds(45 ,350 ,60 ,36 ); Login.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { String userName = userField.getText(); String passWord = userField2.getText(); if ("Huey" .equals(userName) && "123" .equals(passWord)){ JOptionPane.showMessageDialog(null , "欢迎" +userName+"来到天天酷跑游戏" ); dispose(); }else if ("" .equals(userName) || "" .equals(passWord)){ JOptionPane.showMessageDialog(null , "用户名 / 密码不能为空,请重新输入!" ); }else { JOptionPane.showMessageDialog(null , "用户名 / 密码输入错误,请重新输入!" ); } } }); this .add(Login); Cancel = new JButton("取消" ); Cancel.setBounds(135 ,350 ,60 ,36 ); this .add(Cancel); Cancel.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { dispose(); } }); LoginPanel panel = new LoginPanel(); this .add(panel); this .setSize(900 ,530 ); this .setLocationRelativeTo(null ); this .setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this .setUndecorated(true ); this .setIconImage(new ImageIcon("Image/115.png" ).getImage()); this .setVisible(true ); } public static void main (String[] args) { new LoginFrame(); } class LoginPanel extends JPanel { Image background; public LoginPanel () { try { background = ImageIO.read(new File("Image/login.jpg" )); } catch (IOException e) { e.printStackTrace(); } } @Override public void paint (Graphics g) { super .paint(g); g.drawImage(background, 0 , 0 ,900 ,530 , null ); } } }
为了更清楚地看出代码结构,这里给出部分代码的作用域。
LoginFrame作用域一直到最后一个}
LoginPanel的代码块:
运行结果截图:
1.界面
2.登录
2.1、用户名及密码输入为空的情况:
2.2、用户名或密码输入错误的情况:
2.3、用户名及密码输入正确的情况:
单击弹窗中的“确定”,直接退出。
3.退出
点“取消”即可
二、开始游戏界面 前文,我们完成了登录界面的搭建。接下来将完成开始游戏界面的搭建,并建立起登录界面与开始游戏界面的桥梁。
实现在输对用户名和密码后即可进入开始游戏界面的功能。
界面功能需求图:
具体要求: 当鼠标移入开始游戏按钮后,按钮将由暗变亮,鼠标移开后,按钮又由亮变暗。
帮助、离开按钮同理。
另外,当点击离开时,需要实现关闭当前界面的效果。
上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 package cn.sqc.runday.view;import java.awt.Graphics;import java.awt.Image;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import javax.swing.ImageIcon;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JOptionPane;import javax.swing.JPanel;import cn.sqc.runday.controller.WindowFrame;public class MainFrame extends JFrame implements MouseListener { JLabel start,help,exit; JPanel MainPanel; public MainFrame () { start = new JLabel(new ImageIcon("Image/hh1.png" )); start.setBounds(350 ,320 ,150 ,40 ); start.setEnabled(false ); start.addMouseListener(this ); this .add(start); help = new JLabel(new ImageIcon("Image/hh2.png" )); help.setBounds(350 ,420 ,150 ,40 ); help.setEnabled(false ); help.addMouseListener(this ); this .add(help); exit = new JLabel(new ImageIcon("Image/hh3.png" )); exit.setBounds(350 , 520 , 150 , 40 ); exit.setEnabled(false ); exit.addMouseListener(this ); this .add(exit); MainPanel panel = new MainPanel(); this .add(panel); this .setSize(1200 ,730 ); this .setLocationRelativeTo(null ); this .setUndecorated(true ); this .setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this .setIconImage(new ImageIcon("Image/115.png" ).getImage()); this .setVisible(true ); } public static void main (String[] args) { new MainFrame(); } class MainPanel extends JPanel { Image background; public MainPanel () { try { background = ImageIO.read(new File("Image/main.png" )); } catch (IOException e) { e.printStackTrace(); } } @Override public void paint (Graphics g) { super .paint(g); g.drawImage(background, 0 , 0 ,1200 ,730 , null ); } } @Override public void mouseClicked (MouseEvent e) { if (e.getSource().equals(start)){ new WindowFrame().Start(); }else if (e.getSource().equals(exit)){ dispose(); }else if (e.getSource().equals(help)){ JOptionPane.showMessageDialog(null , "有疑问请联系开发者:Huey" ); } } @Override public void mousePressed (MouseEvent e) { } @Override public void mouseReleased (MouseEvent e) { } @Override public void mouseEntered (MouseEvent e) { if (e.getSource().equals(start)){ start.setEnabled(true ); }else if (e.getSource().equals(help)){ help.setEnabled(true ); }else if (e.getSource().equals(exit)){ exit.setEnabled(true ); } } @Override public void mouseExited (MouseEvent e) { if (e.getSource().equals(start)){ start.setEnabled(false ); }else if (e.getSource().equals(help)){ help.setEnabled(false ); }else if (e.getSource().equals(exit)){ exit.setEnabled(false ); } } }
测试: 先填补上文的缺憾,加上new MainFrame();语句。调用我们刚刚写好的开始游戏界面。
登录界面:
单击确定
完美进入我们写好的登录游戏界面:
现在看开始游戏按钮:
帮助按钮:
点击帮助按钮:
退出按钮:
点击:
大功告成!
三、缓冲加载游戏界面 前文,我们完成了开始游戏界面的搭建。接下来将实现缓冲加载界面的搭建。并搭建与前面俩界面间的桥梁。实现输入正确用户名密码后,进入开始游戏界面,点击开始游戏按钮后,进入缓冲加载界面的功能。
搜索 Java知音 公众号,回复“后端面试”,送你一份Java面试题宝典.pdf
界面示意图:
具体要求: 缓存加载界面:背景图片、进度条
动态加载过程。(线程)
我们想要实现动态的缓冲加载过程,让进度条动起来,就需要引入线程的概念了。
线程: Thread类中这样定义:
线程是程序中执行的线程,Java虚拟机允许程序同时运行多个执行线程。
举个例子,你用百度网盘下载一部电影,这就是一个线程。而如果你同时下载多部电影,这就是多线程了。
1.线程有6种状态:新建,运行,阻塞,等待,计时等待和终止。
新建:当使用new操作符创建新线程时,线程处于“新建”状态。
运行(可运行):调用start()方法。
阻塞:当线程需要获得对象的内置锁,而该锁正在被其他线程拥有。
等待:当线程等待其他线程通知调度表可以运行时。
计时等待:对于一些含有时间参数的方法,如Thread类的sleep() 。
终止:当run()方法运行完毕或出现异常时。
2.创建线程的两种方式:
1、实现Runnable
2、实现Thread类
直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package cn.sqc.runday.controller;import java.awt.BorderLayout;import java.awt.Color;import javax.swing.ImageIcon;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JProgressBar;public class WindowFrame extends JFrame implements Runnable { JLabel background; JProgressBar jdt; public void Start () { WindowFrame frame = new WindowFrame(); Thread t = new Thread(frame); t.start(); dispose(); } public WindowFrame () { background = new JLabel(new ImageIcon("Image/hbg.jpg" )); this .add(BorderLayout.NORTH,background); jdt = new JProgressBar(); jdt.setStringPainted(true ); jdt.setBackground(Color.ORANGE); this .add(BorderLayout.SOUTH,jdt); this .setSize(568 ,340 ); this .setLocationRelativeTo(null ); this .setDefaultCloseOperation(3 ); this .setUndecorated(true ); this .setIconImage(new ImageIcon("Image/115.png" ).getImage()); this .setVisible(true ); } public static void main (String[] args) { new WindowFrame().Start(); } @Override public void run () { int [] values = {0 ,1 ,3 ,10 ,23 ,32 ,40 ,47 ,55 ,66 ,76 ,86 ,89 ,95 ,99 ,99 ,99 ,100 }; for (int i=0 ; i<values.length; i++){ jdt.setValue(values[i]); try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }
加载界面代码敲完,现在开始造桥。
现在,我们从第一个登录界面开始测试。
点击开始游戏:
非静止画面……
成功实现!
四、游戏主界面 接上文,接下来将实现游戏主界面,功能如下:
移动的背景图片、动态的玩家、玩家的移动功能、五种障碍物持续出现、玩家和障碍物的碰撞、暂停、继续功能。
首先,看一下整体效果:
动图实在太大,几秒钟的 Gif 就十几兆了。无奈,图片展示效果。
跳跃、得分、下落、障碍物:
碰到障碍物后,玩家被推着走。
下面,分别解释一下每个功能的逻辑:
1、创建一个显示窗体,承载游戏的主面板类。 GameFrame.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package cn.sqc.runday.view;import javax.swing.ImageIcon;import javax.swing.JFrame;import cn.sqc.runday.controller.GamePanel;public class GameFrame extends JFrame { public static final int WIDTH=1500 ; public static final int HEIGHT=900 ; public GameFrame () { GamePanel panel = new GamePanel(); panel.action(); this .addKeyListener(panel); this .add(panel); this .setSize(WIDTH,HEIGHT); this .setLocationRelativeTo(null ); this .setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this .setIconImage(new ImageIcon("Image/115.png" ).getImage()); this .setUndecorated(true ); this .setVisible(true ); } public static void main (String[] args) { new GameFrame(); } }
2、游戏主面板类(核心逻辑类): 背景图片滚动效果
使用两张背景图片,实现背景图片滚动效果的逻辑如下:
下面用视频演示一下:
玩家动态效果
我国早期很有名的一部动画片《大闹天宫》,由于当时没有电脑,所以需要一帧一帧的画,随后快速播放图片,形成动态的画面(我愿称之:真·动画),并为之配音,短短10分钟的动画却要画7000到10000张原画!
而此处,我们的玩家的奔跑姿态,同理是由九张图片构成。
下面动图演示:
下面是实现玩家的(生成、移动、绘制)的基本代码,后面的障碍物的实现,也都遵循这一编写逻辑。
为更方便读懂代码,已尽力注释,若仍有不清楚的地方,欢迎留言交流。
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 package cn.sqc.runday.model;import java.awt.Graphics;import java.awt.Image;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import cn.sqc.runday.view.GameFrame;public class Person { private Image image; private Image[] images; public static final int WIDTH = 120 ; public static final int HEIGHT = 120 ; private int x,y; int index; private int score; private int distance; public Person () { init(); image = images[0 ]; x = 90 ; y = 580 ; index = 0 ; score = 0 ; distance = 0 ; } public void drop () { y += 5 ; if (y>=580 ){ y = 580 ; } } public void step () { image = images[index ++ /3 %images.length]; } public void paintPerson (Graphics g) { g.drawImage(image, x, y, WIDTH, HEIGHT, null ); } public boolean outOfBounds () { return this .x >= GameFrame.WIDTH || this .x <= -WIDTH; } private void init () { images = new Image[9 ]; for (int i = 0 ; i<images.length; i++){ try { images[i] = ImageIO.read(new File("Image/" +(i+1 ) + ".png" )); } catch (IOException e) { e.printStackTrace(); } } } public Image getImage () { return image; } public void setImage (Image image) { this .image = image; } public Image[] getImages() { return images; } public void setImages (Image[] images) { this .images = images; } public int getX () { return x; } public void setX (int x) { this .x = x; } public int getY () { return y; } public void setY (int y) { this .y = y; } public static int getWidth () { return WIDTH; } public static int getHeight () { return HEIGHT; } public int getIndex () { return index; } public void setIndex (int index) { this .index = index; } public int getScore () { return score; } public void setScore (int score) { this .score = score; } public int getDistance () { return distance; } public void setDistance (int distance) { this .distance = distance; } }
3、几种障碍物的出现 障碍物一:螃蟹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package cn.sqc.runday.model;import java.awt.Graphics;import java.awt.Image;import java.awt.Paint;import java.io.File;import javax.imageio.ImageIO;import cn.sqc.runday.view.GameFrame;public class Barrs_1 { private Image image; private Image [] images; public static final int WIDTH=100 ; public static final int HEIGHT=110 ; private int x,y; int index; private int speed; public Barrs_1 () { images = new Image[2 ]; try { images[0 ]=ImageIO.read(new File("image/a2.png" )); images[1 ]=ImageIO.read(new File("image/a4.png" )); } catch (Exception e) { } image = images[0 ]; x=GameFrame.WIDTH+100 ; y=580 ; speed =30 ; index = 0 ; } public void step () { image =images[index++/5 %images.length]; x-=speed; } public void paintBarrs (Graphics g) { g.drawImage(image, x,y,WIDTH,HEIGHT, null ); } public boolean outofBounds () { return this .x <=-WIDTH; } public Image getImage () { return image; } public void setImage (Image image) { this .image = image; } public Image[] getImages() { return images; } public void setImages (Image[] images) { this .images = images; } public int getX () { return x; } public void setX (int x) { this .x = x; } public int getY () { return y; } public void setY (int y) { this .y = y; } public int getIndex () { return index; } public void setIndex (int index) { this .index = index; } public int getSpeed () { return speed; } public void setSpeed (int speed) { this .speed = speed; } public static int getWidth () { return WIDTH; } public static int getHeight () { return HEIGHT; } }
需要注意的是,在创建后,记得添加set、get方法。以便在面板类中对其障碍物进行操作。
障碍物二:宠物 与其称之障碍物,不如说它是个跟着玩家的小跟班。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package cn.sqc.runday.model;import java.awt.Graphics;import java.awt.Image;import java.awt.event.KeyListener;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import cn.sqc.runday.view.GameFrame;public class Barrs_2 { private Image image; private Image images [] ; public static final int WIDTH= 70 ; public static final int HEIGHT = 60 ; private int x,y; int index; public Barrs_2 () { init(); image = images[0 ]; x=300 ; y=460 ; } public void drop () { y ++; if (y>=460 ){ y = 460 ; } } public void step () { image = images[index++/2 %images.length]; } public void paintBarrs (Graphics g) { g.drawImage(image, x,y,WIDTH,HEIGHT, null ); } public boolean outofBounds () { return this .x<=-WIDTH; } public void init () { images = new Image[6 ]; for ( int i=0 ;i<6 ;i++){ try { images[i]=ImageIO.read(new File ("Image/" +"d" +(i+1 )+".png" )); } catch (IOException e) { e.printStackTrace(); } } } public Image getImage () { return image; } public void setImage (Image image) { this .image = image; } public Image[] getImages() { return images; } public void setImages (Image[] images) { this .images = images; } public int getX () { return x; } public void setX (int x) { this .x = x; } public int getY () { return y; } public void setY (int y) { this .y = y; } public int getIndex () { return index; } public void setIndex (int index) { this .index = index; } public static int getWidht () { return WIDTH; } public static int getHeight () { return HEIGHT; } }
障碍物三、导弹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package cn.sqc.runday.model;import java.awt.Graphics;import java.awt.Image;import java.io.File;import javax.imageio.ImageIO;import cn.sqc.runday.view.GameFrame;public class Barrs_3 { private Image image; private int x,y; public static final int WIDTH = 150 ; public static final int HEIGHT=70 ; private int speed; public Barrs_3 () { try { image = ImageIO.read(new File("image/daodan.png" )); } catch (Exception e) { } x=GameFrame.WIDTH+1000 ; y=450 ; speed = 25 ; } public void step () { x-=speed; } public void paintBarrs (Graphics g) { g.drawImage(image, x, y, WIDTH, HEIGHT, null ); } public boolean outofBounds () { return this .x<=-WIDTH; } public Image getImage () { return image; } public void setImage (Image image) { this .image = image; } public int getX () { return x; } public void setX (int x) { this .x = x; } public int getY () { return y; } public void setY (int y) { this .y = y; } public int getSpeed () { return speed; } public void setSpeed (int speed) { this .speed = speed; } public static int getWidth () { return WIDTH; } public static int getHeight () { return HEIGHT; } }
障碍物四:鱼叉等障碍物
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package cn.sqc.runday.model;import java.awt.Graphics;import java.awt.Image;import java.io.File;import java.util.Random;import javax.imageio.ImageIO;import cn.sqc.runday.view.GameFrame;public class Barrs_4 { private Image image; private Image images[]; public static final int WIDTH =150 ; public static final int HEIGHT =350 ; private int x,y; public Barrs_4 () { Random random = new Random(); images = new Image[4 ] ; try { images[0 ] = ImageIO.read(new File("image/11.png" )); images[1 ]= ImageIO.read(new File("image/12.png" )); images[2 ]= ImageIO.read(new File("image/13.png" )); images[3 ]= ImageIO.read(new File("image/14.png" )); } catch (Exception e) { } image= images[random.nextInt(4 )]; x=GameFrame.WIDTH+1500 ; y=0 ; } public void step () { x-=20 ; } public void paintBarrs (Graphics g) { g.drawImage(image, x, y, WIDTH, HEIGHT, null ); } public boolean outofBounds () { return this .x<=-WIDTH; } public Image getImage () { return image; } public void setImage (Image image) { this .image = image; } public Image[] getImages() { return images; } public void setImages (Image[] images) { this .images = images; } public int getX () { return x; } public void setX (int x) { this .x = x; } public int getY () { return y; } public void setY (int y) { this .y = y; } public static int getWidth () { return WIDTH; } public static int getHeight () { return HEIGHT; } }
障碍物五、金币
在此,暂且先不写金币的动态效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 package cn.sqc.runday.model;import java.awt.Graphics;import java.awt.Image;import java.io.File;import java.io.IOException;import java.util.Random;import javax.imageio.ImageIO;import cn.sqc.runday.view.GameFrame;public class Barrs_5 { private Image image; public static final int WIDTH = 30 ; public static final int HEIGHT = 30 ; private int x,y; private int speed; Random random = new Random(); public Barrs_5 () { try { image = ImageIO.read(new File("Image/" +(random.nextInt(6 ) + 21 ) + ".png" )); } catch (IOException e) { e.printStackTrace(); } x = GameFrame.WIDTH + 10 ; y = random.nextInt(600 ); speed = 20 ; } public void step () { x -= speed; } public void paintBarrs (Graphics g) { g.drawImage(image, x, y, WIDTH, HEIGHT, null ); } public boolean outofBounds () { return this .x<=-WIDTH; } public Image getImage () { return image; } public void setImage (Image image) { this .image = image; } public int getX () { return x; } public void setX (int x) { this .x = x; } public int getY () { return y; } public void setY (int y) { this .y = y; } public int getSpeed () { return speed; } public void setSpeed (int speed) { this .speed = speed; } public Random getRandom () { return random; } public void setRandom (Random random) { this .random = random; } public static int getWidth () { return WIDTH; } public static int getHeight () { return HEIGHT; } }
4、玩家和障碍物的碰撞逻辑 以玩家与导弹的碰撞举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (int i = 0 ;i<barrs3.length;i++){ if (person.getX() + Person.WIDTH >= barrs3[i].getX() && person.getX() <= barrs3[i].getX() + Barrs_3.WIDTH && person .getY() +Person.getHeight() >= barrs3[i].getY() && person.getY() <= barrs3[i].getY () + Barrs_3.HEIGHT){ if (person.getX() + Person.WIDTH <= barrs3[i].getX() + Barrs_3.WIDTH){ person.setX(barrs3[i].getX() - Barrs_3.WIDTH); }else { person.setX(barrs3[i].getX()+ Barrs_3.WIDTH ); } } }
以下动图演示了玩家从右边与障碍物b发生碰撞和从左边碰撞的逻辑,上下碰撞同理。
上下左右碰撞的逻辑代码,在动图下方:
5、暂停、继续逻辑 在监听键盘按键的方法中。
代码如下:
此处的 flag 来源于上面程序启动的方法中,不难看出只要按了空格键,就能实现生成、移动、绘制方法的暂停,也就相当于画面的静止、游戏的暂停!
6、结束逻辑
游戏主界面代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 package cn.sqc.runday.controller;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Image;import java.awt.event.KeyEvent;import java.awt.event.KeyListener;import java.io.File;import java.io.IOException;import java.util.Arrays;import javax.imageio.ImageIO;import javax.swing.JPanel;import cn.sqc.runday.model.Barrs_1;import cn.sqc.runday.model.Barrs_2;import cn.sqc.runday.model.Barrs_3;import cn.sqc.runday.model.Barrs_4;import cn.sqc.runday.model.Barrs_5;import cn.sqc.runday.model.Person;import cn.sqc.runday.view.EndFrame;import cn.sqc.runday.view.GameFrame;public class GamePanel extends JPanel implements KeyListener { Image background; Image score; Image pause; Image proceed; Person person; Barrs_2 barrs_2; Barrs_4 barrs_4; Barrs_5 barrs_5; Barrs_1[]barrs1 = {}; Barrs_3[]barrs3 ={}; Barrs_4[]barrs4={}; Barrs_5[]barrs5 = {}; public GamePanel () { person = new Person(); barrs_2 = new Barrs_2(); try { background =ImageIO.read(new File("Image/cc.png" )); score =ImageIO.read(new File("Image/a12.png" )); pause = ImageIO.read(new File("Image/b2.png" )); proceed = ImageIO.read(new File("Image/b1.png" )); }catch (IOException e){ e.printStackTrace(); } } int x=0 ; @Override public void paint (Graphics g) { super .paint(g); if (flag){ x-=20 ; } g.drawImage(background, x, 0 , GameFrame.WIDTH, GameFrame.HEIGHT, null ); g.drawImage(background, x+GameFrame.WIDTH, 0 , GameFrame.WIDTH, GameFrame.HEIGHT,null ); if (x<=-GameFrame.WIDTH){ x = 0 ; } person.paintPerson(g); for (int i =0 ;i<barrs1.length;i++){ barrs1[i].paintBarrs(g); } barrs_2.paintBarrs(g); for (int i =0 ;i<barrs3.length;i++){ barrs3[i].paintBarrs(g); } for (int i =0 ;i<barrs4.length;i++){ barrs4[i].paintBarrs(g); } for (int i = 0 ;i<barrs5.length;i++){ barrs5[i].paintBarrs(g); } g.drawImage(score, 120 , 50 ,null ); g.setColor(Color.ORANGE); g.setFont(new Font("宋体" ,Font.BOLD,30 )); g.drawString("玩家得分:" +person.getScore()+"分" , 133 , 95 ); if (flag){ g.drawImage(proceed, 200 , 800 , 90 ,90 ,null ); }else { g.drawImage(pause, 200 , 800 , 90 , 90 , null ); } } int index =0 ;public void enteredAction () { index++; if (index%100 ==0 ){ Barrs_1 b1 = new Barrs_1(); Barrs_3 b3 = new Barrs_3(); Barrs_4 b4 = new Barrs_4(); barrs1 =Arrays.copyOf(barrs1,barrs1.length+1 ); barrs1[barrs1.length-1 ]= b1; barrs3 =Arrays.copyOf(barrs3,barrs3.length+1 ); barrs3[barrs3.length-1 ]= b3; barrs4 =Arrays.copyOf(barrs4,barrs4.length+1 ); barrs4[barrs4.length-1 ]= b4; } if (index%15 ==0 ){ Barrs_5 b5 = new Barrs_5(); barrs5 = Arrays.copyOf(barrs5, barrs5.length +1 ); barrs5[barrs5.length-1 ] = b5; } } public void stepAction () { person.step(); person.drop(); barrs_2.drop(); for (int i =0 ;i<barrs1.length;i++){ barrs1[i].step(); if (barrs1[i].outofBounds()){ barrs1[i] = barrs1[barrs1.length - 1 ]; barrs1= Arrays.copyOf(barrs1, barrs1.length - 1 ); } } barrs_2.step(); for (int i =0 ;i<barrs3.length;i++){ barrs3[i].step(); if (barrs3[i].outofBounds()){ barrs3[i] = barrs3[barrs3.length - 1 ]; barrs3 = Arrays.copyOf(barrs3, barrs3.length - 1 ); } } for (int i =0 ;i<barrs4.length;i++){ barrs4[i].step(); if (barrs4[i].outofBounds()){ barrs4[i] = barrs4[barrs4.length - 1 ]; barrs4 = Arrays.copyOf(barrs4, barrs4.length - 1 ); } } for (int i = 0 ;i<barrs5.length;i++){ barrs5[i].step(); if (barrs5[i].outofBounds()){ barrs5[i] = barrs5[barrs5.length - 1 ]; barrs5 = Arrays.copyOf(barrs5, barrs5.length - 1 ); } } } public void pengAction () { for (int i = 0 ;i<barrs1.length;i++){ if (person.getX() + Person.WIDTH >= barrs1[i].getX() && person.getX() <= barrs1[i].getX() + Barrs_1.WIDTH && person .getY() +Person.getHeight() >= barrs1[i].getY() && person.getY() <= barrs1[i].getY () + Barrs_1.HEIGHT){ if (person.getX() + Person.WIDTH <= barrs1[i].getX() + Barrs_1.WIDTH){ person.setX(barrs1[i].getX() - Barrs_1.WIDTH); }else { person.setX(barrs1[i].getX()+ Barrs_1.WIDTH ); } } } for (int i = 0 ;i<barrs3.length;i++){ if (person.getX() + Person.WIDTH >= barrs3[i].getX() && person.getX() <= barrs3[i].getX() + Barrs_3.WIDTH && person .getY() +Person.getHeight() >= barrs3[i].getY() && person.getY() <= barrs3[i].getY () + Barrs_3.HEIGHT){ if (person.getX() + Person.WIDTH <= barrs3[i].getX() + Barrs_3.WIDTH){ person.setX(barrs3[i].getX() - Barrs_3.WIDTH); }else { person.setX(barrs3[i].getX()+ Barrs_3.WIDTH ); } } } for (int i = 0 ;i<=barrs4.length -1 ;i++){ if (person.getX() + Person.WIDTH >= barrs4[i].getX() && person.getX() <= barrs4[i].getX() + Barrs_4.WIDTH && person.getY() + Person.HEIGHT >= barrs4[i].getY() && person.getY() <= barrs4[i].getY() + Barrs_4.HEIGHT ){ if (person.getX() + Person.WIDTH <= barrs4[i].getX() + Barrs_4.WIDTH ){ person.setX(barrs4[i].getX() - Barrs_4.WIDTH); }else { person.setX(barrs4[i].getX()+ Barrs_4.WIDTH ); } } } for (int i = 0 ;i<barrs5.length;i++){ if (person.getX() + Person.WIDTH >= barrs5[i].getX() && person.getX() <= barrs5[i].getX() + Barrs_5.WIDTH && person .getY() +Person.getHeight() >= barrs5[i].getY() && person.getY() <= barrs5[i].getY () + Barrs_5.HEIGHT){ if (person.getX() + Person.WIDTH <= barrs5[i].getX() + Barrs_5.WIDTH){ barrs5[i] = barrs5[barrs5.length - 1 ]; barrs5 = Arrays.copyOf(barrs5, barrs5.length - 1 ); int score = person.getScore(); person.setScore(score + 10 ); } } } } public void gameOverAction () { if (person.outOfBounds()){ isGameOver = true ; new EndFrame(person); person = new Person(); barrs1 = new Barrs_1[]{}; barrs3 = new Barrs_3[]{}; } } public static boolean isGameOver = false ; boolean flag = true ; public void action () { new Thread(){ public void run () { while (!isGameOver){ if (flag){ enteredAction(); stepAction(); pengAction(); gameOverAction(); } repaint(); try { Thread.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } } }; }.start(); } @Override public void keyTyped (KeyEvent e) { } @Override public void keyPressed (KeyEvent e) { int x = person.getX(); int y = person.getY(); int x1 = barrs_2.getX(); int y1 = barrs_2.getY(); if (e.getKeyCode() == KeyEvent.VK_UP && y > 10 && y1 > 10 ){ person.setY(y-25 ); barrs_2.setY(y-25 ); } if (e.getKeyCode()== KeyEvent.VK_DOWN && y<=560 && y1<560 ){ person.setY(y+30 ); barrs_2.setY(y-30 ); } if (e.getKeyCode()==KeyEvent.VK_LEFT && x>=0 ){ person.setX(x-30 ); barrs_2.setX(x1-30 ); } if (e.getKeyCode()==KeyEvent.VK_RIGHT){ person.setX(x+22 ); barrs_2.setX(x1+22 ); if (x>=GameFrame.WIDTH-Person.WIDTH){ person.setX(GameFrame.WIDTH-Person.WIDTH); } if (x1>=GameFrame.WIDTH-barrs_2.WIDTH){ barrs_2.setX(GameFrame.WIDTH - barrs_2.WIDTH); } } if (e.getKeyCode() == KeyEvent.VK_SPACE){ flag = !flag; } } @Override public void keyReleased (KeyEvent e) { } }
五、结束界面 接上文,接下来将实现天天酷跑游戏的结束界面,功能如下:
跑酷距离、获取玩家的得分。再来一次、返回主菜单、直接退出。
具体啥样子,先睹为快!
点击再来一次按钮,进入加载状态,加载结束,直接进入游戏。
点击主菜单按钮,进入主菜单界面:
1、跑酷距离 我是在Person类的玩家移动方法中,添加了一个自增的diatance,只要玩家的图片还在切换,也就是游戏还没有结束,这个distance都在自增,也算是一种间接的实现计算跑酷距离的方法。
通过在Person类中添加get、set方法,获取数据。
2、获取玩家的得分 玩家与金币碰撞的得分即为图中的表现分,在GamePanel 获取。
而总分,我在Person类中,设定了一个简单的计分规则:
3、再来一次 在鼠标点击事件内,new一个新的加载界面,加载完成后自动进入游戏。
4、返回主界面 同理。
5、直接退出 同理。
上代码
EndFrame.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 package cn.sqc.runday.view;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Image;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import java.io.File;import java.io.IOException;import javax.imageio.ImageIO;import javax.swing.ImageIcon;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;import cn.sqc.runday.controller.GamePanel;import cn.sqc.runday.model.Person;public class EndFrame extends JFrame implements MouseListener { JLabel again,back,exit; public EndFrame (Person person) { again = new JLabel(new ImageIcon("Image/hh5.png" )); again.setBounds(520 , 622 , 60 , 25 ); again.addMouseListener(this ); this .add(again); back = new JLabel(new ImageIcon("Image/hh6.png" )); back.setBounds(520 , 722 , 60 , 25 ); back.addMouseListener(this ); this .add(back); exit = new JLabel(new ImageIcon("Image/hh3.png" )); exit.setBounds(520 , 822 , 60 , 25 ); exit.addMouseListener(this ); this .add(exit); EndPanel end = new EndPanel(person); this .add(end); this .setSize(1500 , 900 ); this .setLocationRelativeTo(null ); this .setUndecorated(true ); this .setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this .setIconImage(new ImageIcon("Image/115.png" ).getImage()); this .setVisible(true ); } public static void main (String[] args) { } class EndPanel extends JPanel { Image background; Person p; public EndPanel (Person person) { this .p = person; try { background = ImageIO.read(new File("Image/chou.png" )); } catch (IOException e) { e.printStackTrace(); } } @Override public void paint (Graphics g) { super .paint(g); g.drawImage(background, 0 , 0 ,1500 ,900 ,null ); g.setColor(Color.CYAN); g.setFont(new Font("宋体" ,Font.BOLD,30 )); g.drawString(p.getScore()+"" ,1110 ,705 ); g.drawString(p.getDistance() + " " , 1110 , 622 ); g.setFont(new Font("宋体" ,Font.BOLD,50 )); g.setColor(Color.ORANGE); g.drawString(p.getTotalScore() + "" , 1075 , 500 ); } } @Override public void mouseClicked (MouseEvent e) { if (e.getSource().equals(again)){ new WindowFrame().Start(); dispose(); } else if (e.getSource().equals(back)){ new MainFrame(); dispose(); }else if (e.getSource().equals(exit)){ System.exit(0 ); } } @Override public void mousePressed (MouseEvent e) { } @Override public void mouseReleased (MouseEvent e) { } @Override public void mouseEntered (MouseEvent e) { } @Override public void mouseExited (MouseEvent e) { } }
来源:https://mp.weixin.qq.com/s/-xtGga1PU6olPVHh5Z_FEA