javaswing

Java Swing Issue (While true loop having a burst effect)


Main.java:

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.security.Key;
import java.util.HashSet;
import java.util.Set;

class GameLoop implements Runnable
{
    public boolean running;

    public GameLoop() {
        running = true;
    }

    @Override
    public void run() {
        int counter = 1;
        while (running) {
            // Task that runs in the separate thread
            System.out.println("Separate thread counter: " + counter);
            counter++;

            try {
                Thread.sleep(1);  // Sleep for 1 second
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void stopThread() {
        running = false;
    }
}

public class Main extends JFrame implements KeyListener // H: 23, W: 24
{
    private int backgroundTileSize = 24;
    private int backgroundTileSpace = 1;
    private int backgroundTileCount = 26;
    private int objectTileSize = 18;
    private int objectTileSpace = 1;
    private int WIDTH = backgroundTileSize * backgroundTileCount - (int) (backgroundTileSize * 1.65);
    private int HEIGHT = backgroundTileSize * backgroundTileCount - (int) (backgroundTileSize * 1.65);
    Background background;
    Player player;
    Objects objects;
    JLayeredPane layeredPane;
    private KeyEvent key = null;
    private final Set<Integer> keysPressed = new HashSet<>();
    private boolean keyPressed = false;
    private final int targetFPS = 60;
    private final int frameTime = 1000 / targetFPS;

    /*
            objects = new Objects(objectTileSize, objectTileSpace, HEIGHT, WIDTH);
        player = new Player(300, 300, "assets/kenney_pixel-platformer/Tiles/Characters/tile_0000.png");
     */
    public Main() throws IOException {
        super("Game");


        setSize(WIDTH, HEIGHT);
        setResizable(false);

        addKeyListener(this);
        setFocusable(true);
        requestFocusInWindow();


        layeredPane = new JLayeredPane();
        layeredPane.setBounds(0, 0, WIDTH, HEIGHT);

        //layeredPane.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        //layeredPane.setLayout(null);

        background = new Background(backgroundTileSize, backgroundTileSpace, WIDTH, HEIGHT);
        objects = new Objects(objectTileSize, objectTileSpace, WIDTH, HEIGHT);
        player = new Player(400, 400, "assets/kenney_pixel-platformer/Tiles/Characters/tile_0000.png", WIDTH, HEIGHT);

        System.out.println(WIDTH + ", " + HEIGHT);

        layeredPane.add(background, Integer.valueOf(0));
        layeredPane.add(objects, Integer.valueOf(1));
        layeredPane.add(player, Integer.valueOf(2));

        getContentPane().add(layeredPane);
        layeredPane.repaint();
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //startGameLoop();
        GameLoop gameLoop = new GameLoop();
        Thread thread = new Thread(gameLoop);
        thread.start();
    }

    private void startGameLoop() {
        new Thread(() -> {
            while (true) {
                long startTime = System.currentTimeMillis();

                System.out.println(1);
                handleMovement();
                layeredPane.repaint();

                long elapsedTime = System.currentTimeMillis() - startTime;
                long sleepTime = frameTime - elapsedTime;
                if (sleepTime < 0) sleepTime = 0;

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

    private void handleMovement() {
        if (!keysPressed.isEmpty()) {
            if (keysPressed.contains(KeyEvent.VK_W)) {
                player.move(0, -1);
            }
            if (keysPressed.contains(KeyEvent.VK_A)) {
                player.move(-1, 0);
            }
            if (keysPressed.contains(KeyEvent.VK_D)) {
                player.move(1, 0);
            }
        }
    }


    public static void main(String[] args) throws IOException {
        Main main = new Main();
    }

    @Override
    public void keyTyped(KeyEvent keyEvent) {

    }

    @Override
    public void keyPressed(KeyEvent keyEvent) {
        keysPressed.add(keyEvent.getKeyCode());
    }

    @Override
    public void keyReleased(KeyEvent keyEvent) {
        keysPressed.remove(keyEvent.getKeyCode());
    }
}

Background.java:

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.Canvas;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.awt.event.KeyEvent;
import java.util.*;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;


public class Background extends JPanel {
    private int backgroundTileSize;
    private int backgroundTileSpace;
    private int HEIGHT;
    private int WIDTH;
    private String level1Path = "levels/level1.txt";
    private int[][] level1 = new int[24][24];
    private Graphics window_;
    private Objects objects;
    private Player player;


    public Background(int bgTileSize, int bgTileSpace, int h, int w) throws IOException {
        setOpaque(true);

        backgroundTileSize = bgTileSize;
        backgroundTileSpace = bgTileSpace;
        HEIGHT = h;
        WIDTH = w;


        try (Scanner scan = new Scanner(new File(level1Path))) {
            int i = 0;
            int j = 0;
            while (scan.hasNext()) {
                if (i >= 24) {
                    i = 0;
                    j++;
                }
                level1[j][i] = Integer.parseInt(scan.next());
                i++;
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }


        setBackground(Color.WHITE);
        setBounds(0, 0, WIDTH, HEIGHT);
    }

    @Override
    public void paintComponent(Graphics window) {
        super.paintComponent(window);
        drawLevel1(window);
        window_ = window;

        /*
        try {
            objects.draw(window);
            player.draw(window);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

         */
    }


    public void drawLevel1(Graphics window) {
        for (int i = 0; i < level1.length; i++) {
            for (int j = 0; j < level1[i].length; j++) {
                int[] pos = {j * 24, i * 23};


                if (level1[i][j] == 0)
                    drawTile(window, "assets/kenney_pixel-platformer/Tilemap/tilemap-backgrounds.png", 1, 1, pos);


                if (level1[i][j] == 1)
                    drawTile(window, "assets/kenney_pixel-platformer/Tilemap/tilemap-backgrounds.png", 3, 1, pos);
            }
        }
    }


    public void drawTile(Graphics window, String tilemap, int row, int col, int[] pos) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(new File(tilemap));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


        int x = (backgroundTileSize * (col - backgroundTileSpace)) + (col - backgroundTileSpace);
        int y = (backgroundTileSize * (row - backgroundTileSpace)) + (row - backgroundTileSpace);
        BufferedImage croppedImge = image.getSubimage(x, y, backgroundTileSize, backgroundTileSize);
        window.drawImage(croppedImge, pos[0], pos[1], null);
    }
}

Objects.java:

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.Canvas;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;


public class Objects extends JPanel {
    private int objectTileSize;
    private int objectTileSpace;
    private int HEIGHT;
    private int WIDTH;
    private String level1Path = "levels/level1Objects.txt";
    private int[][] level1 = new int[32][32];
    ArrayList<int[]> levers;


    public ArrayList<int[]> getLevers() {
        return levers;
    }
    public Objects(int objTileSize, int objTileSpace, int h, int w) {
        setOpaque(false);

        levers = new ArrayList<>();

        objectTileSize = objTileSize;
        objectTileSpace = objTileSpace;
        HEIGHT = h;
        WIDTH = w;

        try (Scanner scan = new Scanner(new File(level1Path))) {
            int i = 0;
            int j = 0;
            while (scan.hasNext()) {
                if (i>=32) {
                    i = 0;
                    j++;
                }
                level1[j][i] = Integer.parseInt(scan.next());
                i++;
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }

        setBackground(Color.WHITE);
        setBounds(0, 0, WIDTH, HEIGHT);
    }

    @Override
    public void paintComponent(Graphics window) {
        super.paintComponent(window);
        try {
            drawLevel1(window);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void drawLevel1(Graphics window) throws IOException {
        for (int i = 0; i < level1.length; i++) {
            for (int j = 0; j < level1[i].length; j++) {
                int[] pos = {j * objectTileSize, (i * objectTileSize) - objectTileSize + 6};


                if (level1[i][j] == 1) {
                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0021.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }


                if (level1[i][j] == 2) {
                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0023.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }


                if (level1[i][j] == 3) {
                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0022.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }


                if (level1[i][j] == 4) {
                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0020.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }


                if (level1[i][j] == 5) {
                    if (!levers.isEmpty()) {
                        for (int[] lever : levers) {
                            if (lever[0] != pos[0] && lever[1] != pos[1]) {
                                int[] lever_ = {pos[0], pos[1], -1};
                                levers.add(lever_);
                            }
                        }
                    } else {
                        int[] lever = {pos[0], pos[1], -1};
                        levers.add(lever);
                    }


                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0064.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }


                if (level1[i][j] == 6) {
                    if (!levers.isEmpty()) {
                        for (int[] lever : levers) {
                            if (lever[0] != pos[0] && lever[1] != pos[1]) {
                                int[] lever_ = {pos[0], pos[1], 1};
                                levers.add(lever_);
                            }
                        }
                    } else {
                        int[] lever = {pos[0], pos[1], 1};
                        levers.add(lever);
                    }


                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0066.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }


                if (level1[i][j] == 7) {
                    Image img = ImageIO.read(new File("assets/kenney_pixel-platformer/Tiles/tile_0053.png"));
                    window.drawImage(img, pos[0], pos[1], null);
                }
            }
        }
    }


    public void flipLever(int px, int py) { // player pos will be centered x and at the bottom of the img
        for (int[] lever : levers) {
            if ( (px >= lever[0] && px <= lever[0] + objectTileSize) && (py >= lever[1] + objectTileSize && py <= lever[1]) ) {
                System.out.println(1);
            }
        }
    }
}

Player.java:

import java.awt.*;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;

class PlayerImg extends Canvas {
    private BufferedImage img;
    private int x, y;

    public PlayerImg(int x_, int y_, String path) throws IOException {
        try {
            img = ImageIO.read(new File(path)); // Update the path
        } catch (IOException e) {
            e.printStackTrace();
        }

        this.x = x_;
        this.y = y_;
    }

    public void render(Graphics window) {
        window.drawImage(img, x, y, null);
    }

    public void move(int x_, int y_) {
        x += x_;
        y += y_;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}

public class Player extends JPanel {
    PlayerImg playerImg;
    private int WIDTH, HEIGHT;

    public Player(int x, int y, String path, int w, int h) throws IOException {
        setOpaque(false);

        playerImg = new PlayerImg(x, y, path);

        WIDTH = w;
        HEIGHT = h;

        setBackground(Color.WHITE);
        setBounds(0, 0, WIDTH, HEIGHT);
    }

    @Override
    public void paintComponent(Graphics window) {
        super.paintComponent(window);
        playerImg.render(window);
    }

    public void move(int x, int y) {
        playerImg.move(x, y);
        repaint();
    }
}

I make a seperate thread, yet my code looks to be affected by my other code. That is a theory I have, but I really just don't know. I print 1's in a while true loop in my thread, and it prints it in a burst pattern. Oh, and I need this for player movement.

Example Output:

1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

----> .1 Second delay

1
1
1
1
1
1
1
1
1
1
1
1
1
1

----> .1 Second delay

1
1
1
1
1
1
1
1
1
1
1
1
1
1

I make this inside the main class and I tried using Booleans. For Booleans, I just hopped it would use its toggle effect to fix it, but that was before I learned the problem was just in the thread itself.


Solution

  • I think you're running into a classic Swing threading issue. The "burst effect" you're seeing is likely related to how threads are scheduled and prioritized by the JVM. Here are a few suggestions:

    Your GameLoop thread is using Thread.sleep(1) which is too small - the OS can't reliably honor 1ms sleeps. Try increasing this to at least 10-16ms for smoother timing. You're creating the GameLoop but not actually using it for game updates - it's just printing numbers. Consider hooking this up to your actual game state updates. Make sure all Swing UI updates happen on the Event Dispatch Thread (EDT) using SwingUtilities.invokeLater(). Look into using a proper game loop pattern like "fixed timestep" that can handle variations in execution time. Since this is for player movement, consider using javax.swing.Timer instead of raw threads - it's designed to work with Swing and runs on the EDT.

    I had a similar issue in a Swing project and found that implementing proper delta timing and being careful about which thread handles UI updates fixed the stuttering/burst effect.