作者简介
作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java、JavaScript,博主也是从零开始一步步把学习成长、深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢迎您关注,期待与您一起学习、成长、起飞!
系列目录
1. Java俄罗斯方块
2. Java五子棋小游戏
3. 老Java程序员花一天时间写了个飞机大战
4. Java植物大战僵尸
5. 老Java程序员花2天写了个连连看
6. Java消消乐(天天爱消除)
7. Java贪吃蛇小游戏
8. Java扫雷小游戏
9. Java坦克大战
10. Java迷宫小游戏
引言:
前几天回乡下老家,看到小鱼在小河里游来游去,手痒就一度想下河去捞,但我女儿提醒我不要吃野生动物,这一下子让我回到了现实,原来我已经30多岁了,还真是老程序员,那既然老程序员不能去下河捞鱼,我自己做个大鱼吃小鱼的游戏不过分吧!于是它来了,它来了,它扛着电脑和 jdk 就来了。
效果图
实现思路
因为做的是简单版本的,所以思路也很简单。
- 绘制窗口。
- 创建菜单
- 创建我方鱼
- 创建敌鱼
- 键盘事件监听
- 创建主线程,重新绘制界面,并且定时创建敌鱼。
- 处理其他细
代码实现
创建窗口
首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。
package com.view;
import java.awt.BorderLayout;
import javax.swing.JFrame;
public class FishFrame extends JFrame {
public int window_Width = 1180, window_Height = 640;
public FishFrame() {
setTitle("大鱼吃小鱼");//窗口标题
setSize(window_Width, window_Height);//窗口分辨率
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
setLocationRelativeTo(null); //设置居中
setResizable(false); //不允许修改界面大小
setVisible(true);//窗口是否可以显示 true---false
}
}
创建面板容器GamePanel继承至JPanel
package com.view;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GamePanel extends JPanel{
GamePanel gamePanel=this;
private JFrame mainFrame=null;
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
mainFrame = frame;
}
}
再创建一个Main类,来启动这个窗口,用来启动。
package com.main;
import com.view.FishFrame;
import com.view.GamePanel;
public class Main {
public static void main(String[] args) {
FishFrame frame = new FishFrame();
GamePanel panel = new GamePanel(frame);
frame.add(panel);
frame.setVisible(true);//设定显示
}
}
右键执行这个Main类,窗口建出来了
绘制背景图
创建Picture 类,负责加载所有的图片,方便调用
package com.utils;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Picture {
public static BufferedImage bg0,FishL,FishR;
public static List badFishImages =new ArrayList() ;
public static List badFishImages1 =new ArrayList() ;
//静态代码块---程序启动,在加载类的时候,就会执行
static {
try {
bg0 = ImageIO.read(Picture.class.getClassLoader().getResourceAsStream("img/bg0.jpg"));
FishL = ImageIO.read(Picture.class.getClassLoader().getResourceAsStream("img/fishIcon0.png"));
FishR = ImageIO.read(Picture.class.getClassLoader().getResourceAsStream("img/fishIcon0R.png"));
BufferedImage temp;
for (int i = 1; i <= 18; i++) {
temp = ImageIO.read(Picture.class.getClassLoader().getResourceAsStream("img/fishIcon"+i+".png"));
badFishImages.add(temp);
temp = ImageIO.read(Picture.class.getClassLoader().getResourceAsStream("img/fishIconL"+i+".png"));
badFishImages1.add(temp);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在GamePanel中重写paint方法,并绘制背景图。
@Override
public void paint(Graphics g) {
super.paint(g);
//绘制背景图
g.drawImage(Picture.bg0, 0,0,null);
}
运行如下:
创建菜单
//初始化按钮
private void initMenu(){
// 创建菜单及菜单选项
JMenuBar jmb = new JMenuBar();
JMenu jm1 = new JMenu("游戏");
jm1.setFont(new Font("思源宋体", Font.BOLD, 15));// 设置菜单显示的字体
JMenuItem jmi1 = new JMenuItem("开始新游戏");
JMenuItem jmi2 = new JMenuItem("退出");
jmi1.setFont(new Font("思源宋体", Font.BOLD, 15));
jmi2.setFont(new Font("思源宋体", Font.BOLD, 15));
jm1.add(jmi1);
jm1.add(jmi2);
jmb.add(jm1);
mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
jmi1.addActionListener(this);
jmi1.setActionCommand("Restart");
jmi2.addActionListener(this);
jmi2.setActionCommand("Exit");
}
此时直接把这个代码加入到GamePanel中,发现是会报错的,需要实现ActionListener,并重写actionPerformed 方法。
此时GamePanel的代码如下:
package com.view;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
import com.utils.Picture;
public class GamePanel extends JPanel implements ActionListener{
GamePanel gamePanel=this;
private JFrame mainFrame=null;
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
mainFrame = frame;
//菜单
initMenu();
}
@Override
public void paint(Graphics g) {
super.paint(g);
//绘制背景图
g.drawImage(Picture.bg0, 0,0,null);
}
//初始化按钮
private void initMenu(){
// 创建菜单及菜单选项
JMenuBar jmb = new JMenuBar();
JMenu jm1 = new JMenu("游戏");
jm1.setFont(new Font("思源宋体", Font.BOLD, 15));// 设置菜单显示的字体
JMenuItem jmi1 = new JMenuItem("开始新游戏");
JMenuItem jmi2 = new JMenuItem("退出");
jmi1.setFont(new Font("思源宋体", Font.BOLD, 15));
jmi2.setFont(new Font("思源宋体", Font.BOLD, 15));
jm1.add(jmi1);
jm1.add(jmi2);
jmb.add(jm1);
mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
jmi1.addActionListener(this);
jmi1.setActionCommand("Restart");
jmi2.addActionListener(this);
jmi2.setActionCommand("Exit");
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
if ("Exit".equals(command)) {
Object[] options = { "确定", "取消" };
int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
if (response == 0) {
System.exit(0);
}
}else if("Restart".equals(command)){
restart();
}
}
//重新开始
private void restart() {
}
}
创建我方鱼
package com.model;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import com.utils.Dir;
import com.utils.Picture;
import com.view.GamePanel;
public class Fish extends ArrayList<Fish> {
private int x, y,/*主角鱼出生坐标*/
width = 50, height = 30,
speed = 10;
private Dir dir;
private boolean isMoving = false, isLiving = true;
private GamePanel panel;//相互持有
private String group;
private Graphics g;
private BufferedImage image=Picture.FishL;
private int type = 1;//1 右, -1 左
public Fish(int x, int y, Dir dir, GamePanel panel, String group) {
this.x = x;
this.y = y;
this.dir = dir;
this.panel = panel;
this.group = group;
}
public void paint(Graphics g) {
this.g = g;
switch (dir) {
case UP:
image = type==1?Picture.FishR:Picture.FishL;
break;
case DOWN:
image = type==1?Picture.FishR:Picture.FishL;
break;
case LEFT:
type = -1;
image = type==1?Picture.FishR:Picture.FishL;
break;
case RIGHT:
type = 1;
image = type==1?Picture.FishR:Picture.FishL;
break;
}
g.drawImage(image, x, y,width,height, null);
}
public void setDir(Dir dir) {
this.dir = dir;
}
public void setMoving(boolean moving) {
isMoving = moving;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public boolean isLiving() {
return isLiving;
}
public void setLiving(boolean living) {
isLiving = living;
}
public int size() {
return 0;
}
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 getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
实例化这个鱼
private void initMyFish() {
myFish = new Fish(0, 320, Dir.RIGHT, this, "GOOD");
}
在构造方法中调用
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
mainFrame = frame;
//菜单
initMenu();
//创建我的鱼
initMyFish();
}
在paint方法中绘制
@Override
public void paint(Graphics g) {
super.paint(g);
//绘制背景图
g.drawImage(Picture.bg0, 0,0,null);
//绘制鱼
if(myFish!=null) myFish.paint(g);
}
运行如下(左方出现我方小鱼):
绘制敌方鱼
创建坏鱼类
package com.model;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.utils.Picture;
import com.view.GamePanel;
public class BadFish {
private int x, y, width = 50, height = 50, speed = 1;
private boolean isMoving = false, isLiving = true;
private GamePanel panel;// 相互持有
private String group;
private BufferedImage image;
private int large = 0;// 0表示小的,1表示大一号
private int type = 0;// 1-18 表示不同的鱼
private List imgList = null;
public BadFish(GamePanel panel, String group) {
this.panel = panel;
this.group = group;
init();
}
private void init() {
this.x=1180;
Random random = new Random();
this.type= random.nextInt(18)+1;
this.large = random.nextInt(2)==0? 0:1;
if(large==1){
imgList = Picture.badFishImages1;
}else {
imgList = Picture.badFishImages;
}
image = (BufferedImage)imgList.get(type-1);
width = image.getWidth();
height = image.getHeight();
y = random.nextInt(590-height);
}
public void paint(Graphics g) {
if (!isLiving) {
ArrayList<BadFish> badFishs = panel.badFishss;
badFishs.remove(this);
return;
}
g.drawImage(image, x, y, width, height, null);
}
public void remove() {
this.isMoving=false;
this.isLiving=false;
panel.badFishss.remove(this);
}
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 getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public boolean isLiving() {
return isLiving;
}
public void setLiving(boolean isLiving) {
this.isLiving = isLiving;
}
}
实例化,并调用
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
mainFrame = frame;
//菜单
initMenu();
//创建我的鱼
initMyFish();
//创建敌鱼
initBadFishs();
}
//创建坏鱼
private void createBadFish(){
BadFish badFish = new BadFish(this,"BAD");
badFishss.add(badFish) ;
}
//初始创建6只
private void initBadFishs() {
for (int i = 0; i < 6; i++) {
createBadFish();
}
}
在paint方法中绘制
@Override
public void paint(Graphics g) {
super.paint(g);
//绘制背景图
g.drawImage(Picture.bg0, 0,0,null);
//绘制鱼
if(myFish!=null) myFish.paint(g);
//绘制敌方鱼
for (int i = 0; i < badFishss.size(); i++) {
BadFish fish = badFishss.get(i);
if(fish!=null)
fish.paint(g);
}
}
在坏鱼类中加入移动方法move
private void move() {
new Thread(new Runnable() {
@Override
public void run() {
while (isLiving) {
x -= speed;
if (x + width < 0) {
x = 1180;
y = new Random().nextInt(590 - height);
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!isLiving){
break;
}
}
}
}).start();
}
加入主线程新绘制
//游戏线程,用来自动下移
private class GameThread implements Runnable {
@Override
public void run() {
while (true) {
if("start".equals(gameFlag)){
repaint();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
记得构造中调用哦
//启动主线程
new Thread(new GameThread()).start();
运行如下:
加入更多的鱼
在paint加入代码,定时的创建鱼,并且控制鱼的数量不超过60
if("start".equals(gameFlag)){
if(time==30){
time=0;
if(badFishss.size()<60){
createBadFish();
}
}else {
time++;
}
}
运行如下图:
加入键盘移动事件
记得在构造中调用这个方法哦。
//添加键盘监听
private void createKeyListener() {
KeyAdapter l = new KeyAdapter() {
//按下
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch (key) {
//向上
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
if(myFish!=null){
myFish.move(Dir.UP);
}
break;
//向右
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
if(myFish!=null){
myFish.move(Dir.RIGHT);
}
break;
//向下
case KeyEvent.VK_DOWN:
case KeyEvent.VK_S:
if(myFish!=null){
myFish.move(Dir.DOWN);
}
break;
//向左
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
if(myFish!=null){
myFish.move(Dir.LEFT);
}
break;
}
}
//松开
@Override
public void keyReleased(KeyEvent e) {
}
};
//给主frame添加键盘监听
mainFrame.addKeyListener(l);
}
在我方鱼 Fish类 加入move方法
public void move(Dir dir) {
this.setDir(dir);
this.setMoving(true);
if (!isMoving) {
return;
}
switch (dir) {
case UP:
y -= speed;
if(y<=0){
y=0;
}
break;
case DOWN:
y += speed;
if(y>=590-height){
y=590-height;
}
break;
case LEFT:
x -= speed;
if(x<=0){
x=0;
}
break;
case RIGHT:
x += speed;
if(x>=1180-width){
x=1180-width;
}
}
}
此时我方小鱼用键盘可以控制移动。
处理鱼碰撞的问题
判断我鱼与敌鱼的碰撞,因为图片是方形的,我们拿出其中一个鱼的4个顶点坐标,分别去与另外一个鱼做判断,只要有一个点在范围内就表示已经碰撞了。 注意:碰撞的问题,在 飞机大战 讲的很清楚,也画了图来说明,可以去翻一下,原理是一样的。
public boolean isHit(Fish fish,BadFish badFish) {
if(isHit1(fish, badFish)){//如果在碰撞范围内
if(fish.getX()<badFish.getX()){//鱼在敌鱼的左边
if(fish.getY()>badFish.getY()){//鱼在敌鱼的下方
if((fish.getY()+10<badFish.getY()+badFish.getHeight())
&& (fish.getX() + fish.getWidth()>badFish.getX()+10)){
return true;
}
}else if(fish.getY()<badFish.getY()){//鱼在敌鱼的上方
if(badFish.getY()+10<fish.getY()+fish.getHeight()
&&fish.getX() + fish.getWidth()>badFish.getX()+10){
return true;
}
}
}else if(fish.getX()>badFish.getX()){//鱼在敌鱼的左边
if(fish.getY()>badFish.getY()){//鱼在敌鱼的下方
if(fish.getY()+10<badFish.getY()+badFish.getHeight()
&& badFish.getX() + badFish.getWidth()>fish.getX()+10){
return true;
}
}else if(fish.getY()<badFish.getY()){//鱼在敌鱼的上方
if(badFish.getY()+10<fish.getY()+fish.getHeight()
&& badFish.getX() + badFish.getWidth()>fish.getX()+10){
return true;
}
}
}
}
return false;
}
//判断鱼是否碰撞
public boolean isHit1(Fish fish,BadFish badFish) {
//方式1
//左上角
int x1 = badFish.getX();
int y1 = badFish.getY();
//右上角
int x2 = badFish.getX()+badFish.getWidth();
int y2 = badFish.getY();
//右下角
int x3 = badFish.getX()+badFish.getWidth();
int y3 = badFish.getY()+badFish.getHeight();
//左下角
int x4 = badFish.getX();
int y4 = badFish.getY()+badFish.getHeight();
//只要有一个点在范围内,则判断为碰撞
if(comparePointFish(x1,y1,fish)|| comparePointFish(x2,y2,fish)
||comparePointFish(x3,y3,fish)||comparePointFish(x4,y4,fish) ){
return true;
}
//方式1没成立则用方式2判断
//方式2
x1 = fish.getX();
y1 = fish.getY();
//右上角
x2 = fish.getX()+fish.getWidth();
y2 = fish.getY();
//右下角
x3 = fish.getX()+fish.getWidth();
y3 = fish.getY()+fish.getHeight();
//左下角
x4 = fish.getX();
y4 = fish.getY()+fish.getHeight();
if(comparePoint(x1,y1,badFish)|| comparePoint(x2,y2,badFish)||comparePoint(x3,y3,badFish)||comparePoint(x4,y4,badFish) ){
return true;
}
return false;
}
//用坐标来判断
private boolean comparePointFish(int x,int y,Fish fish){
//大于左上角,小于右下角的坐标则肯定在范围内
if(x>fish.getX() && y >fish.getY()
&& x<fish.getX()+fish.getWidth() && y <fish.getY()+fish.getHeight() ){
return true;
}
return false;
}
//用坐标来判断
private boolean comparePoint(int x,int y,BadFish badFish){
//大于左上角,小于右下角的坐标则肯定在范围内
if(x>badFish.getX() && y >badFish.getY()
&& x<badFish.getX()+badFish.getWidth() && y <badFish.getY()+badFish.getHeight()){
return true;
}
return false;
}
另外,上述代码中有看到 +10 像素这样的代码,是为了让碰撞看起来更真实,做的处理,因为鱼对于方形的图片来说,边角还是有很多空缺的。
吃敌鱼处理
在Fish类中加入 eat方法,吃了后会变大。
private void eat() {
ArrayList<BadFish> badFishList = panel.badFishss;
BadFish badFish;
for (int i = 0; i < badFishList.size(); i++) {
badFish = badFishList.get(i);
if(!badFish.isLiving()) continue ;
if(panel.isHit(this, badFish)){//鱼碰撞
if(this.height>=badFish.getHeight()){//吃掉敌鱼
System.out.println("吃掉敌鱼");
System.out.println("this.width==="+this.width);
//鱼变大一点
if(this.width<150){
this.height+=2;
this.width +=2*1.66;
}
//敌鱼消失
badFish.remove();
}else {//被吃掉弹出失败
System.out.println("被敌鱼吃掉");
panel.myFish.isMoving=false;
panel.myFish.isLiving=false;
panel.myFish=null;
panel.gameOver();
}
break;
}
}
}
在 BadFish类中加入碰撞方法,如果敌鱼较大,则游戏失败,反过来我鱼要变大。
private void hit(){
Fish fish = panel.myFish;
if(fish==null) return;
if(!isLiving) return ;
if(panel.isHit(fish, this)){//鱼碰撞
if(fish.getHeight()>=this.getHeight()){//吃掉敌鱼
System.out.println("吃掉敌鱼....");
//鱼变大一点
if(fish.getWidth()<150){
fish.setHeight(fish.getHeight()+2);
fish.setWidth(fish.getWidth()+3);
}
//敌鱼消失
this.remove();
}else {//被吃掉弹出失败
System.out.println("被敌鱼吃掉....");
fish.setMoving(false);
fish.setLiving(false);
panel.myFish=null;
panel.gameOver();
}
}
}
运行效果:
做到这里就基本完成了,加入其他一下辅助的东西就行了,比如重新开始什么的,也就不多说了。
看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。
代码获取方式:
帮忙文章【点赞】 +【 收藏】+【关注】+【评论】 后,加V:qq283582761,我发给你!
更多精彩
1. Java俄罗斯方块
2. Java五子棋小游戏
3. 老Java程序员花一天时间写了个飞机大战
4. Java植物大战僵尸
5. 老Java程序员花2天写了个连连看
6. Java消消乐(天天爱消除)
7. Java贪吃蛇小游戏
8. Java扫雷小游戏
9. Java坦克大战
10. Java迷宫小游戏