Commit f7df798f authored by 刘家荣's avatar 刘家荣 💬
Browse files

feat(BulletAnim)

parent 9c3c4693
Loading
Loading
Loading
Loading
+120 −29
Original line number Diff line number Diff line
package controller;

import model.game.Game;
import io.vavr.Tuple3;
import model.game.Msg;
import view.AppFrame;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.Deque;
import java.util.Random;

public class BulletAnim extends Thread {
    public final int BULLET_FONT_SIZE = 10;
    public final Font BULLET_FONT = new Font("Rockwell", Font.PLAIN, BULLET_FONT_SIZE);
    
    public final double BULLET_SPEED = 3.1;
    
    private int l = 0;  // 最小序号,序号区间左端
    
    private int r = 0; // 最大序号+1,序号区间右端excluded
    
    private Deque<Tuple3<Integer, Long, JLabel>> bulletQueue = new ArrayDeque<>();
    // 每个元组有三个元素:第一是对应msg在msgList中的序号;第二是发出时间;第三是JLabel元素
    
    private Random random = new Random();
    
    @Override
    public void run() {
        super.run();
        while (true) {
            for (Msg msg: Game.instance.getMsgList()){
                drawBullet(msg);
            // 更新bulletQueue,尽可能减少GUI元素销毁或新建的次数
            
            // 1. 首先寻找尾部能加入的新元素,右界右移
            while (r < Msg.msgList.size() && canShowBullet(Msg.msgList.get(r))) r++;
            // 左界右移
            while (l < Msg.msgList.size() && !canShowBullet(Msg.msgList.get(l))) l++;
            // 2. 出界的元素从头部出来后重新从尾部加入
            while (true) {
                Tuple3<Integer, Long, JLabel> head = bulletQueue.getFirst();
                Tuple3<Integer, Long, JLabel> tail = bulletQueue.getLast();
                if (!(head._1 < l && tail._1 + 1 < r)) break;
                bulletQueue.pop();
                bulletQueue.add(new Tuple3<>(tail._1 + 1, Msg.msgList.get(tail._1 + 1).time().getTime(), head._3));
            }
            
            // 3. 销毁左边多余的元组
            while (bulletQueue.getFirst()._1 < l) {
                AppFrame.instance.getContentPane().remove(bulletQueue.getFirst()._3);
                bulletQueue.pop();
            }
            
            // 4. 右边加入新的元组
            while (true) {
                Tuple3<Integer, Long, JLabel> tail = bulletQueue.getLast();
                if (!(tail._1 + 1 < r)) break;
                Tuple3<Integer, Long, JLabel> newTuple = new Tuple3<>(
                    tail._1 + 1,
                    Msg.msgList.get(tail._1 + 1).time().getTime(),
                    newBulletLabel(Msg.msgList.get(tail._1 + 1))
                );
                bulletQueue.add(newTuple);
                AppFrame.instance.getContentPane().add(newTuple._3);
            }
            
            // 迭代每一个元组,画弹幕
            bulletQueue.forEach(this::drawBullet);
            
            try {
                sleep(50);
            } catch (InterruptedException ignored) {
@@ -23,27 +75,66 @@ public class BulletAnim extends Thread{
        }
    }
    
    public void drawBullet(Msg msg){
        //TODO: drawBullet
    /**
     * 创建新的弹幕,需要传入这个弹幕所显示的消息对象
     * @param msg 消息
     */
    private JLabel newBulletLabel(Msg msg){
        JLabel jLabel = new JLabel();
        jLabel.setOpaque(false);
        jLabel.setSize(evalLength(msg), BULLET_FONT_SIZE);
        jLabel.setFont(new Font("Rockwell", Font.BOLD, BULLET_FONT_SIZE));
        jLabel.setLocation(
            AppFrame.instance.getContentPane().getWidth(),
            random.nextInt(AppFrame.instance.getContentPane().getHeight() - BULLET_FONT_SIZE)
        );
        jLabel.setVisible(true);
        return jLabel;
    }
    
    
        JPanel bullet = new JPanel();
        JTextArea bulletText = new JTextArea();
        bullet.add(bulletText);
    private void drawBullet(Tuple3<Integer, Long, JLabel> tuple) {
        Msg msg = Msg.msgList.get(tuple._1);
        tuple._3.setText(msg.content());
        tuple._3.setLocation(evalX(msg), tuple._3.getY());
        tuple._3.repaint();
    }
    
        bullet.setOpaque(false);
        bulletText.setSize(50,20);
        bulletText.setFont(new Font("Rockwell", Font.BOLD, 20));
        bullet.setVisible(true);
        Timer draw = new Timer(100, new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int x = 400;
                bulletText.setText(String.valueOf(msg));
                while (x >= 0) {
                    x -= 20;
                    bullet.setLocation(x, 200);
    /**
     * 判断一个弹幕是否需要显示
     * <br/>
     * <br/>设某个消息的发出时间为T0,当前时间为T,屏幕宽度为w,
     * <br/>那么弹幕在T0时刻的左端x坐标为w;
     * <br/>左端x坐标关于T的函数:x - w = -v * (T - T0)
     * <br/>因此弹幕暴露在屏幕中的条件是: -L &lt; x &lt; w
     */
    public boolean canShowBullet(Msg msg) {
        return -evalLength(msg) < evalX(msg) && evalX(msg) < AppFrame.instance.getContentPane().getWidth();
    }
    
    /**
     * 计算弹幕左端x坐标
     * <br/>
     * <br/>设某个消息的发出时间为T0,当前时间为T,屏幕宽度为w,
     * <br/>那么弹幕在T0时刻的左端x坐标为w;
     * <br/>左端x坐标关于T的函数:x - w = -v * (T - T0)
     * @param msg 消息
     * @return 左端x坐标
     */
    private int evalX(Msg msg){
        long now = new Date().getTime();
        long msgTime = msg.time().getTime();
        return (int)(AppFrame.instance.getContentPane().getWidth() - BULLET_SPEED * (now - msgTime));
    }
        });
    
    /**
     * <br/>计算弹幕的像素长度
     * <br/>
     * <br/>不确定这种方式对不对,早晚有一天会要修改的
     * @param msg 消息
     * @return 长度
     */
    private int evalLength(Msg msg){
        return msg.content().length() * BULLET_FONT_SIZE;   //不确定这种长度计算方式对不对
    }
}