Tartamudeo de animación del juego 2D básico de Java

Entonces, he estado trabajando en un 2d rpg por algún tiempo y parece que no puedo solucionar este problema. Los gráficos parecen "saltar" o tartamudear cada pocos segundos por una razón desconocida. Esto se está volviendo bastante molesto porque no sé qué lo está causando.

Aquí hay un programa muy básico que escribí que solo tiene un cuadrado rojo que se mueve de un lado de la pantalla al otro lado. Incluso en este programa muy básico, el cuadrado todavía tartamudea cada pocas actualizaciones y realmente no puedo entenderlo por mi vida.

public class Main extends JPanel {

int x=0, y=0;

public JFrame window = new JFrame("Window");

public Main(){
    window.setSize(1000, 500);
    window.setVisible(true);
    window.add(this);
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    g.setColor(Color.red);
    g.fillRect(x, y, 500, 500);
    x+=3;
    if(x>900){
        x=0;
    }
}

public void start(){
    while(true){
        repaint();
        try {
            Thread.sleep(16);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args){
    Main game = new Main();

    game.start();
}

}

Si ejecuta la clase, verá cuál es el problema gráficamente. Obviamente mi juego se compone de muchas más clases y es mucho más complejo que esto, pero se aplica el mismo principio. Si alguien tiene alguna idea de mi problema, me encantaría escucharlo. Gracias por adelantado.

Actualizado

Aquí están mis dos clases principales:

Clase principal:
paquete com.ultimatum.Main;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;

import com.ultimatum.Mangers.ImageLoader;
import com.ultimatum.Mangers.KeyStates;
import com.ultimatum.Mangers.ScreenUpdater;
import com.ultimatum.Mangers.UserInput;

@SuppressWarnings("serial")
public class Ultimatum extends JPanel {

    /**
     * @param x This is the start width of the screen and can be adjusted manually, but will not go any lower than this integer
     * @param y This is the start height of the screen and can be adjusted manually, but will not go any lower than this integer
     * @param contentPlaneX This is how much the width of the content plane is (Frame-Borders)
     * @param contentPlaneY This is how much the height of the content plane is (Frame-Borders)
     */
    public int x=850, y=610, contentPlaneX, contentPlaneY, middleX, middleY, tileSize=90;

    public Dimension minimumSize = new Dimension(x, y);

    public JFrame window = new JFrame("Ultimatum");//This makes the JFrame for the game
    public KeyStates keyStates = new KeyStates();
    public UserInput input = new UserInput(keyStates);
    public ImageLoader imageLoader = new ImageLoader();
    public static Ultimatum ultimatum;//Makes the object of this class
    public static ScreenUpdater su;//This is creating the object that is going to be making changes to the screen. For example, the animation.
    private BufferedImage screenImage;

    public boolean isWindowInFullscreenMode;

    private boolean imagesLoaded;

    public void initializeUltimatum(){
        toWindowedMode();

        addMouseListener(input);
        addMouseMotionListener(input);

        contentPlaneX=window.getContentPane().getWidth();
        contentPlaneY=window.getContentPane().getHeight();
        middleX=(int)contentPlaneX/2;
        middleY=(int)contentPlaneY/2;
        su = new ScreenUpdater(ultimatum, keyStates, imageLoader, "Test", tileSize);

        imageLoader.loadImages();
    }

    public void toFullscreenMode(){
        window.dispose();
        window.setUndecorated(true);
        window.setVisible(true);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setBounds(0,0,Toolkit.getDefaultToolkit().getScreenSize().width,Toolkit.getDefaultToolkit().getScreenSize().height);
        addListenersAndClassToWindow();
        isWindowInFullscreenMode=true;
    }

    public void toWindowedMode(){
        window.dispose();
        window.setUndecorated(false);
        window.setSize(x,y);
        window.setMinimumSize(minimumSize);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(true);
        window.setLocationRelativeTo(null);
        addListenersAndClassToWindow();
        isWindowInFullscreenMode=false;
    }

    public void addListenersAndClassToWindow(){
        window.add(ultimatum);//This connects paintComponent and the frame to this class
        window.addKeyListener(input);
    }

    public void paintComponent(Graphics g){
        if(imagesLoaded){
            super.paintComponent(g);
            //su.updateScreen(g);
            g.drawImage(screenImage, 0, 0, contentPlaneX, contentPlaneY, null);
        }else imagesLoaded = true;
    }

    public void update(){
        screenImage = su.updateScreen();
    }

    /**
     * This main class sets up the program. The while loop that keeps the game running is also contained inside this class. Most of this class is easily
     * readable so i'm not going to comment that much.
     */
    public static void main(String[] args){
        ultimatum = new Ultimatum();
        ultimatum.initializeUltimatum();

        final int FPS=60, TARGET_TIME=1000/FPS;

        long start, elapsed, wait;

        while(true){//This loops purpose is to keep the game running smooth on all computers 
            start = System.nanoTime();

            ultimatum.update();
            ultimatum.repaint();//This calls the paintComponent method

            elapsed = System.nanoTime() - start;

            wait = TARGET_TIME-elapsed/1000000;
            if(wait<0) wait = TARGET_TIME;


        try{//Catches the error in case the tries to give an error (which it won't)
            Thread.sleep(wait);//This is how long it waits it till the screen gets repainted
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
}

Actualizador de pantalla:

package com.ultimatum.Mangers;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;

import com.ultimatum.Engine.BuildingGenerator;
import com.ultimatum.Engine.TextBoxGenerator;
import com.ultimatum.Entities.Character.Player;
import com.ultimatum.Gamestates.Buildings.HealingCenter;
import com.ultimatum.Gamestates.Menus.EscapeScreen;
import com.ultimatum.Gamestates.Menus.StartScreen;
import com.ultimatum.Gamestates.Menus.TitleScreen;
import com.ultimatum.Gamestates.Routes.RouteSuperClass;
import com.ultimatum.Gamestates.Towns.TownSuperClass;
import com.ultimatum.Main.Ultimatum;

public class ScreenUpdater {

    public Ultimatum ul;
    public Resizer rs;//This is the object that captures the resize in two integers
    public KeyStates ks;
    public ImageLoader loader;
    public Fader fader;
    public TextBoxGenerator textBox;
    public Initializer initer;
    public TileMap tm;
    public Player p;
    public BuildingGenerator bg;

    //Menus
    public TitleScreen titleScreen;
    public StartScreen startScreen;
    public EscapeScreen escScreen;

    //Towns
    public TownSuperClass towns;

    //Routes
    public RouteSuperClass routes;

    //Buildings
    public HealingCenter healingCenter;

    public final int TITLE_SCREEN=0, START_SCREEN=TITLE_SCREEN+1, LOAD=START_SCREEN+1, TOWN_ONE=LOAD+1, 
            ROUTE_ONE=TOWN_ONE+1, HEALING_CENTER=ROUTE_ONE+1, ESC_MENU=HEALING_CENTER+1;

    public int screenNo = TITLE_SCREEN;

    public int prevScreen=0;
    public boolean prevMenuState, menuState;//These variables are for the checkEsc method
    public boolean isMouseVisible=true, prevIsMouseVisible;//Simple boolean for setting the mouse from visible to invisible and vice versa

    public ScreenUpdater(Ultimatum ultimatum, KeyStates keyStates, ImageLoader imageloader, String location, 
            int tileSize){

        ul = ultimatum;
        ks = keyStates;
        loader = imageloader;
        fader = new Fader(ul, this);
        textBox = new TextBoxGenerator(loader, ks, ul);
        initer = new Initializer(fader, textBox);
        fader.sendIniterData(initer);

        p = new Player(ul, fader, loader, ks, initer, this);
        fader.sendPlayerData(p);

        tm = new TileMap(tileSize, loader, p);
        fader.sendTileMapData(tm);

        rs = new Resizer(ul, p);

        bg = new BuildingGenerator(ul, p, loader, tm);

        //Below are the game states being loaded

        //Menus
        titleScreen = new TitleScreen(ul, this, loader, ks, fader);
        startScreen = new StartScreen(ul, this, fader, loader, ks, textBox);
        escScreen = new EscapeScreen(ul, fader, loader, ks);
        rs.sendEscapeScreenData(escScreen);

        //Towns
        towns = new TownSuperClass(p, fader, bg, tm, this);

        //Routes
        routes = new RouteSuperClass(p, fader, bg, tm, this);

        //Buildings
        healingCenter = new HealingCenter(ul, fader, loader, ks, textBox);
    }

    public void clearScreen(Graphics g){
        g.setColor(Color.black);
        g.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);
    }

    public void checkEsc(Graphics g){
        if(ks.escReleased&&screenNo>LOAD&&!fader.fadingOut&&fader.fadingIn){
            if(screenNo<HEALING_CENTER&&!p.isMoving){


        menuState=true;
            prevScreen=screenNo;
        }
        else if(screenNo==ESC_MENU) menuState=false;
    }

    if(prevMenuState!=menuState){
        int toScreen;
        boolean mouseVisiblity;
        if(menuState){
            toScreen=ESC_MENU;
            mouseVisiblity=true;
        }
        else{
            toScreen=prevScreen;
            mouseVisiblity=false;
        }

        fader.FadeOut(g, 255, toScreen, false, "", 0, 0, false, 0, mouseVisiblity);//The zeros don't matter because the boolean is set to false
        if(!fader.fadingOut){
            prevMenuState=menuState;
            initer.initFader();
        }
    }
}

public void checkForF11(){
    if(ks.isF11PressedThenReleased){
        if(ul.isWindowInFullscreenMode) ul.toWindowedMode();
        else ul.toFullscreenMode();
    }
}

public void setMouseVisible(){
    ul.window.setCursor(ul.window.getToolkit().createCustomCursor(loader.cursor, new Point(0, 0),"Visible"));
}

public void setMouseInvisble(){
    ul.window.setCursor(ul.window.getToolkit().createCustomCursor(new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB), new Point(0, 0),"Clear"));
}

public void checkMouseState(){
    if(isMouseVisible!=prevIsMouseVisible){
        if(isMouseVisible) setMouseVisible();
        else setMouseInvisble();
        prevIsMouseVisible=isMouseVisible;
    }
}

public BufferedImage updateScreen(){
    BufferedImage screenImage = new BufferedImage(ul.contentPlaneX, ul.contentPlaneY, BufferedImage.TYPE_INT_ARGB);
    Graphics2D screenGraphics = screenImage.createGraphics();
    Color oldColor = screenGraphics.getColor();
    screenGraphics.setPaint(Color.white);
    screenGraphics.fillRect(0, 0, ul.contentPlaneX, ul.contentPlaneY);
    screenGraphics.setColor(oldColor);

    checkForF11();
    clearScreen(screenGraphics);
    switch(screenNo){
        case TITLE_SCREEN:
            titleScreen.titleScreen(screenGraphics);
            break;
        case START_SCREEN:
            startScreen.startScreen(screenGraphics);
            break;
        case TOWN_ONE:
            towns.townOne(screenGraphics);
            break;
        case ROUTE_ONE:
            routes.routeOne(screenGraphics);
            break;
        case HEALING_CENTER:
            healingCenter.healingCenter(screenGraphics);
            break;
        case ESC_MENU:
            escScreen.escapeScreen(screenGraphics);
            break;
    }
    checkEsc(screenGraphics);
    rs.checkForResize();
    ks.update();
    checkMouseState();

    //g.drawImage(screenImage, 0, 0, ul.contentPlaneX, ul.contentPlaneY, null);
    //screenGraphics.dispose();
    return screenImage;
}
}

Respuestas a la pregunta(1)

Su respuesta a la pregunta