制作一个线程小游戏

注:本内容会随时更新,从基础到复杂,直到处理完善。

在前面讲述了进程,线程,并发,并行的概念,以及线程如何启动,现在开始制作一个线程小游戏。

目前完成的功能有:

       面板创建

       一个线程控制多个小球

       小球的运动

       小球不会运动到界面以外

       小球之间的相互碰撞

       左键控制添加小球,右键控制小球暂停

       不会出现闪屏

       监听键盘,A,D键控制左右移动;空格键控制游戏开始

       添加背景图片并保证循环显示

        创建一个ThreadUI类,用于创建窗体添加面板以及运行程序。因为是在窗体添加的面板上体现动态,所以要在面板上获取画笔,再添加鼠标点击监听器和键盘监听器,在使用键盘监听器时,需要获取焦点requestFocus()才可以使用。


import javax.swing.*;
import java.awt.*;

/**
 * 线程游戏
 */

public class ThreadUI {

    //使用静态变量(类变量)
    static JPanel jPanel = new JPanel();
    public void initUI()
    {
        JFrame jf = new JFrame();
        jf.setSize(900,900);
        jf.setTitle("线程游戏");
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setLocationRelativeTo( null);

        //面板对象
        jPanel.setBackground(Color.WHITE);
        jf.add(jPanel,BorderLayout.CENTER);

        jf.setVisible(true);

        //获取面板上的画笔对象
        Graphics g = jPanel.getGraphics();

        //监听器
        ThreadListener listener = new ThreadListener(g);
        jPanel.addMouseListener(listener);

        //添加键盘监听器
        jPanel.addKeyListener(listener);
        //让事件源得到焦点,获取焦点
        jPanel.requestFocus();
    }

    public static void main(String[] args) {
        ThreadUI ui = new ThreadUI();
        ui.initUI();
    }
}

创建一个监听器类ThreadListener类,用于监听事件。extends MouseAdapter比implements MouseListener更优化的点在于,extends MouseAdapter不需要重写MouseListener中的所有代码,可以简化程序。将画笔引用传递到监听器中,在点击类中定义坐标位置x,y。然后创建线程对象tb,通过tb.start()启动线程,(每个线程只能启动一次)。判断线程对象为空时才能创建线程对象,线程对象最后通过鼠标点击监听器的左键来控制小球的更新创建,右键控制小球暂停运动。在键盘监听器中键盘按压方法里面获取按键,使用case语句判断键盘的内容,A键左移,D键右移,空格键控制线程的开始,在键盘释放方法里面设置速度为0,这样就可以确保按压时的一动一定。


import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

/**
 * 鼠标监听器-时间处理类
 */

public class ThreadListener extends MouseAdapter implements KeyListener, ActionListener {

    //画笔 引用传递
    public Graphics g;
    //动作数组
    public ArrayList<Ball> balls = new ArrayList<>();
    //线程对象
    public ThreadBall tb;
    //玩家对象
    public MRect mr;
    public int speedX = 5;

    //构造方法初始化属性
    public ThreadListener(Graphics g)
    {
        this.g = g;
    }
    @Override
    public void mouseClicked(MouseEvent e)
    {
        //获取坐标
        int x = e.getX();
        int y = e.getY();

        //判断鼠标左键,右键问题
        if (e.getButton() ==1)
        {
            //创建Ball对象
            Ball ball = new Ball(x, y, balls);
            balls.add(ball);
        } else if (e.getButton() == 3) {//右键
            for (int i = 0; i < balls.size(); i++)
            {
                Ball ball = balls.get(i);
                ball.start = !ball.start;
            }

        }

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        ThreadUI.jPanel.requestFocus();
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        switch (key)
        {
            case KeyEvent.VK_A:
                mr.spaceX = -speedX;
                break;
            case KeyEvent.VK_D:
                mr.spaceX = speedX;
                break;
            case KeyEvent.VK_SPACE://开始游戏
                //线程对象为空
                //在启动线程之前,创建玩家对象
                if (tb == null)
                {
                    //创建玩家对象
                    mr = new MRect(400,700);

                    //创建线程对象
                    tb = new ThreadBall(g, balls,mr);
                    //启动线程,每个线程只能启动一次
                    new Thread(tb).start();
                }
                break;
        }

    }

    @Override
    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        switch (key) {
            case KeyEvent.VK_A:
                mr.spaceX = 0;
                break;
            case KeyEvent.VK_D:
                mr.spaceX = 0;
                break;
        }

    }
}

创建一个ThreadBall类,这是一个自定义线程类,首先将画笔,坐标引入,再调用run()方法,通过sleep()方法控制小球运动的速度,设置黑球运动。run()方法是由主线程来执行,不能直接调用run()方法,必须先调用start()方法,因为run方法不会开辟新的栈空间。

        出现闪屏的原因是每次按顺序添加小球之后,白色添加一次,黑色添加一次,就会出现一闪一闪的现象,解决办法是在run方法里创建一个缓冲图片,在缓冲图片上面画画就要在上面添加画笔buffG,通过画笔绘制整个游戏背景,绘制完之后再将其添加到面板中显示界面。

         在绘制游戏背景时,也需要将图片插入缓冲图片中去,在线程run方法中的死循环中添加两张图片,将两张图片拼接起来,这样可以实现当上面一张图片恰好满屏时,将下面的图片y轴设置为0,可以循环利用两张图片,避免图片的“不够用”。


