简体   繁体   中英

2D Game camera logic

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..

在此处输入图片说明

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

在此处输入图片说明

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?

在此处输入图片说明


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);      
    }


}

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 Keybinding s 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
    }
}

Your world is a virtual area larger than the screen (or your jpanel for what matters). All objects' positions are relative to the world. Let's call them absolute coordinates.

Your camera is a small rectangular portion of the world (your panel). By moving it you see different world portions. If you could move the camera like in the post you link to, then at some point you would not be able to see neither the player nor the other sprite.

Since your goal is to keep the player centered on the screen what does this mean for our world? This means that the player and the camera are moving together in relation to the world.

Given the above it does not make sense to draw a camera sprite as in your first screenshot. The camera sprite should be either invisible or it should be drawn in the same position with the player sprite. Nor it makes sense to change the camera's absolute coordinates without changing the player's. Those two are moving together. (take this into account in your keyPressed() methods)

Now when you are drawing, you are drawing from the camera's point of view (or in other words in the camera's coordinate system ). From that point of view, the camera always see a rectangle of (0, 0, cameraWidth, cameraHeight) . That's what you should use when clearing the area with gray color. This will fix your moving background issue. Since camera and player always have the same absolute coordinates the player will always be in the same place (this is what we want). The rest of the sprites will be seen relative to camera.

For each one of them you translate them in the camera's coordinate system when you do (sprite.x - cam.x) and (sprite.y - cam.y). Since they are translated, you only need to check if they are inside the camera's rectangle (0, 0, cameraWidth, cameraHeight) . If they are you go ahead and draw them. If not ignore them.

I hope that helps

Note: cameraWidth, cameraHeight are your jpanel's dimensions

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM