How to reproduce Matrix digital rain freezing symbols effect?

Viewed 292

I'm trying to make a matrix rain app. I want different arrays to move at different speeds, but I can't solve the effect of freezing symbols (when a symbol from the array stays at its previous Y coordinate while the array continues to move down). When the speed of the array doesn't match the font size it starts to twitch while moving. My headache lines are these two

int charIndex = Math.abs((i - s.posY) % s.text.length);
g2d.drawString(s.text[charIndex], s.posX, s.posY - (i * FONT_SIZE));

I know I need to compensate for font-size and speed offset but I can't figure it out

public class TextDrops extends JPanel
{
    private static final int SCREEN_WIDTH = 1400;
    private static final int SCREEN_HEIGHT = 700;
    private static final int FONT_SIZE = 14;
    private static final int DELAY_MILLIS = 30;
    private static final int SPEED_MIN = 5; // 5
    private static final int SPEED_MAX = 20; // 20

    private List<Streamer> listStreamers;

    public TextDrops() {
        listStreamers = new ArrayList<>();
        populateList();
    }

    public void populateList() {
        int maxStreamers = 200;

        for (int i = 0; i < maxStreamers; i++) {
            Streamer s = new Streamer();
            listStreamers.add(prepareStreamer(s));
        }
    }

    public Streamer prepareStreamer(Streamer s) {
        // pick random x-coord for a rain drop
        s.posX = new Random().nextInt(SCREEN_WIDTH / FONT_SIZE) * FONT_SIZE;
        s.posY = 0;
        s.speed = new Random().nextInt(SPEED_MAX) + SPEED_MIN;

        s.text = new String[] {
            "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z"
        };

        return s;
    }

    
    @Override
    public void paint(Graphics g) {
        super.paint(g);

        Graphics2D g2d = (Graphics2D) g;

        for (Streamer s : listStreamers) {

            s.posY += s.speed;

            for (int i = 0; i < s.text.length; i++) {
                // trying to emulate the freezing symbols effect here 
                int charIndex = Math.abs((i - s.posY) % s.text.length);

                // draw frame
                g2d.drawString(s.text[charIndex], s.posX, s.posY - (i * FONT_SIZE));
            }

            // reset text drop as soon as its tail reaches the end of the screen
            if (s.posY - (s.text.length * FONT_SIZE) >= SCREEN_HEIGHT) {
                prepareStreamer(s);
            }
        }

        try {
            Thread.sleep(DELAY_MILLIS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        repaint();
    }

    public static class Streamer
    {
        private int posX;
        private int posY;
        private int speed;
        private String[] text;
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new TextDrops());
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}
1 Answers

I guess I solved the problem. Each stream generates a new array of different length every time it reappears at the top and this works better than trying to apply different speeds. So I took speed value away, and used font size to move streams.

enter image description here

Related