import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

public class ThreadBall implements Runnable {
    public Graphics g;
    public ArrayList<Ball> balls;
    public MRect mr;
    public int x,y,w,h;
    //加载背景图片
    public Image image;


    //初始化属性
    public ThreadBall(Graphics g, ArrayList<Ball> balls, MRect mr)
    {
        this.g = g;
        this.balls = balls;
        this.mr = mr;

        image = new ImageIcon("C:\\Users\\bkf\\IdeaProjects\\SummerTest\\.idea\\img_1.png").getImage();
    }

    //run方法是线程启动后自动执行的方法
    //该方法执行完,当前线程的所有资源被回收(不能再次启动)
    public void run()
    {
        System.out.println(Thread.currentThread().getName()+"线程启动");

        //创建缓冲图片,避免出现闪屏
        BufferedImage bufferedImage = new BufferedImage(ThreadUI.jPanel.getWidth(),
                ThreadUI.jPanel.getHeight(),BufferedImage.TYPE_INT_RGB);
        //获取缓冲图片的画笔
        Graphics buffG = bufferedImage.getGraphics();
        w = bufferedImage.getWidth();
        h = bufferedImage.getHeight();

        //循环绘制
        while(true)
        {
            //延时
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //绘制整个游戏背景
            buffG.drawImage(image,x,y-h,w,h,null);
            buffG.drawImage(image,x,y++,w,h,null);
            if (y - h >= 0){
                y = 0;
            }


            mr.drawRect(buffG);

            //取出所有的小球对象
            for (int i = 0; i < balls.size(); i++)
            {
                Ball ball = balls.get(i);
                ball.drawBall(buffG);
                ball.collision();
            }

            //把缓冲图片显示在界面
            g.drawImage(bufferedImage,0,0,null);
        }
    }
}

创建一个Ball类,如果要实现一个线程管理所有小球,那么就要定义一个小球类,将小球的所有属性定义出来,在构造方法中定义小球基础属性,这样就可以在创建类时定义出小球,x,y表示小球坐标,speedX,speedY表示小球分别在x轴,y轴的速度,size表示小球的大小。

在小球中创建一个collision类用于解决小球碰撞问题。首先需要获取两个小球对象,通过计算这两个小球的距离与刚好碰撞时的距离相比较(也就是小球的直径)来判断小球是否会发生碰撞,如果碰撞之后则交换他们的速度。

在定义的属性中有一个布尔类型的start,它用来控制小球暂停,还需要每次点击的时候将start取反,内容在ThreadListener中:ball.start = !ball.start;。


import java.awt.*;
import java.util.ArrayList;

public class Ball {

    //属性
    public int x, y;
    public int speedX, speedY;
    public int size;
    public ArrayList<Ball> balls;
    public boolean start = true;

    public Ball(int x, int y, ArrayList<Ball> balls)
    {
        this.x = x;
        this.y = y;
        this.balls = balls;


        this.size = 50;
        this.speedX = 3;
        this.speedY = 3;
    }

    //画小球
    public void drawBall(Graphics g)
    {
        g.setColor(Color.BLACK );
        g.fillOval(x,y,size,size);

        //控制移动
        if ( start)
        {
            x += speedX;
            y += speedY;
        }


        //判断是否需要反弹
        if (x > ThreadUI.jPanel.getWidth() -  size || x < 0)
        {
            speedX = -speedX;
        }
        if (y > ThreadUI.jPanel.getHeight() - size || y < 0)
        {
            speedY = -speedY;
        }
    }

    //小球碰撞
    public void collision()
    {
        //遍历所有小球对象
        for (int i = 0;i<balls.size();i++)
        {
            Ball ball = balls.get(i);
            int w =this.x - ball.x;
            int h = this.y - ball.y;
            double len = Math.sqrt(w * w + h * h);
            if (len < size)
            {
                int tx = this.speedX;
                this.speedX = ball.speedX;
                ball.speedX = tx;
                int ty = this.speedY;
                this.speedY = ball.speedY;
                ball.speedY = ty;
            }
        }
    }

}

创建一个MRect类:和创建小球方法一样,通过画笔绘制,本类先初步绘制好玩家移动体验界面。


import java.awt.*;

public class MRect {
    public int x,y,size;
    public int spaceX,spaceY;


    public MRect(int x, int y)
    {
        this.x = x;
        this.y = y;
        size = 100;
    }

    public void drawRect(Graphics g)
    {
        g.setColor(Color.RED);
        g.fillRect(x, y, size, size);
        move();
    }

    public void move()
    {
        x += spaceX;
        y += spaceY;
    }
}

运行界面如下:运行效果是可以运动的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值