精华内容
下载资源
问答
  • java多线程小游戏
    千次阅读
    2021-12-10 16:22:12

    1.创建弹窗,并设置窗口属性。

    2.创建Pool类,继承JPanel类,并重写paint方法。

    3.创建Net类,

    4.创建Fish类,继承Thread类,重写run方法。

    5.刚开始先画一条鱼,一个动作,然后利用数组,定义多条鱼,在水里游起来,最后定义catchFish方法,捕鱼。

    import javax.swing.*;
    
    public class FishGame {
        public static void main(String[] args) {
            //创建窗口
            JFrame jFrame = new JFrame();
    
            //创建一个Pool对象,并把该对象放到窗口上
            Pool pool = new Pool();
            jFrame.add(pool);
            //设置大小(窗口大小由池塘图片大小决定)
            jFrame.setSize(800,480);
            //设置居中
            jFrame.setLocationRelativeTo(null);
            //设置大小不可变
            jFrame.setResizable(false);
            //设置关闭窗口退出程序
            jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            //展示窗口
            jFrame.setVisible(true);
        }
    }
    import javax.imageio.ImageIO;
    import javax.swing.*;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    //继承JPanel类
    public class Pool extends JPanel {
        //声明池塘图片
        BufferedImage poolImage;
        //声明池塘的宽和高,XY坐标
        int poolWidth = 0;
        int poolHeight = 0;
        int poolX = 0;
        int poolY = 0;
    
        //定义一个构造方法
        Pool(){
            try {
                //接收读取到的池塘图片,宽和高
                poolImage = ImageIO.read(new File("src/images/bg.jpg"));
                poolWidth = poolImage.getWidth();
                poolHeight = poolImage.getHeight();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //重写父类paint方法
        @Override
        public void paint(Graphics g) {
            g.drawImage(poolImage,0,0,null);
        }
    }

    运行结果如下




    import javax.swing.*;
    
    public class FishGame {
        public static void main(String[] args) {
            //创建窗口
            JFrame jFrame = new JFrame();
    
            //创建一个Pool对象,并把该对象放到窗口上
            Pool pool = new Pool();
            jFrame.add(pool);
            //设置大小(窗口大小由池塘图片大小决定)
            jFrame.setSize(800,480);
            //设置居中
            jFrame.setLocationRelativeTo(null);
            //设置大小不可变
            jFrame.setResizable(false);
            //设置关闭窗口退出程序
            jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            //展示窗口
            jFrame.setVisible(true);
            //启动鼠标监听事件
            pool.action();
        }
    }
    import javax.imageio.ImageIO;
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    //继承JPanel类
    public class Pool extends JPanel {
        //声明池塘图片
        BufferedImage poolImage;
        //声明池塘的宽和高,XY坐标
        int poolWidth = 0;
        int poolHeight = 0;
        int poolX = 0;
        int poolY = 0;
    
        Net net = null;
        //定义一个构造方法
        Pool(){
            try {
                net = new Net();
                //接收读取到的池塘图片,宽和高
                poolImage = ImageIO.read(new File("src/images/bg.jpg"));
                poolWidth = poolImage.getWidth();
                poolHeight = poolImage.getHeight();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //重写父类paint方法
        @Override
        public void paint(Graphics g) {
            g.drawImage(poolImage,0,0,null);
            g.drawImage(net.netImage,net.netX - net.netWidth/2,net.netY - net.netHeight/2,null);
        }
        //定义鼠标监听事件
        public void action(){
            MouseAdapter mouseAdapter = new MouseAdapter() {
                //点击
                @Override
                public void mousePressed(MouseEvent e) {
                    super.mousePressed(e);
                }
                //进入
                @Override
                public void mouseEntered(MouseEvent e) {
                    super.mouseEntered(e);
                }
                //退出
                @Override
                public void mouseExited(MouseEvent e) {
                    super.mouseExited(e);
                }
                //移动
                @Override
                public void mouseMoved(MouseEvent e) {
                    //将鼠标的坐标赋值给渔网
                    net.netX = e.getX();
                    net.netY = e.getY();
                }
            };
            //鼠标点击事件生效
            this.addMouseMotionListener(mouseAdapter);
            //重复画网,实现网跟着鼠标移动
            while (true){
                repaint();
            }
        }
    }
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    public class Net {
        //声明渔网图片,和渔网的长宽和XY坐标
        BufferedImage netImage;
        int netWidth = 0;
        int netHeight = 0;
        int netX = 0;
        int netY = 0;
    
        //创建无参构造方法
        Net(){
            try {
                //接收渔网图片,渔网的宽和高
                netImage = ImageIO.read(new File("src/images/net09.png"));
                netWidth = netImage.getWidth();
                netHeight = netImage.getHeight();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    运行效果,渔网可以跟着鼠标移动



    import javax.swing.*;
    
    public class FishGame {
        public static void main(String[] args) {
            //创建窗口
            JFrame jFrame = new JFrame();
    
            //创建一个Pool对象,并把该对象放到窗口上
            Pool pool = new Pool();
            jFrame.add(pool);
            //设置大小(窗口大小由池塘图片大小决定)
            jFrame.setSize(800,480);
            //设置居中
            jFrame.setLocationRelativeTo(null);
            //设置大小不可变
            jFrame.setResizable(false);
            //设置关闭窗口退出程序
            jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            //展示窗口
            jFrame.setVisible(true);
            //启动鼠标监听事件
            pool.action();
        }
    }
    import javax.imageio.ImageIO;
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    //继承JPanel类
    public class Pool extends JPanel {
        //声明池塘图片
        BufferedImage poolImage;
        //声明池塘的宽和高,XY坐标
        int poolWidth = 0;
        int poolHeight = 0;
        int poolX = 0;
        int poolY = 0;
    
        Fish fish;
        Net net = null;
        Fish[] fishes = new Fish[9];
        //定义一个构造方法
        Pool(){
            try {
                net = new Net();
                for (int i = 0; i < fishes.length; i++) {
                    fishes[i] = new Fish(i);
                    fishes[i].start();
                }
                //接收读取到的池塘图片,宽和高
                poolImage = ImageIO.read(new File("src/images/bg.jpg"));
                poolWidth = poolImage.getWidth();
                poolHeight = poolImage.getHeight();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //重写父类paint方法
        @Override
        public void paint(Graphics g) {
            g.drawImage(poolImage,0,0,null);
            for (int i = 0; i < fishes.length; i++) {
                g.drawImage(fishes[i].fishImage,fishes[i].fishX,fishes[i].fishY,null);
            }
            g.drawImage(net.netImage,net.netX - net.netWidth/2,net.netY - net.netHeight/2,null);
        }
        //定义鼠标监听事件
        public void action(){
            MouseAdapter mouseAdapter = new MouseAdapter() {
                //点击
                @Override
                public void mousePressed(MouseEvent e) {
                    for (int i = 0; i < fishes.length; i++) {
                        net.catchFish(fishes[i]);
                    }
                }
                //进入
                @Override
                public void mouseEntered(MouseEvent e) {
                    super.mouseEntered(e);
                }
                //退出
                @Override
                public void mouseExited(MouseEvent e) {
                    super.mouseExited(e);
                }
                //移动
                @Override
                public void mouseMoved(MouseEvent e) {
                    //将鼠标的坐标赋值给渔网
                    net.netX = e.getX();
                    net.netY = e.getY();
                }
            };
            //鼠标点击事件生效
            this.addMouseMotionListener(mouseAdapter);
            this.addMouseListener(mouseAdapter);
            //重复画网,实现网跟着鼠标移动
            while (true){
                repaint();
            }
        }
    }
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    public class Net {
        //声明渔网图片,和渔网的长宽和XY坐标
        BufferedImage netImage;
        int netWidth = 0;
        int netHeight = 0;
        int netX = 0;
        int netY = 0;
    
        //创建无参构造方法
        Net(){
            try {
                //接收渔网图片,渔网的宽和高
                netImage = ImageIO.read(new File("src/images/net09.png"));
                netWidth = netImage.getWidth();
                netHeight = netImage.getHeight();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public void catchFish(Fish fish){
            if (netX > fish.fishX && netX < fish.fishX + fish.fishWidth &&
                netY > fish.fishY && netY < fish.fishY + fish.fishHeight){
                fish.fishX = 800;
                fish.fishY = (int) (Math.random()*480);
            }
        }
    }
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    public class Fish extends Thread{
        BufferedImage fishImage;
        int fishWidth = 0;
        int fishHeight = 0;
        int fishX = 0;
        int fishY = 0;
    
        int speed = (int) (Math.random()*10);
        BufferedImage[] fishImages = new BufferedImage[10];
        Fish(int index){
            try {
                for (int i = 0; i < fishImages.length; i++) {
                    fishImages[i] = ImageIO.read(new File("src/images/fish0" + (index + 1) +"_0" + i +".png"));
                    fishWidth = fishImages[i].getWidth();
                    fishHeight = fishImages[i].getHeight();
    
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        int a = 0;
        @Override
        public void run() {
            fishX = 800;
            fishY = (int) (Math.random()*480);
            while (true){
    
                fishX = fishX - speed;
                a++;
                fishImage = fishImages[a%10];
                //鱼游到头重新游
                if (fishX < 0){
                    fishX = 800;
                    fishY = (int) (Math.random()*480);
                }
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    


    运行结果如下:鼠标点击鱼即可捕捉,被捕捉的鱼返回起点 重新游

    图片及源码在资源里

    更多相关内容
  • java多线程小游戏

    热门讨论 2012-11-30 17:09:48
    java多线程小游戏
  • java
  • 主要为大家详细介绍了Java多线程实现方块赛跑小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 实验总结:五子棋是一个很简单的游戏,但是如果认真对待,一个代码一个代码的去研究,会收获到很知识,会打好学习基础。方便以后开发更高、更难的项目时打下稳固的基础。在自己开发的过程中会有各种意想不到的bug...

    总结一下JAVA本学期的大作业。

    实验总结:五子棋是一个很简单的游戏,但是如果认真对待,一个代码一个代码的去研究,会收获到很多知识,会打好学习基础。方便以后开发更高、更难的项目时打下稳固的基础。在自己开发的过程中会有各种意想不到的bug,通过查阅资料及询问老师同学进行解决对本身的一个代码能力会有一个质的增长,同时这也是一个非常快乐的过程。有进步,总归是好事。

    1. 用户登录:使用本地文件输入/输出模拟数据库完成用户注册、登录的信息本地化存储及check。
      在这里插入图片描述
    2. 对战联网:socket联网

    在这里插入图片描述
    在这里插入图片描述
    3.线程重构:run()重构代码完成任务需求。
    在这里插入图片描述
    4.聊天功能:通过联网、获取I/O流实现实时聊天。
    在这里插入图片描述

    展开全文
  • 主要为大家详细介绍了java利用多线程和Socket实现猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的伙伴们可以参考一下
  • 通过JAVA运用多线程控制球的运动,通过窗口中的滑条,对球的大小和颜色进行选择后,随机从窗口左右两方择一进入,小球在遇到障碍或边界后会折回。
  • Java多线程游戏仿真实例分享

    万次阅读 多人点赞 2021-02-02 18:20:57
    这是一篇学习分享博客,这篇博客将会介绍以下几项内容: 1、如何让一个程序同时做多件事?(多线程的创建、多线程的应用、多线程的特点以及...3、多线程游戏仿真实例分享(飞机大战、接豆人、双线挑战三个游戏实例)

    这是一篇学习分享博客,这篇博客将会介绍以下几项内容:

    1、如何让一个程序同时做多件事?(多线程的创建、多线程的应用)
    2、如何让小球在画面中真实地动起来?(赋予小球匀速直线、自由落体、上抛等向量运动)
    3、多线程游戏仿真实例分享(飞机大战、接豆人、双线挑战三个游戏实例)

    • 涉及的知识点有:多线程的应用、双缓冲绘图、小球的向量运动、游戏的逻辑判断、键盘监听器的使用、二维数组的使用、添加音乐效果等

    游戏效果:
    在这里插入图片描述
    在这里插入图片描述
    怎么样?如果觉得还不错的话就请继续看下去吧!

    热身

    第一步:创建画布

    • 心急吃不了热豆腐,我们先从最简单的创建画布开始。
      首先我们创建一个窗体,然后设置一些参数,从窗体中取得画笔,尝试在画布中心画一个图形,以下是参考代码:
    import java.awt.FlowLayout;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    
    public class Frame {
    	//声明画布对象
    	public Graphics g;
    	
    	//主函数
    	public static void main(String[] args) {
    		//创建Frame类,然后运行showFrame函数
    		Frame fr=new Frame();
    		fr.showFrame();
    	}
    	
    	//编写窗体显示的函数
    	public void showFrame(){
    		//创建窗体
    		JFrame jf=new JFrame();
    		jf.setTitle("小球演示");//设置窗体标题
    		jf.setSize(900,900);//设置窗体大小
    		jf.setDefaultCloseOperation(3);//设置点击窗体右上角的叉叉后做什么操作,这里的3代表点击叉叉后关闭程序
    		jf.setLocationRelativeTo(null);//设置窗体居中显示
    		FlowLayout flow=new FlowLayout();//设置窗体布局为流式布局
    		jf.setLayout(flow);
    		
    		Mouse mou=new Mouse();//创建监听器对象
    		
    		JButton jbu=new JButton("START");//创建按钮,按下按钮后可以在画布中间画一个圆
    		jbu.addActionListener(mou);//为按钮添加事件监听器
    		jf.add(jbu);
    		
    		//设置窗体可见
    		jf.setVisible(true);
    		
    		//从窗体获取画布
    		g=jf.getGraphics();
    	}
    	
    	//创建内部类监听器(也可以重新创建一个文件编写该类)
    	class Mouse implements ActionListener{
    		//重写按钮监听方法
    		public void actionPerformed(ActionEvent e){
    			//按下按钮后会执行这里的代码,下面这条代码指的是在画布中心画一个圆
    			g.fillOval(300,300,300,300);
    		}
    	}
    }
    
    • 我们可以试着运行一下,出现以下图片所示效果第一步就成功了。

    第二步:让小球动起来

    • 用一段循环代码重复地画小球,每次循环让小球偏移一点距离
      我们在上述代码中的监听器类Mouse的按钮监听器方法actionPerformed(ActionEvent e)下加这样一段代码
    //重复画100次小球,每次横纵坐标分别加1
    for(int i=0;i<100;i++){
    	g.fillOval(300+i,300+i,30,30);
    	/*下面这段代码的意思是每执行一次循环,系统暂停30毫秒,否则画的
    	太快我们就观察不到小球在动了*/
    	try{
    		Thread.sleep(30);
    	}catch(Exception ef){
    	}
    }
    
    • 运行程序并点击START按键后,我们可以看到一个圆往右下角方向缓缓移动了一段距离,并且留下了痕迹。同时我们还可以发现,每次点击START键后,START键会保持被按下的状态,直至整个绘制小球的循环代码执行结束后才会弹起。这是因为我们现在写的程序只有一个线程在运行,所以只有当前任务执行完后按钮才能重新接收响应。想要解决这一点,可以利用下面将要讲到的多线程的原理。
      在这里插入图片描述

    那么,热身结束,下面让我们一起进入多线程的世界吧!

    一、如何让一个程序同时做多件事情?

    创建线程对象

    • 创建线程对象我们需要用到Thread类,该类是java.lang包下的一个类,所以调用时不需要导入包。下面我们先创建一个新的子类来继承Thread类,然后通过重写run()方法(将需要同时进行的任务写进run()方法内),来达到让程序同时做多件事情的目的。
    import java.awt.Graphics;
    import java.util.Random;
    
    public class ThreadClass extends Thread{
    	public Graphics g;
    	//用构造器传参的办法将画布传入ThreadClass类中
    	public ThreadClass(Graphics g){
    		this.g=g;
    	}
    	public void run(){
    		//获取随机的x,y坐标作为小球的坐标
    		Random ran=new Random();
    		int x=ran.nextInt(900);
    		int y=ran.nextInt(900);
    		for(int i=0;i<100;i++){
    			g.fillOval(x+i,y+i,30,30);
    			try{
    				Thread.sleep(30);
    			}catch(Exception ef){
    			}
    		}
    	}
    }
    
    • 然后我们在主类的按钮事件监听器这边插入这样一段代码,即每按一次按钮则生成一个ThreadClass对象
    public void actionPerformed(ActionEvent e){
    	ThreadClass thc=new ThreadClass(g);
    	thc.start();
    }
    
    • 在这里我们生成ThreadClass对象并调用start()函数后,线程被创建并进入准备状态,每个线程对象都可以同时独立执行run()方法中的函数,当run()方法中的代码执行完毕时线程自动停止。

    接下来我们试着运行一下吧!
    在这里插入图片描述

    加入清屏功能,让小球真正的动起来

    • 从上面的画图示范我们可以看出,小球在移动过程中是留下了轨迹的,那如果我只想看到小球的运动,不想看到小球的轨迹怎么办?
    • 很简单,我们只需要每次画新的小球之前先给整个画布画上一个大的背景色矩形,把原来的图案覆盖即可。

    让我们试着把run()方法中的代码改为下面这样:

    public void run(){
    	//获取一个随机数对象
    	Random ran=new Random();
    	//生成一对随机的x,y坐标设为小球的坐标,范围都在0-399
    	int x=ran.nextInt(400);
    	int y=ran.nextInt(400);
    	for(int i=0;i<100;i++){
    		//画一个能够覆盖画面中一块区域的白色矩形来清屏(把原来的笔迹都覆盖掉)
    		g.setColor(Color.white);
    		g.fillRect(300,300,300,300);
    		g.setColor(Color.black);
    		g.fillOval(200+x+i,200+y+i,30,30);
    		try{
    			Thread.sleep(30);
    		}catch(Exception ef){
    		}
    	}
    }
    

    让我们试着运行一下
    在这里插入图片描述

    • 我们运行后发现,小球确实在中间的白色矩形区域内实现了不留轨迹的运动,但是小球闪烁的非常厉害。这其中的原因有两个,一个是多个线程对象同时运行会产生冲突,另一个是IO设备使用频率过高。后者我们在稍后的部分讲到用双缓冲绘图去解决,前者则通过下面的方法解决。
    • 我们只在主类中创建一次ThreadClass对象。然后再创建一个列表,每次按按钮时将一组坐标存到这个列表中,最后通过run()方法中依次读出这个列表中的每一项并画出。
      在主类中创建ThreadClass对象并运行(主类的showFrame方法中插入以下代码)

    先创建坐标类

    public class Location {
    	public int x;
    	public int y;
    	public Location(int x,int y){
    		this.x=x;
    		this.y=y;
    	}
    }
    

    然后在主类和ThreadClass类中创建列表

    
    	public ArrayList<Location> locs=new ArrayList<Location>();
    

    然后在按钮监听器的方法下写入这段代码

    public void actionPerformed(ActionEvent e){
    	Random ran=new Random();
    	int x=ran.nextInt(400);
    	int y=ran.nextInt(400);
    	Location loc=new Location(x,y);
    	locs.add(loc);
    	System.out.println(locs.size());
    }
    

    然后将画布g和列表locs传入创建的线程对象中,在主类的showFrame方法插入以下代码。

    ThreadClass thc=new ThreadClass(g,locs);
    thc.start();
    

    重载Thread Class的run()方法

    public void run(){
    	while(true){
    		g.setColor(Color.white);
    		g.fillRect(300,300,300,300);
    		for(int i=0;i<locs.size();i++){
    			g.setColor(Color.black);
    			//每次给小球坐标偏移一下
    			int x=locs.get(i).x++;
    			int y=locs.get(i).y++;
    			g.fillOval(200+x,200+y,30,30);
    		}
    		try{
    			Thread.sleep(30);
    		}catch(Exception ef){
    		}
    	}
    }
    

    让我们再来试一下!
    在这里插入图片描述
    这下小球就没有闪烁的那么厉害了。

    二、如何让小球在画面中真实地动起来?

    • 众所周知,要想描述物体的运动状态,需要知道物体的三个物理量——位置、速度和加速度。我们只需要找到方法描述这三个物理量,便可以很好的模拟真实小球的运动。

    在这里我们可以创建一个Vector类来描述位置、速度和加速度这三个物理量

    public class Vector {
    	public int x;
    	public int y;
    	public Vector(int x,int y){
    		this.x=x;
    		this.y=y;
    	}
    	//向量的加和运算
    	public void add(Vector vec){
    		this.x+=vec.x;
    		this.y+=vec.y;
    	}
    }
    

    然后我们再创建一个Ball类来代表小球(move函数是本部分的关键)

    public class Ball {
    	public Vector location;//位置
    	public Vector speed;//速度
    	public Vector acce;//加速度
    	
    	//构造器传参,设定小球的基本参数
    	public Ball(Vector location,Vector speed,Vector acce){
    		this.location=location;
    		this.speed=speed;
    		this.acce=acce;
    	}
    	
    	//小球移动,这是整个部分的关键!!!每画完一次小球就调用一次move函数,让小球依据速度和加速度来改变一次位置
    	public void move(){
    		this.speed.x+=acce.x;//每调用一次move函数小球的速度就和加速度做一次加法
    		this.speed.y+=acce.y;
    		this.location.x+=speed.x;//每调用一次move函数小球的位置坐标就和速度做一次加法
    		this.location.y+=speed.y;
    	}
    }
    

    有了这两个类,我们就可以表示任意二维的向量运动了

    • 比如说从原点出发,向右速度为5,向下加速度为10的平抛运动可以表示为
    Vector location=new Vector(0,0);
    Vector speed=new Vector(5,0);
    Vector acce=new Vector(10,0);
    
    • 从原点出发,向右速度为5,向上速度为10,向下加速度为10的上抛运动可以表示为
    Vector location=new Vector(0,0);
    Vector speed=new Vector(5,10);
    Vector acce=new Vector(10,0);
    
    • 利用这个原理,我们已经可以做出一点好玩的东西了!
      试想一下,我们可以先给窗体添加一个鼠标监听器,然后获取鼠标按下松开的点的坐标,然后沿着按下和松开的点连成的直线方向丢出一个小球,这样是不是就可以做一个投篮游戏了呢。
    • 具体操作:给窗体加上鼠标监听器👉在mousePress函数下获取鼠标按下的点的坐标x1,y1👉在mouseRelease函数下获取鼠标松开的点的坐标x2,y2👉生成一个小球对象,以(x2,y2)作为小球坐标,(x2-x1)作为x方向上的速度,(y2-y1)作为y方向上的速度,y方向上加速度为1。然后把这个小球放入到传入ThreadClass的列表中,让线程将这个小球画出。

    需要改变的是主类中Mouse类的代码和ThreadClass类中run方法的代码

    • Mouse类
    //创建内部类监听器(也可以重新创建一个文件编写该类)
    	class Mouse implements ActionListener,MouseListener{
    		int prx=0;
    		int pry=0;//记录按下鼠标的点的坐标
    		
    		//重写按钮监听方法
    		public void actionPerformed(ActionEvent e){}
    
    		public void mouseClicked(MouseEvent e) {}
    
    		public void mousePressed(MouseEvent e) {
    			prx=e.getX();
    			pry=e.getY();//获取按下鼠标的点的坐标
    		}
    
    		public void mouseReleased(MouseEvent e) {
    			int speedx=(int)((e.getX()-prx)/10);
    			int speedy=(int)((e.getY()-pry)/10);
    			
    			Vector location=new Vector(e.getX(),e.getY());
    			Vector speed=new Vector(speedx,speedy);
    			Vector acce=new Vector(0,1);
    			Ball ball=new Ball(location,speed,acce);
    			balls.add(ball);
    		}
    		public void mouseEntered(MouseEvent e) {}
    		public void mouseExited(MouseEvent e) {}
    	}
    
    • ThreadClass下的run方法
    public void run(){
    		while(true){
    			g.setColor(Color.white);
    			g.fillRect(300,0,600,900);
    			for(int i=0;i<balls.size();i++){
    				g.setColor(Color.black);
    				g.fillOval(balls.get(i).location.x,balls.get(i).location.y,30,30);
    				balls.get(i).move();
    			}
    			try{
    				Thread.sleep(30);
    			}catch(Exception ef){
    			}
    		}
    	}
    

    执行效果如图所示(为了显示小球向量运动的效果,这里省去了清屏操作)
    ![](https://img-blog.csdnimg.cn/20210129145320124.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDcxMDQxMg==,size_16,color_FFFFFF,t_7
    完整代码放到这里:
    https://pan.baidu.com/s/10HcOSvuov14moes1jPe9JQ
    提取码:z8ii

    三、多线程游戏仿真实例分享

    (三个游戏的源代码、图片素材链接在下文中获取)

    游戏一:飞机大战

    游戏演示:

    Java游戏制作

    游戏说明:
    在这里插入图片描述

    飞机大战简介:

    • 飞机大战整个程序一共用了五个类GameUIFrame(游戏界面窗体显示及主程序)、ThreadClass(线程类,进行图片绘制、生成怪物、判断碰撞、刷新分数等一系列功能,是程序的主体部分)、FlyObject(创建所有飞行物对象的类,可以定义飞行物的位置、速度、加速度、显示图片等)、Vector(上文中介绍过的向量类)以及Listener(鼠标监听器,负责获取鼠标在屏幕上的点绘制我方飞船和生成飞船发出的子弹)。

    要实现飞机大战主要完成这几件事:

    1. 绘制我方飞机、不断发射子弹
    2. 不断随机生成怪物、宝箱
    3. 判断子弹与怪物、怪物与我方飞机之间是否碰撞
    4. 爆炸动效、刷新分数

    由于时间关系,目前博主所制作的游戏暂时只具有以上这些功能,有兴趣的伙伴还可以试着增加关卡、Boss、新的怪物(比如会发射子弹的怪物)、剧情等等。
    我们在游戏进行的过程中,难免会生成大量的图片对象。前面我们讲到,当我们需要在屏幕上绘制的图像过多时会出现卡顿闪屏现象,第二个解决方法就是双缓冲绘图,下面我来简单的介绍一下。

    双缓冲绘图解决闪屏

    • 我们正常画图的时候,是从窗体对象中直接获取Graphics类对象来绘画,然后每一次把需要画的图形传输到我们的屏幕上时,都需要占用一定的输入输出(IO)设备通道。所以当我们需要绘制的图像过多时将导致IO设备使用频率过高,屏幕就会出现闪屏现象。
    • 双缓冲绘图就是一次性把所有要画的对象先画到内存中,最后再把内存中的图片用窗体对象中直接获取Graphics类对象画出来。
    • 打个比方,比如说我要把地上的落叶全部扫进垃圾桶里,我把地上的树叶一片一片直接捡到垃圾桶里,这就是不用双缓冲绘图的情况;如果说我有一个垃圾铲,先把树叶捡到垃圾铲里,然后再一次性倒进垃圾桶,效率是不是高多了?这就有点类似双缓冲绘图的原理。

    要实现双缓冲绘图,首先我们要创建BufferedImage对象,然后从这个缓存对象中获取画布:

    //创建缓存
    BufferedImage bufImg=new BufferedImage(1200,1200,BufferedImage.TYPE_INT_ARGB);
    //最后的TYPE_INT_ARGB代表创建的是具有合成整数像素的 8 位 RGBA 颜色分量的图像,也可以选择其他类型,详见Java的API文件
    //获取缓存上的画布
    Graphics bufg=bufImg.getGraphics();
    

    获取了Graphics bufg后,我们所有的绘图操作先在bufg上完成,等一轮图像画完之后,再把bufg上的图像画到原本的Graphics对象中

    g.drawImage(bufImg,0,0,null);
    

    飞机大战制作Step1:飞行物

    • 游戏中真正的飞行物一共只有三种,我方飞机、怪物和子弹(均需要在创建FlyObject对象时设定位置、速度、加速度、图片、血量)。但是因为宝箱、爆炸特效需要定义的参数比真正的飞行物要少(只需要位置和图片),所以宝箱和爆炸特效也可以使用FlyObject类创建。
    • 我们可以为上面五类飞行物各创建一个列表,用于存放其对象;每次需要生成一架飞机或者一个怪物时,就往对应的列表中放入一个对象。然后在线程的run方法中依次将每个列表中的所有对象全部画出
    • 在FlyObject类中,最重要的是FlyObject的构造方法、move方法(负责计算飞行物的下一个坐标)和drawFO方法(传入画布,将飞行物图片画到飞行物的坐标上)。
    //有图片、有血量的飞行物
    public FlyObject(Vector location,Vector speed,Vector acce,String imgName,int HP){
    	this.location=location;//位置
    	this.speed=speed;//速度
    	this.acce=acce;//加速度
    	this.HP=HP;//血量
    	
    	this.imgName=fileAddress+imgName;//图片地址
    	ImageIcon imgicon=new ImageIcon(this.imgName);//如果我们想要在画布上画一张图片,可以先用图片地址创建一个ImageIcon对象,然后再从这个对象中获取Image对象
    	img=imgicon.getImage();
    }
    //前面介绍过的move方法
    public void move(){
    		speed.add(acce);
    		location.add(speed);
    }
    //将飞行物的图片画到画布上
    public void drawFO(Graphics g){
    //如果被绘制的对象有图片就画图片,没图片就画一个圆
    	if(imgName!=null){
    //			System.out.println(imgName);
    		g.drawImage(img,location.x, location.y,null);
    	}else{
    		g.fillOval(location.x, location.y,10,10);
    	}
    }
    
    • 游戏过程中,我方飞船是始终跟随着鼠标共同移动的。要实现这一点,我们需要在Listener类中实现MouseMotionListener,然后重写鼠标移动mouseMoved方法(记得最后要给窗体添加MouseMotionListener监听器)。当鼠标在窗体中进行移动时,该方法会不断地获取鼠标在窗体中的坐标,参考下面这段代码重写mouseMoved方法:
     public void mouseMoved(MouseEvent e){
        	Vector location=new Vector(e.getX(),e.getY());
        	FlyObject mp=new FlyObject(location,null,null,"我机.png");
        	mps.add(mp);//mps是存放我方飞机对象的列表ArrayList<FlyObject> mps
        }
    
    • 这样一来,在移动鼠标的过程该方法会被不停的调用,并且不停的往mps列表中存放我放飞机对象。在线程类的run方法中,我们每一次只需要获取该列表的最后一项(我方飞机的最新坐标)将其画出即可。
    • 因为子弹是我方飞机发射出来的,所以子弹生成坐标只需要取我方飞机的坐标即可。
    //不断发射子弹
    	public void generateBullet(){
    	//隔一段时间就生成一些子弹(int len是一个计数器,它记录的是run方法中的运行次数,所有代码跑完一次就加一)
    			if(len%5==0){
    		    	for(int i=0;i<4;i++){
    		    	//设定子弹坐标
    		    		Vector location_fo=new Vector(mps.get(mps.size()-1).location.x,mps.get(mps.size()-1).location.y+20*i);
    		        	Vector speed_fo=new Vector(100,0);//设定子弹速度
    		        	Vector acce_fo=new Vector(0,0);//设定子弹加速度(这里把加速度设为0意思就是让子弹做匀速运动)
    		        	FlyObject fo=new FlyObject(location_fo,speed_fo,acce_fo,"子弹.png",1);
    		        	fos.add(fo);
    		    	}
    			}
    	}
    
    • 怪物的生成就更简单了,在游戏设定中,怪物会从界面的最右边被生成,一直往窗体的最左边走,纵坐标和速度是随机的。
    public void generateEnemy(){
    	if(len%20==0){
    		Random ran=new Random();
    		//怪物的横坐标是固定的(窗体的最右边),纵坐标是随机的
    		int loc_y=ran.nextInt(900)+100;
    		//怪物只在x方向有速度
    		int spd_x=-ran.nextInt(10)-10;
    		Vector location=new Vector(1200,loc_y);
    		Vector speed=new Vector(spd_x,0);
    		Vector acce=new Vector(0,0);
    		FlyObject enemy=new FlyObject(location,speed,acce,"怪物.png",5);
    		enemys.add(enemy);
    	}
    }
    

    飞机大战制作Step2:判断碰撞

    • 判断两个物体是否碰撞,我这里用到的原理是判断两个飞行物坐标的距离是否小于一定的值(比如说图片宽度)。而且我们每一轮都要判断每一个子弹和每一个怪物的距离,都要判断我方飞机和怪物的距离等等。我们的所有飞行物都被放入了列表中,所以我们需要建立循环拆解列表,将其中的元素逐个取出,逐个比较
    //判断子弹是否击中怪物、怪物是否触碰我机、是否拾得宝箱
    public void judgeAttack(Graphics bufg_judgeAttack){
    	//判断子弹是否击中怪物
    	for(int i=0;i<enemys.size();i++){
    		//取出怪物对象
    		FlyObject en=enemys.get(i);
    		for(int j=0;j<fos.size();j++){
    			//取出子弹对象
    			FlyObject fo=fos.get(j);
    			//获取子弹和怪物的坐标位置
    			int fo_x=fo.location.x;
    			int fo_y=fo.location.y;
    			int en_x2=en.location.x;
    			int en_y2=en.location.y;
    			
    			//计算怪物和子弹之间的距离(也可以采用if(横坐标的差值<某数&纵坐标的差值<某数)
    			int distance_fo_en=(int)Math.sqrt(Math.pow((fo_x-en_x2),2)+Math.pow((fo_y-en_y2),2));
    			if(distance_fo_en<=50){
    				//这里en(怪物)的HP是血量,fo(子弹)的HP是伤害值。
    				en.HP-=fo.HP;
    				//在该子弹位置添加一个子弹爆炸效果,后面会介绍
    				explosion(fos.get(j));
    				//将该子弹从列表中移除
    				fos.remove(j);
    				if(en.HP<=0){
    					//怪物爆炸效果
    					explosion(enemys.get(i));
    					//这里如果直接用enemys.remove(i)会导致循坏for(int j=0;j<fos.size();j++)继续执行,误删其他元素
    					enemys.get(i).img=null;
    					//把怪物图片去除(每次画图就不画该怪物了),然后把它移出屏幕
    					enemys.get(i).location=new Vector(-1000,0);
    					if(en.imgName.equals(fileAddress+"怪物.png")){
    						score+=10;
    					}else if(en.imgName.equals(fileAddress+"怪物2.png")){
    						score+=50;
    					}
    				}
    				
    			}
    			
    		}
    	}
    }
    

    飞机大战制作Step3:爆炸动效

    • 前面说到过爆炸动效也可以放进FlyObject类列表中,完成爆炸动效需要写两个方法,一个方法生成爆炸动效对象,一个方法绘制爆炸动效。因为爆炸动效一般都是在子弹或者怪物消失的时候才会生成,所以只生成一次;但是绘制爆炸动效需要多次绘制,所以生成爆炸动效对象和绘制爆炸动效需要分成两个方法来写。

    • 生成爆炸动效的方法传入的是飞行物的对象,因为绘制爆炸动效至少需要两个元素:爆炸发生在哪里,生成什么爆炸效果(怪物的爆炸效果和子弹的爆炸效果不同)。所以首先我们对该飞行物的图片名称进行一个判断(判断是什么东西爆炸),然后取出它的坐标。最后生成一个对应的爆炸效果对象放入列表中。
      在这里插入图片描述

    • 爆炸效果是一种动态效果,所以还涉及到切换图片的操作。我们可以将预先准备好的几张图片同意文件名格式并编好序号,方便每画完一次图片就切换一张。

    	//爆炸动效
    	public void explosion(FlyObject flo){
    	//判断是什么对象爆炸
    		if(flo.imgName.equals(fileAddress+"怪物.png")|flo.imgName.equals(fileAddress+"怪物2.png")){
    		//获取爆炸对象的坐标
    			int x_explo=flo.location.x;
    			int y_explo=flo.location.y;
    			Vector location=new Vector(x_explo,y_explo);
    			//生成爆炸动效对象
    			FlyObject explo=new FlyObject(location,null,null,"爆炸_1.png",10);
    			//将爆炸动效对象添加到列表中
    			explotions.add(explo);
    		}
    	}
    	
    	//绘制爆炸动效
    	public void drawExplo(Graphics bufg_explotion){
    	//依次将列表中的每个爆炸图像画出
    		for(int i=0;i<explotions.size();i++){
    			explotions.get(i).drawFO(bufg_explotion);
    			//这里的HP表示的是这个爆炸效果持续的时间,每画一次效果HP减一,当HP等于0时停止绘制该爆炸效果
    			explotions.get(i).HP--;
    			
    			if(explotions.get(i).imgName.equals(fileAddress+"爆炸_1.png")){
    				//下面这条代码的作用是每画完一次图像就更换一次图片,以此达到动态变化的效果
    				ImageIcon imgicon=new ImageIcon(fileAddress+"爆炸_"+((explotions.get(i).HP%3)+1)+".png");//因为我绘制的爆炸效果图片一共有三张,所以这里取除以三的余数来设定图片的文件名
    				explotions.get(i).img=imgicon.getImage();
    			}
    			//当爆炸动效对象的HP等于0时移除该对象
    			if(explotions.get(i).HP==0){
    				explotions.remove(i);
    			}
    		}
    	}
    

    飞机大战制作Step4:游戏暂停/继续,判定游戏结束

    • 我们想要的效果:在游戏画面的左下角有一个暂停键,我们点击暂停键时游戏会进入暂停状态,再点击开始游戏会恢复到暂停之前的状态。在这里插入图片描述

    • 我们需要做的操作:我们可以创建一个布尔值对象gameRest(布尔值只有true和false两种状态),初始值设定为false,每一轮线程运行时都需要先判断一下gameRest值是否为false,如果为true,则跳过绘制飞行物、判断碰撞等操作;如果为false,则继续正常运行。

    • 接着我们写一个方法来改变gameRest的值,这样我们每调用一次方法就切换一次gameRest的值

    //游戏暂停/开始
    public void on_off(){
    	gameRest=!gameRest;
    }
    
    • 在鼠标监听器中添加一个监听事件,在mouseReleased方法下我们可以判断一下鼠标松开的坐标是否落在画面左下角这块区域,如果是就调用on_off方法来改变gameRest的值。
    • 当我们的飞船与怪物相碰时,飞船坠落,游戏结束,这里同样用到了一个布尔值gameOver
    //判断游戏是否结束
    	public void judgeGameOver(Graphics g_judgeGameOver){
    		for(int i=0;i<enemys.size();i++){
    			FlyObject en=enemys.get(i);
    			
    			FlyObject mp=mps.get(mps.size()-1);
    			
    			int mp_x=mp.location.x;
    			int mp_y=mp.location.y;
    			int en_x=en.location.x;
    			int en_y=en.location.y;
    			
    			int distance_mp_en=(int)Math.sqrt(Math.pow((mp_x-en_x),2)+Math.pow((mp_y-en_y),2));
    			if(distance_mp_en<=60){
    				//绘制gameOver图片
    				ImageIcon imgicon_gamover=new ImageIcon(fileAddress+"gameover.png");
    				Image img_gamover=imgicon_gamover.getImage();
    				g_judgeGameOver.drawImage(img_gamover,0,0,null);
    				gameOver=true;
    			}
    		}
    	}
    
    • 最后在run方法中插一段判断gameOver的代码
    if(gameOver==true){break;}
    

    飞机大战制作Step5:刷新分数

    • 将游戏分数的万位、千位、百位和十位和个位分别取出,然后每个数字对应显示一张图片,将刷新分数的方法写入run方法中,每一轮刷新一次分数。(Java中的符号“/”代表整除)
    //获取万位
    int number_5=score/10000;
    //获取千位
    int number_4=(score-number_5*10000)/1000;
    //获取百位
    int number_3=(score-number_5*10000-number_4*1000)/100;
    //获取十位
    int number_2=(score-number_5*10000-number_4*1000-number_3*100)/10;
    //获取个位
    int number_1=score-number_5*10000-number_4*1000-number_3*100-number_2*10;
    
    • 同样的,将每个数字的图片素材同一文件名格式并编号

    在这里插入图片描述

    • 这里的fileAddress是我存放图片素材的目录,这样当我更换图片目录时只需要更改这一个值就可以了。
    //生成图片对象
    ImageIcon imgicon_score=new ImageIcon(fileAddress+"Score.png");
    Image img_score=imgicon_score.getImage();
    ImageIcon imgicon5=new ImageIcon(fileAddress+number_5+".png");
    Image img5=imgicon5.getImage();
    ImageIcon imgicon4=new ImageIcon(fileAddress+number_4+".png");
    Image img4=imgicon4.getImage();
    ImageIcon imgicon3=new ImageIcon(fileAddress+number_3+".png");
    Image img3=imgicon3.getImage();
    ImageIcon imgicon2=new ImageIcon(fileAddress+number_2+".png");
    Image img2=imgicon2.getImage();
    ImageIcon imgicon1=new ImageIcon(fileAddress+number_1+".png");
    Image img1=imgicon1.getImage();
    
    //bufg_score是该方法导入的Graphics类画布
    bufg_score.drawImage(img_score, 340,50,null);
    bufg_score.drawImage(img5, 590,50,null);
    bufg_score.drawImage(img4, 650,50,null);
    bufg_score.drawImage(img3, 710,50,null);
    bufg_score.drawImage(img2, 770,50,null);
    bufg_score.drawImage(img1, 830,50,null);
    
    • 第一个游戏案例的分享差不多就到这里,如果有什么描述不够清楚的地方欢迎大家在评论区留言。也可以点击下方链接,下载我这三个游戏的全部源代码和游戏素材进行参考

    游戏源代码及游戏素材链接——提取码:hjzd

    • 这个游戏目前来说做得还非常粗糙,还有一些小漏洞和可以优化的地方,如果有小伙伴下载了我的代码,发现有什么好的建议欢迎私信或在评论区中指出。欢迎交流,您的评论将给我的学习之路带来巨大帮助。

    可以优化的地方:

    1. 有的子弹会穿过怪物,或者有时候碰到怪物没有死,说明判断碰撞和物体移动的方法还有缺陷。
    2. 怪物血量减少到一定程度时出现破损效果,这样看起来对怪物剩余血量更直观
    3. 游戏玩法比较单一,可以给飞机适当增加新的技能,增加关卡和Boss,丰富玩法
    4. 缺少游戏开始界面、背景音乐、音效等
    5. 游戏玩到后期比较卡顿,因为飞出窗体的子弹、怪物等仍然存在列表中,每次绘制图片时都要将这些看不见的对象重新再画一遍,十分消耗性能。

    游戏二:接豆人

    游戏演示:

    Java原创游戏分享

    游戏介绍:
    在这里插入图片描述

    • 接豆人游戏和飞机大战玩法虽然差异比较大,但是用到的代码原理其实是类似的。

    • 黄色的吃豆人的移动,同样是依靠鼠标监听器的mouseMoved方法不断获取鼠标的坐标然后绘制接豆人的图像。只不过这次我们只获取鼠标的横坐标,纵坐标设定为一个定值,这样就可以实现我们的接豆人只做水平方向的运动了。
      在这里插入图片描述

    • 接豆人吃到宝石和道具的判断,和飞机大战中的判断碰撞是类似的;接豆人中随机掉落的宝石、炸弹和道具,与飞机大战中刷新怪物是类似的。

    • 两个游戏比较不同的地方是,在接豆人游戏中如果吃到了蜘蛛或者金币礼包是会触发新事件的。而且在接豆人中也增加了玩家的生命值。

    • 大致总结一下,实现接豆人需要完成这几件事:让接豆人在水平方向跟随鼠标移动👉随机生成宝石、炸弹蜘蛛和道具,并且赋予下落物体一个垂直方向的加速度,增加真实感👉判断接豆人是否接到了掉落物👉给金币礼包和蜘蛛添加触发效果(下金币雨和接豆人进入眩晕)👉游戏暂停、游戏结束后重新开始👉间隔一段时间清理一下飞行物列表,提高游戏流畅度

    • 和飞机大战类似的地方就不再赘述,这里介绍一些不同的地方

    接豆人制作Step1:金币礼包和蜘蛛的触发效果

    • 首先我们需要定义四个变量
    	public int rewardTime;//奖励时间
    	public Boolean pause=false;//是否进入眩晕状态
    	public int pauseTime;//眩晕时间
    	public FlyObject mp_pause;//用于绘制眩晕时接豆人的图片
    
    • 在判断碰撞的方法下面,如果接豆人碰到的是礼物,则给rewardTime加上200,如果是蜘蛛,则给pauseTime加上200,且将pause的值改为true。

    在这里插入图片描述

    • 在生成下落物的方法中,我们先对rewardTime进行一个判断,如果rewardTime大于0,就下金币,如果小于等于0,就生成其他掉落物。
    //生成下落物
    public void generateDrop(){
    	if(rewardTime>0){
    		if(len%1==0){
    			rewardTime--;//每次rewardTime递减
    			Random ran=new Random();
    			Vector location=new Vector(ran.nextInt(750)+50,50);
    			Vector speed=new Vector(0,ran.nextInt(1)+10);
    			Vector acce=new Vector(0,2);
    			
    			FlyObject fo=new FlyObject(location,speed,acce,"金币1.png");
    			fos.add(fo);
    		}
    	}else{//生成其他掉落物
    	}
    
    • 当接豆人进入眩晕状态时,身边的掉落物还是正常掉落的,但是接豆人在眩晕状态下不能移动、不能接取掉落物。所以我们需要在绘制接豆人和判断碰撞的方法下分别先对pause的值进行一个判断,如果pause为false则正常运行。
    //绘制我机
    public void draw_mp(){
    	if(pause==false){
    		if(mps.size()-5>=0){
    			FlyObject mp=mps.get(mps.size()-5);
    			mp.drawFO(bufg);
    		}
    	}else{
    		//当pause为true时执行
    		pauseTime--;//pauseTime递减
    		mp_pause.drawFO(bufg);//绘制接豆人眩晕时的图片
    		if(pauseTime==0){
    			pause=false;//当pauseTime减少到0时将pause改回为false
    		}
    	}
    }
    

    接豆人制作Step2:游戏重新开始

    • 这个功能的实现和飞机大战中说过的暂停功能非常类似,我们需要创建一个布尔值gameOver,然后当生命值减少到0时将gameOver改为true。然后屏幕上显示gameOver的图像
      在这里插入图片描述
    • 当我们点击该区域时,将gameOver的值改回为false,并且将所有的飞行物列表、分数、生命值等全部恢复到游戏刚开始的状态。
    if(thc.gameOver){
    	if(e.getX()>340&e.getX()<540&e.getY()>630&e.getY()<710){
    		thc.life=3;
    		thc.fos.removeAll(fos);
    		thc.mps.removeAll(mps);
    		thc.score=0;
    		thc.gameOver=false;
    	}
    }
    

    接豆人制作Step3:清理列表数据,提升流畅度

    • 我们每间隔一段时间就把超出窗体可见范围的飞行物都从列表中删去,防止游戏后期需要画的飞行物太多导致卡顿。
    //清理缓存(在run方法中调用该方法)
    public void clear(){
    	//每500轮清理一次
    	if(len%500==0){
    		System.out.println("清理前:");
    		System.out.println("fos size is"+fos.size());
    		System.out.println("mps size is"+mps.size());
    		System.out.println("exps size is"+explotions.size());
    		clearList(fos,0);
    		clearList(mps,1);
    		clearList(explotions,0);
    		System.out.println("清理后:");
    		System.out.println("fos size is"+fos.size());
    		System.out.println("mps size is"+mps.size());
    		System.out.println("exps size is"+explotions.size());
    	}
    }
    	
    //清理列表(传入需要清理的列表,并传入清理类型)
    public void clearList(ArrayList<FlyObject> fos,int flag){
    	int fos_size=fos.size();
    	//其他飞行物类型的清理
    	if(flag==0){
    		for(int i=fos.size()-10;i>-1;i--){
    			//判断一下从哪个飞行物开始超出窗体可见范围(在它之前的飞行物一定是超过了)
    			if(fos.get(i).location.y>1000){
    				for(int j=0;j<i;j++){
    					fos.remove(0);//不断删除列表的第一个元素,直到删到开始超出窗体范围的那一个
    				}
    				break;
    			}
    		}
    	//接豆人的列表的清理
    	}else if(flag==1){
    		//只保留列表中最后一百个元素,前面的全部删除
    		for(int i=0;i<fos_size-100;i++){
    			fos.remove(0);
    		}
    	}
    }
    

    游戏源代码及游戏素材链接——提取码:hjzd

    游戏三:双线挑战(双人游戏)

    游戏截图:
    在这里插入图片描述
    游戏介绍:

    • 这个游戏和上面两个游戏不太一样,它是一个使用键盘操控的双人小游戏。操作方法有点类似贪吃蛇,两个人分别操控一条线,当触碰到游戏边界或者自身及对手的线时,游戏结束。所以在游戏过程中,双方可以尽可能地把对方包围在一个比较小的空间里,使自己成为最后的赢家。
    • 虽然这个游戏的画面设计比较粗糙,但是这次增加了游戏开始界面、游戏背景音乐的播放功能,仍然非常有意思。
    • 这个游戏一共做了三个版本
      在这里插入图片描述

    游戏皮肤:在这里插入图片描述

    双线挑战制作Step1:键盘监听器的使用

    • 键盘监听器的接口是KeyListener,我们主要用到keyPress和keyReleased两个方法,他们分别在键盘按下和键盘松开时被调用。
    class Listener implements KeyListener{
    	public void keyTyped(KeyEvent e) {
    	}
    	public void keyPressed(KeyEvent e) {
    	//获取按下键的keycode
    		int keyc=e.getKeyCode();
    		System.out.println(keyc+" is pressed!");
    		//也可以使用String press=e.getKeyChar()+"";这样获取到的就是键盘的字符
    	}
    	public void keyReleased(KeyEvent e) {
    	//获取松开键的keycode
    		int keyc=e.getKeyCode();
    		System.out.println(keyc+" is released!");
    	}
    }
    
    • 然后一定要记得给窗体加上监听器对象!而且键盘的监听相较于鼠标监听器还有一个特殊的地方,键盘的监听器需要焦点,键盘监听器需要获取焦点发生的动作事件。比如说我们的qq登陆界面上有两个输入框,如果我们直接敲击键盘,此时电脑是不知道我们需要输入的是账号还是密码。只有我们点击账号文本框后,才能让账号文本框得到焦点,从而顺利输入我们的账号。

    在这里插入图片描述

    • 同时,窗体获取焦点的代码也必须放在窗体可见之后,否则无法正常监听键盘事件
    //设置窗体可见,jf是创建的JFrame对象
    jf.setVisible(true);
    jf.addKeyListener(mou);//为窗体添加键盘监听器
    jf.requestFocusInWindow();//窗体获得焦点,记得要放在窗体可见之后
    
    • 现在让我们一起来做几个小实验,对键盘监听器的原理进一步了解(记得在实验前将键盘调为英文输入模式,否则无法正常监听英文键的输入):连续敲击F键;长按F键;慢速交替敲击F和D键;同时按下F和D键;快速交替敲击F和D键。
    • 下面是博主测出的结果(F键的keycode为70,D键的keycode为68):
      在这里插入图片描述
    • 看出来这几种按键方式的特点了吗?我们使用键盘与程序交互时,这些按键方式反馈的差异会给我们带来很大帮助。比如说有的游戏中同时按下W和D键是向右上跳,只按W键是向上跳,只按D键是向右走。我们就可以在键盘监听器中先判断用户是同时按了W,D键还是先按了D键再按W键,从而决定让角色向右上跳,还是先向右走再向垂直上跳。

    双线挑战制作Step2:用键盘控制线条的走向

    • 因为在双线挑战游戏中,我们是需要线条留下轨迹的,所以我们的游戏背景图片只需要画一次(否则就会把轨迹覆盖了)。那怎么让图片只画一次呢?
    //我们可以先定义一个整数flag1
    public int flag1=0;
    
    • 在画图之前先判断一下这个值是不是0,是0的话说明没有被画过;在画图的代码中,记得将flag1改为除0以外的数,表示这个图已经被画过一次了
    	//只画一次图片
    public void draw_just_once(int type){
    //如果说flag1为0,则开始画图
    	if(flag1==0){
    		ImageIcon imgic=new ImageIcon(fileAddress+"游戏背景_2.png");
    		Image img=imgic.getImage();
    		g.drawImage(img, 0,0,null);
    		flag1++;//更改flag1的值,表示图已画过
    	}
    }
    
    • 当我们的背景只画一次,而小方块又在不停的移动时,小方块自然就留下了轨迹;对于实现小方块移动的方法,和飞机大战中的FlyObject类、Vector类差不多。
    • 我们先创建一个从窗体的左上角出发的小方块,速度向右为1(这里的LineBall类和FlyObject原理及代码基本相同(move方法、Vector类的使用等),不太清楚的小伙伴可以回到飞机大战Step1看一看)
    lb_blue=new LineBall(new Vector(0,0),new Vector(1,0));
    
    • LineBall的drawLB方法和FlyObject的drawFO方法稍有不同。因为我这里用的小方块的图片是5个像素,所以说小方块的location每加1,我就让小方块的坐标向右移5个像素。
    public void drawLB(Graphics g){
    	if(imgName==null){
    		g.fillRect(location.x*10+50, location.y*10+50, 10,10);
    	}else{
    		ImageIcon imgic=new ImageIcon(fileAddress+imgName);
    		Image img=imgic.getImage();
    		g.drawImage(img,location.x*10+50, location.y*10+70, null);
    	}
    }
    
    • 然后在线程中运行这一段代码
    	lb_blue.imgName="蓝_4.png";
    	lb_blue.drawLB(g);//画完以后让小方块move移动一次
    	lb_blue.move();
     try{
    	 Thread.sleep(50);
     }catch(Exception ef){}
    
    • 运行效果大概是这样的
      在这里插入图片描述
    • 现在如果我们想让小方块改变它的移动方向,只需要改变对象lb_blue的speed值即可(比如说想让小方块往下走,那speed就改成(0,1);想往左走,那就改成(-1,0)。
    • 因为我们要使用键盘操控,所以我们必须获取WASD和上下左右键的keycode(可以使用双线挑战制作Step1中的方法自己试验一下,把这8个按键都按一遍就知道它们的keycode了,需要知道其他按键的keycode也可用此方法)。
    • 在键盘监听器的keyReleased方法下去判断按键及做出响应
    public void keyReleased(KeyEvent e) {
    	int keyc=e.getKeyCode();
    	System.out.println(keyc+" is released!");
    	int speed=1;
    	if(lb_blue.len!=0){
    		if(lb_blue.speed.y==0){
    			if(keyc==87){//w
    				lb_blue.len=0;
    				lb_blue.speed=new Vector(0,-speed);
    			}
    			if(keyc==83){//s
    				lb_blue.len=0;
    				lb_blue.speed=new Vector(0,speed);
    			}
    		}
    		if(lb_blue.speed.x==0){
    			if(keyc==65){//a
    				lb_blue.len=0;
    				lb_blue.speed=new Vector(-speed,0);
    			}
    			if(keyc==68){//d
    				lb_blue.len=0;
    				lb_blue.speed=new Vector(speed,0);
    			}
    		}
    	}
    }
    
    • 博主这里还用到了几个判断,在这里我给大家解释一下。
    • if(lb_blue.speed.y==0)/if(lb_blue.speed.x==0):这里的判断是,假如小方块目前正在往左走或者往右走(即y方向速度为0)时,才可以向上或者向下拐(不然就会出现本来在往上走,按了向下键后突然原地掉头,在这个游戏设定中是不符合规则的)后面的判断x方向速度同理。在这里插入图片描述
    • if(lb_blue.len!=0):这里的len代表的是小方块在当前方向行走的距离,每更改一次方向len就清零一次。它的意思是小方块更改方向后,必须往更改后的方向至少前进一格才能再次更改方向,否则仍然有可能出现“原地掉头”的操作。

    双线挑战制作Step3:利用二维数组设定“棋盘”

    • 回顾一下双线挑战最重要的游戏规则:玩家线条不能够触碰到边界、不能触碰对方和自身的线条。

    在这里插入图片描述

    • 大家觉得这种判定方法是不是像在下棋?两个玩家就像顺着小方块的移动方向不停的摆棋子(小方块),当下一个要摆的棋子超出了边界,或者摆在了原来有棋子的格子上时,游戏结束。
    • 二维数组的特点就十分符合我们的需求,比如说我们创建了一个大小为70*70的棋盘chessBoard。(横纵坐标范围均为0-69)
    public static int[][] chessBoard=new int [70][70];
    
    • 我们可以用chessBoard[x][y]=?来表示棋盘上坐标为(x,y)的格子里面装的是什么,这里我们用0代表空1代表这里有棋子。那么chessBoard[8][9]=0就代表坐标(8,9)的格子里没有棋子;chessBoard[7][6]=1就代表(7,6)的格子里已经放有棋子了。(二维数组在创建的时候默认每个位置的值都是0,也就是没有棋子)
    //判断游戏是否结束
    public Boolean judge_gameover(){
    	//判断棋子是否超出边界
    	if(location.x>69|location.y>69|location.x<0|location.y<0){
    		gameOver=true;
    		return true;
    	//判断棋子要放下的位置上原本有没有棋子
    	}else if(chessBoard[location.x][location.y]==1){
    		gameOver=true;
    		return true;
    	//如果上面两种情况都不是,则返回false
    	}else{
    		gameOver=false;
    		return false;
    	}
    }
    
    • 同时,我们需要修改一下drawLB的方法
    public void drawLB(Graphics g){
    	if(imgName==null){
    	//当棋子走到某一格时,将棋盘的这一格状态改为“有棋子”
    		chessBoard[location.x][location.y]=1;
    		g.fillRect(location.x*10+50, location.y*10+50, 10,10);
    	}else{
    	//当棋子走到某一格时,将棋盘的这一格状态改为“有棋子”
    		chessBoard[location.x][location.y]=1;
    		ImageIcon imgic=new ImageIcon(fileAddress+imgName);
    		Image img=imgic.getImage();
    		g.drawImage(img,location.x*10+50, location.y*10+70, null);
    	}
    }
    
    • 最后在run方法中:在这里插入图片描述

    双线挑战制作Step4:给游戏添加背景音乐

    • 首先我们需要创建一个PlayMusic类来载入音乐文件,准备播放
    import java.applet.AudioClip;
    import java.net.MalformedURLException;
    import java.net.URL;
    import javax.swing.JApplet;
     
    public class PlayMusic {
    	public AudioClip music = loadSound("此处输入需要播放的音乐文件路径(文件格式必须为WAV格式)");
    	
    	public static AudioClip loadSound(String filename) {
    		URL url = null;
    		try {
    			url = new URL("file:" + filename);
    		} 
    		catch (MalformedURLException e) {;}
    		return JApplet.newAudioClip(url);
    	}
    	//音乐播放
    	public void play() {
    		//音乐播放
    		music.play();
    		//循环播放
    		music.loop();
    	}
    }
    
    • 然后在需要播放音乐和音效的地方,插入这一段代码
    PlayMusic p=new PlayMusic();
    p.play();
    
    • 就这么简单!

    游戏源代码及游戏素材链接——提取码:hjzd

    一点点总结心得:

    实现一个程序的步骤——

    1. 我想要实现什么效果?
    2. 为了实现这样的效果我要怎么做?
      (开干!)
    3. 做好的效果和我的预期符合吗?如果不符合我要怎么修改?
    4. 搜集资料,撰写博客,和同学交流,对程序进一步优化

    写在最后:

    java给了我一种前所未有的体验,或者说一种前所未有的快感。只需要敲击键盘,就可以像在广阔的平原,凭空升起一座城堡。

    复杂纷繁的代码,从我的手中获得了意义,获得了生气。在这个世界里,犹如掌握了“生杀大权”,游戏的一切都由我来定义。

    飞机长什么样子,怪物又长什么样子;飞机一次打多少发子弹,怪物吃多少子弹会被杀死;怪物以什么姿态出生,又以什么姿态死去……

    每一个程序的活泼生动,都是用一条条朴实无华的代码堆砌的。手握代码,我们就是这个世界的造物主!

    展开全文
  • 童年经典,用java Swing写的贪食蛇小游戏 ,适合java新手练手用,可以尝试用不同的方式来实现这些游戏。 箭头控制蛇的方向->
  • Java多线程游戏-雷霆战机

    千次阅读 2021-08-28 17:22:47
    Java多线程游戏-雷霆战机 先来张效果图(结尾附上源码地址,欢迎交流) 一、总述 飞机大战大家童年的时候都玩过,前两天学了线程方面的知识后,决定写一个关于多线程雷霆战机的游戏(单机)。这里用到了许多有关...

    Java多线程游戏-雷霆战机

    先来张效果图(结尾附上源码地址,欢迎交流)
    在这里插入图片描述

    一、总述

    飞机大战大家童年的时候都玩过,前两天学了线程方面的知识后,决定写一个关于多线程雷霆战机的游戏(单机)。这里用到了许多有关线程,图片处理,音乐等的技术我刚接触Java半年,做这个游戏磕磕绊绊。花费了很多时间,最后还是有一些瑕疵。素材是从另一位博主那下载的。

    素材下载地址:
    地址

    二、相关技术

    1. 了解什么是线程,进程,多线程,并发线程
    2. 了解线程寿命以及线程的控制
    3. 了解线程安全问题,处理游戏中出现的线程安全问题
    4. 了解Java中的list类以及它的底层实现
    5. 音乐文件的读取与播放
    6. 了解键盘监听
    7. 缓冲区(解决屏幕闪烁问题)

    三、功能介绍及讲解

    (一)功能简介
    1.加载页面
    2.菜单页面
    3.弹窗提示
    4.多重关卡(四关)
    5.多种飞机(八种)
    6.音效
    7.地图动态滚动,爆炸动图
    8.分数达到一定程度飞机升级
    9.计时
    10.暂停\继续游戏(线程控制)
    11.开始新游戏(清空原有数据与加载新数据)
    12.道具栏与道具使用(刷新道具栏,使用道具,四种道具)
    13.关卡boss

    (二)功能讲解
    1.加载页面的制作
    加载页面
    关键点:进度条的使用

    加载界面,无外乎就是个界面嘛,重要的可能就是那个加载条了吧。这个加载条你可以自己做,也可以用Java中的加载条,这里我用的是Java中自带的加载条。

    		JProgressBar bar = new JProgressBar(1, 100);
    		bar.setStringPainted(true); //描绘文字
    		bar.setString("加载飞机和子弹中,请稍候,精彩马上开始");  //设置显示文字
    		bar.setBackground(Color.ORANGE);  //设置背景色
    		bar.setBounds(100, 800, 1000, 100);//设置大小和位置
    		bar.setValue(20); //设置进度条值
    

    2.菜单页面的制作
    在这里插入图片描述
    就是提供一些功能按键以及弹窗实现各功能的有序进行。

    关键点:弹窗、按钮监听、焦点的获取

    弹窗,无外乎也是个界面嘛,要么自己做一个窗口,要么使用Java中自带的弹窗类

    JDialog cs=new JDialog();
    

    弹窗的各项操作与窗体JFrame类是一致的。
    这里要注意的就是因为后面要实现键盘的监听,所以焦点必须要注意他的位置。并注意获取焦点。

    3.多重关卡
    这个没啥好说的,我就只是找了四张地图而已。

    4.多种飞机
    这个比较麻烦,在设计类的时候,可以将各种飞机不同的属性用私有数据来进行初始化。在创建飞机对象的时候将各项数据传进去即可。
    下面是我的飞机类代码;

    英雄机

    public class MyHeroPlane {
    	private int Blood;
    	private int speed;
    	private ImageIcon image;
    	public MyHeroPlane(int blood, int speed, ImageIcon image) {
    		super();
    		Blood = blood;
    		this.speed = speed;
    		this.image = image;
    	}
    	public int getBlood() {
    		return Blood;
    	}
    	public int getSpeed() {
    		return speed;
    	}
    	public ImageIcon getImage() {
    		return image;
    	}
    	public void setBlood(int blood) {
    		Blood = blood;
    	}
    	
    }
    
    

    敌机类

    public class EnemyPlane extends Thread{
    	private int blood;
    	private int x,y;
    	private ImageIcon image;
    	private int speed;
    	private boolean islive;
    	private int sore;
    	private boolean suspend=false;
    	public EnemyPlane(int blood, int x, int y, ImageIcon image,int speed,int sore) {
    		super();
    		this.blood = blood;
    		this.x = x;
    		this.y = y;
    		this.image = image;
    		this.speed=speed;
    		this.islive=true;
    		this.sore=sore;
    	}
    	public void run()
    	{
    		while(blood>=0&&islive)
    		{
    			while(suspend)
    			{
    				//暂停功能
    				try {
    					Thread.sleep(1000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			y+=speed;
    		}
    	}
    	public int getBlood() {
    		return blood;
    	}
    	public int getX() {
    		return x;
    	}
    	public int getY() {
    		return y;
    	}
    	public ImageIcon getImage() {
    		return image;
    	}
    	public int getSpeed() {
    		return speed;
    	}
    	public boolean isIslive() {
    		return islive;
    	}
    	public void setIslive(boolean islive) {
    		this.islive = islive;
    	}
    	public void setBlood(int blood) {
    		this.blood = blood;
    	}
    	public int getSore() {
    		return sore;
    	}
    	public boolean isSuspend() {
    		return suspend;
    	}
    	public void setSuspend(boolean suspend) {
    		this.suspend = suspend;
    	}
    	
    }
    

    子弹类(我方,敌方子弹)
    我方子弹类

    public class MyHeroBullets extends Thread{
    	private boolean islive=true;
    	private int x;
    	private int y;
    	private int atk;
    	private ImageIcon image;
    	private int speed;
    	private boolean suspend=false;
    	public MyHeroBullets(int x, int y, int atk, ImageIcon image, int speed) {
    		super();
    		this.x = x;
    		this.y = y;
    		this.atk = atk;
    		this.image = image;
    		this.speed = speed;
    	}
    
    	public void run() {
    		while(islive)
    		{
    			while(suspend)
    			{
    				//暂停功能
    				try {
    					Thread.sleep(1000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			try {
    				Thread.sleep(50);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			y-=speed;
    		}
    	}
    
    	public boolean isIslive() {
    		return islive;
    	}
    
    	public int getX() {
    		return x;
    	}
    
    	public int getY() {
    		return y;
    	}
    
    	public int getAtk() {
    		return atk;
    	}
    
    	public ImageIcon getImage() {
    		return image;
    	}
    
    	public int getSpeed() {
    		return speed;
    	}
    
    	public void setIslive(boolean islive) {
    		this.islive = islive;
    	}
    
    	public boolean isSuspend() {
    		return suspend;
    	}
    
    	public void setSuspend(boolean suspend) {
    		this.suspend = suspend;
    	}
    	
    }
    

    敌方子弹类

    public class EnemyBullets extends Thread{
    	private int atk;
    	private ImageIcon image;
    	private int speed;
    	private boolean islive=true;
    	private int x,y;
    	private boolean suspend=false;
    	public EnemyBullets(int atk, ImageIcon image, int speed, int x, int y) {
    		super();
    		this.atk = atk;
    		this.image = image;
    		this.speed = speed;
    		this.x = x;
    		this.y = y;
    	}
    	public void run() {
    		while(islive)
    		{
    			while(suspend)
    			{
    				//暂停功能
    				try {
    					Thread.sleep(1000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			try {
    				Thread.sleep(50);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			y+=speed;
    		}
    	}
    	public boolean isIslive() {
    		return islive;
    	}
    	public void setIslive(boolean islive) {
    		this.islive = islive;
    	}
    	public int getAtk() {
    		return atk;
    	}
    	public ImageIcon getImage() {
    		return image;
    	}
    	public int getSpeed() {
    		return speed;
    	}
    	public int getX() {
    		return x;
    	}
    	public int getY() {
    		return y;
    	}
    	public boolean isSuspend() {
    		return suspend;
    	}
    	public void setSuspend(boolean suspend) {
    		this.suspend = suspend;
    	}
    	
    }
    
    

    这里我没有将子弹与飞机封装在一起,这个不太方便,各位可以采用封装在一起的方法来实现。

    5.音效
    话不多说,读取文件后播放,直接上代码。

    public class GetMusic extends Thread{
    	private String path;
    	private boolean flag=true;
    	private boolean whileplay;
    	private double value ;
    
    	public GetMusic(String path,boolean whileplay,double value) {
    		super();
    		this.path = path;
    		this.whileplay=whileplay;
    		this.value=value;
    	}
    	public void run()
    	{
    		if(whileplay)
    		{
    			while(flag)
    			{
    				playMusic();
    			}
    		}
    		else
    			playMusic();
    	}
    	public void playMusic() {// 背景音乐播放
    		 
    		try {
    			AudioInputStream ais = AudioSystem.getAudioInputStream(new File("music\\"+path));
    			AudioFormat aif = ais.getFormat();
    			final SourceDataLine sdl;
    			DataLine.Info info = new DataLine.Info(SourceDataLine.class, aif);
    			sdl = (SourceDataLine) AudioSystem.getLine(info);
    			sdl.open(aif);
    			sdl.start();
    			FloatControl fc = (FloatControl) sdl.getControl(FloatControl.Type.MASTER_GAIN);
    			// value可以用来设置音量,从0-2.0
    			double value = 2;
    			float dB = (float) (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0);
    			fc.setValue(dB);
    			int nByte = 0;
    			int writeByte = 0;
    			final int SIZE = 1024 * 64;
    			byte[] buffer = new byte[SIZE];
    			while (nByte != -1) {
    				if(flag) {
    					sdl.write(buffer, 0, nByte);
    					nByte = ais.read(buffer, 0, SIZE);
    				}else {
    					
    					nByte = ais.read(buffer, 0, 0);
    					
    				}
    			}
    			sdl.stop();
     
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    	public boolean isFlag() {
    		return flag;
    	}
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    }
    
    

    6.飞机升级
    这个挺好实现的,你在产生子弹的线程中多产生两路子弹就可以了。当然如果你有什么更好的想法,欢迎交流。
    效果图:(达到4000分升级)
    在这里插入图片描述
    7.暂停\重新开始
    我在这里用的是在线程run方法中加入死循环,进入暂停时,所有线程进入死循环。当然这样也有缺点,就是对电脑资源占用较高。也可以采用对线程进行休眠与唤醒来实现,这种方法在我的程序中较为麻烦,就没采用。

    8.开始新游戏
    这个你可以用重新创建对象来实现,但是如果你之前创建对象使用的是static,那么,你就需要去手动清空所有数据。很不巧,我就是部分对象创建的是static,所以会稍微麻烦一些。

    9.道具的使用
    为了增加游戏的可玩性,你可以增加一些道具,例如,加血道具,护盾道具,僚机,强化子弹。。。。。
    这个你可以自己设计。你可以通过我的源码看懂怎么设计的就不赘述了。上两张效果图。

    护盾
    在这里插入图片描述

    僚机在这里插入图片描述
    强化子弹
    在这里插入图片描述
    10.关卡boss
    这个和设计敌机一样,只是吧,你可把boss血量调高一些,发射子弹的方式调一下。
    在这里插入图片描述
    (三)关键算法
    碰撞判断
    先看一张图
    在这里插入图片描述
    碰撞无外乎就是上面四种情况,所以,我们可以有很多种方法去实现它,你可以通过判断两张图片中心点之间的距离,你也可以通过遍历我方飞机的四个点,是否有一个点在敌方飞机或子弹的范围内即可。

    四、我这个项目明显的缺点

    1. 所有我方子弹,敌方飞机,敌方子弹都是一个新线程,游戏进行中同时运行了成百上千的线程。造成电脑卡顿,资源浪费。解决方法,移动的线程不必每次创建子弹和飞机时都启动一个线程,用一个移动线程就能控制。
    2. 图片因为是四四方方的一张图片,有一部分留白,所以在写碰撞函数时无法兼顾所有图片,存在一定视觉上的误差(明明打中了却穿过去了,或者明明看上去没打中,但实际上却打中了)
    3. 游戏难度设置不合理(啊哈哈哈哈,因为我没有考虑玩家感受,在打boss的时候我试了一下,基本很难过关)。
    4. 游戏程序稳定性不好,遇到部分不容易出现的bug 会卡死。
    5. 存在一定闪烁问题(我加了缓冲功能,但是因为程序占用了太多资源,执行起来还是有一定的闪烁)。

    最后的最后,附上我源代码的链接:
    源码

    在这里插入图片描述

    展开全文
  • 基于Java多线程和GUI的贪吃蛇,本人自己设计的全新界面,带游戏音效,带答辩PPT,保证能用,仅需要2积分,算是对博主的知识成果打赏吧
  • Java多线程赛马游戏

    热门讨论 2013-07-01 19:37:21
    自己用java写的一个赛马的小游戏,用到了多线程java swing ,分享给大家!
  • 这是一款经典小游戏,大鱼吃小鱼,玩家操控小鱼从小吃到大,直面Boss的挑战,全方面的代码解析,适合新手第一款的项目实战, 游戏窗口类完整代码: package game; import java.awt.Color; import java.awt.Font; ...
  • java代码-使用java制作雷霆战机小游戏的的源代码 ——学习参考资料:仅用于个人学习使用!
  • java 多线程 打字游戏

    2014-05-20 15:55:29
    java写的打字游戏。使用了次按成,awt和swing,代码注释详细,适合初学者。
  • Java多线程弹球

    2015-06-04 22:59:40
    Java做的弹球图形界面小游戏使用awt.*可以创建个小球.
  • 在本篇内容中编给大家分享的是关于java虚拟机中多线程的知识点总结内容,需要的朋友们参考学习下。
  • Java多线程飞机游戏

    2012-06-15 17:31:26
    采用Java多线程技术实现简单的飞机格斗游戏
  • 一文搞懂Java多线程底层逻辑,再也不怕多线程

    多人点赞 热门讨论 2021-09-19 21:44:41
    一文搞懂Java多线程底层逻辑,再也不怕多线程
  • java多线程(详)

    千次阅读 多人点赞 2022-03-11 22:54:13
    什么叫线程? 那我们要先了解什么叫进程,线程依赖于进程而存在的。...多线程:一个进程如果只有多条执行路径就是多线程程序 扫雷程序:点击第一下时间开始计时,时间计时的同时可以玩扫雷游戏 ......
  • swing+Java+多线程

    2022-05-02 16:03:25
    用户能通过GUI组件指定生成小球的...鼠标在界面中显示为方块状,玩家需来回移动鼠标以避开运动的小球及屏幕四周,如果鼠标碰到任一小球或者窗口四周,则游戏结束。 程序需提供计时功能,并最终显示玩家能坚持多少秒。
  • Java多线程超详细教程(1)

    千次阅读 2021-02-28 14:03:17
    java中的多线程是一个同时执行多个线程的过程。线程基本上是一个轻量级的子进程,是最小的处理单元。...Java多线程主要用于游戏,动画等Java多线程的优点1)它不会阻止用户,因为线程是独立的,您可以同时执行多个操...
  • Java多线程(超详细!)

    万次阅读 多人点赞 2021-05-12 17:00:59
    注意:一个进程可以启动线程。 eg.对于java程序来说,当在DOS命令窗口中输入: java HelloWorld 回车之后。 会先启动JVM,而JVM就是一个进程。 JVM再启动一个主线程调用main方法。 同时再启动一个垃圾回收线程...
  • 我的多线程小游戏

    2019-08-07 01:04:12
    NULL 博文链接:https://982677720.iteye.com/blog/2096759
  • 游戏Java Swing 的基础上,利用Java 的图形化界面和图形图像绘制来实现游戏界面、车辆、积分的绘制,使用键盘监听来实现玩家操作监听,使用多线程技术实现对向车不断开来,使用JDBC来存储玩家积分和实现排行榜...
  • Java多线程游戏实例分享-雷火 整体框架:switch结构,页面切换 动图实现 背景音乐、音效实现(音量调节) 判断碰撞的更新 开始界面 动画效果 游戏界面 背景移动 技能冷却效果 技能实现 Boss模式 boss发射子弹 功能...
  • java的演示多线程小游戏 经典java的演示多线程小游戏 经典
  • Java小游戏(可运行)

    2022-01-13 20:53:32
    游戏Java Swing 的基础上,利用Java 的图形化界面和图形图像绘制来实现游戏界面、车辆、积分的绘制,使用键盘监听来实现玩家操作监听,使用多线程技术实现对向车不断开来,使用JDBC来存储玩家积分和实现排行榜...
  • Java多线程精讲上

    2020-08-17 19:47:52
    通过本门课程的学习你可以深刻理解Java多线程的原理及实现运行机制,深刻学习多线程的生命周期、调度、控制等内容。 本门课程(多线程精讲上)涵盖内容如下:线程概述l  多线程的实现方案(2种)l  线程的...
  • JAVA写得弹球,多线程。随机出现,随机选择颜色,遇到墙壁反弹。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,509
精华内容 21,803
关键字:

java多线程小游戏