Java – 2D Game camera logic

camerajavajpanelpaintcomponentswing

I'm trying to implement a camera for a 2D game that I'm making… The goal will to have the cam keep the player in the center and the sprites relative to the camera.

To get the hang of normalocity's post, I tried starting off simple by making a Camera Test project, where I'd simulate a camera by drawing a sprite to a JPanel, and moving a "camera" object (which is the JPanel) around and setting the sprite's x,y relative to that.

The Camera, as I said, is the JPanel… and I've added a "world", which is a class with an x,y of 0,0, and w=1000, h=1000. I've included the sprite's location relative to the world as well as the camera. When I move the camera up, the sprite moves down and the player stays in the middle as expected..

enter image description here

But if I keep pressing down, the sprite seems to keep drawing over itself.

enter image description here

My questions are:

  • Am I on the right track in implementing a camera given the code below?
  • Why does the sprite start to draw over itself there? It should just disappear off the viewPort/JPanel

Thanks!

Now with PaintComponent(g) added, my JPanel bg color of gray now slides off. Is this supposed to happen?

enter image description here


EDIT: SSCCE of my program:

Main Class:

import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class MainSSCCE extends JFrame {
static MainSSCCE runMe;

public MainSSCCE() {
    JFrame f = new JFrame("Camera Test");
    CameraSSCCE cam = new CameraSSCCE(0, 0, 500, 500);
    f.add(cam);
    f.setSize(cam.getWidth(), cam.getHeight());    
    f.setVisible(true);
    f.setResizable(false);
    f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 
    Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
    f.setLocation( (screensize.width - f.getWidth())/2,
         (screensize.height - f.getHeight())/2-100 );
}

public static void main(String[] args) {
    runMe = new MainSSCCE();
}
}

Camera Class:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;

//Camera is the JPanel that will draw all objects... each object location will be in relation to the World
public class CameraSSCCE extends JPanel implements KeyListener {
    //add world to camera...
    private static final long serialVersionUID = 1L;
    private int camX, camY, camH, camW;
    private SpriteSSCCE sprite;
    private PlayerSSCCE player;
    private WorldSSCCE world;

    public CameraSSCCE(int x, int y, int w, int h) {
        camX = x;
        camY = y;
        camW = w;       
        camH = h;   
        sprite = new SpriteSSCCE(this, 300, 300, 20, 20);
        player = new PlayerSSCCE(this, camW/2, camH/2, 25, 40);
        world = new WorldSSCCE(this, 0, 0, 1000, 1000);

        addKeyListener(this);
        setFocusable(true);
    }

    public int getWidth() {
        return camW;
    }

    public int getHeight() {
        return camH;
    }    

    @Override   
    protected void paintComponent(Graphics g) { 
        super.paintComponent(g);

        //cam is 500 x 500
        g.setColor(Color.gray);
        g.fillRect(camX, camY, camW, camH);     

        //draw sprite at JPanel location if in camera sight
        if (((sprite.getX()-camX) >= camX) && ((sprite.getX()-camX) <= (camX+camW)) && ((sprite.getY()-camY) >= camY) && ((sprite.getY()-camY) <= (camY+camH))) {
            g.setColor(Color.green);
            g.fillRect(sprite.getX()-camX, sprite.getY()-camY, 20, 20); 

            //Cam Sprite Location
            g.setColor(Color.white);
            g.drawString("Camera Sprite Location: (" + (sprite.getX()-camX) + ", " + (sprite.getY()-camY) + ")", sprite.getX()-camX, sprite.getY()-camY);                   
        }

        //Player location (center of Camera... Camera follows player)
        g.setColor(Color.cyan);
        g.fillRect(player.getX()-player.getWidth(), player.getY()-player.getWidth(), player.getWidth(), player.getHeight());

        g.setColor(Color.white);
        //World Sprite Location
        g.drawString("World Sprite Location: (" + sprite.getX() + ", " + sprite.getY() + ")", sprite.getX(), sprite.getY());

        //Cam Player Location
        g.drawString("Cam Player Location: (" + (camW/2-player.getWidth()) + ", " + (camH/2-player.getHeight()) + ")", camW/2-player.getWidth(), camH/2-player.getHeight());
    }

    public void keyPressed(KeyEvent e) {
        //move camera right in relation to World
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            camX+=5;
        }
        //move camera left in relation to World
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            camX-=5;
        }
        //move camera up in relation to World
        if (e.getKeyCode() == KeyEvent.VK_UP) {
            camY-=5;
        }
        //move camera down in relation to World
        if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            camY+=5;
        }
        repaint();
    }   

    public void keyReleased(KeyEvent e) {}
    public void keyTyped(KeyEvent e) {}

}

World Class:

public class WorldSSCCE {
    private int x, y, w, h;
    private CameraSSCCE camera;

    public WorldSSCCE(CameraSSCCE cam, int x, int y, int w, int h) {
        camera = cam;               
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;  
    }

    public int getWidth() {
        return this.w;
    }

    public int getHeight() {
        return this.h;
    }
}

Player Class:

import java.awt.Dimension;

public class PlayerSSCCE {
    private int x, y, w, h;
    private CameraSSCCE cam;

    public PlayerSSCCE(CameraSSCCE cm, int x, int y, int w, int h) {
        cam = cm;               
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;  
    }

    public int getWidth() {
        return this.w;
    }

    public int getHeight() {
        return this.h;
    }

    public void setX(int val) {
        this.x += val;
    }

    public void setY(int val) {
        this.y += val;
    }   
}

Sprite Class:

import java.awt.Color;
import java.awt.Graphics;

public class SpriteSSCCE {
    private int xLoc, yLoc, width, height;
    private CameraSSCCE world;

    public SpriteSSCCE(CameraSSCCE wld, int x, int y, int w, int h) {
        xLoc = x;
        yLoc = y;
        width = w;
        height = h;
        world = wld;    
    }

    public int getX() {
        return xLoc;
    }

    public int getY() {
        return yLoc;    
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }


    public void paintComponent(Graphics g) {
        g.setColor(Color.green);
        g.fillRect(xLoc, yLoc, width, height);      
    }


}

Best Answer

1) You have not honored the paint chain by calling super.paintComponent(g) in paintComponent(..):

@Override
protected void paintComponent(Graphics g) {    
    super.paintComponent(g);

    //do drawing here
}

As per Java docs:

protected void paintComponent(Graphics g)

Further, if you do not invoker super's implementation you must honor the opaque property, that is if this component is opaque, you must completely fill in the background in a non-opaque color. If you do not honor the opaque property you will likely see visual artifacts.

2) Also notice the @Override annotation I added and the fact that I changed public modifier to protected as thats what the access level is defined as in the implementation class which we should keep unless for a specific reason.

3) Also Swing uses Keybindings have a read on How to Use Key Bindings

4) Also have a read on Concurrency in Swing specifically on The Event Dispatch Thread which dictates all swing components be created on EDT via SwingUtillities.invokeXXX(..) block:

SwingUtilities.invokeLater(new Runnable() {
   @Override
    public void run() {
         //create and manipulate swing components here
    }
});

5) You extend the JFrame class and create an instance, this is not what you want rather remove the extends JFrame from the class declaration:

public class MainSSCCE extends JFrame { //<-- Remove extends JFrame

    public MainSSCCE() {
       JFrame f = new JFrame("Camera Test");//<-- instance is created here
    }
}
Related Topic