【问题标题】:2D Game camera logic2D 游戏相机逻辑
【发布时间】:2013-07-31 00:35:30
【问题描述】:

我正在尝试为我正在制作的 2D 游戏实现一个摄像头...目标是让摄像头将玩家保持在中心,并使精灵相对于摄像头。

为了掌握normalocity's post 的窍门,我尝试通过制作一个相机测试项目来简单地开始,在该项目中我通过将精灵绘制到 JPanel 并移动“相机”对象(即JPanel) 并设置精灵的 x,y 相对于此。

正如我所说,相机是 JPanel...我添加了一个“世界”,它是一个带有 x,y of 0,0w=1000, h=1000 的类。我已经包含了精灵相对于世界的位置以及相机。当我向上移动相机时,精灵向下移动,玩家按预期停留在中间..

但如果我继续按下,精灵似乎会一直在自己绘制。

我的问题是:

  • 根据下面的代码,我在实现相机方面是否走在正确的轨道上?
  • 为什么精灵开始在此处绘制自身?它应该从 viewPort/JPanel 中消失

谢谢!

现在添加了PaintComponent(g),我的JPanel bg 灰色现在会滑落。这应该发生吗?


编辑:我的程序的 SSCCE:

主类:

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

相机类:

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

}

世界级:

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

玩家类:

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

精灵类:

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


}

【问题讨论】:

  • 只是猜测,但可能会在您的 paintComponent 方法中调用 super(g)
  • 附带说明:2D 摄像机通常最终需要有更复杂的定位,而不仅仅是“始终将玩家显示在中间”。这段关于“Insanely Twisted Shadow Planet”开发的幕后视频介绍了他们为制作良好的相机定位逻辑所做的工作:youtube.com/watch?v=aAKwZt3aXQM
  • @RobertJ.Walker 谢谢。我想是这样的。在尝试任何疯狂的事情之前,我试图先获得主凸轮逻辑。谢谢你的视频!
  • 一个更简单的定位相机的方法是使用翻译的Graphics。保存手动计算,您可以为游戏屏幕上的所有对象使用相同的坐标系。

标签: java swing camera jpanel paintcomponent


【解决方案1】:

1) 你没有通过在paintComponent(..) 中调用super.paintComponent(g) 来兑现绘制链

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

    //do drawing here
}

根据Java docs

protected void paintComponent(Graphics g)

此外,如果你没有调用 super 的实现,你必须遵守 opaque 属性,即如果此组件是不透明的,则必须 以不透明的颜色完全填充背景。如果你不 尊重 opaque 属性,您可能会看到视觉伪影。

2) 还要注意我添加的@Override annotation 以及我将public 修饰符更改为protected 的事实,因为这就是实现类中定义的访问级别,除非出于特定原因,否则我们应该保留它。

3) Swing 还使用Keybindings 阅读How to Use Key Bindings

4) 还可以阅读 Concurrency in Swing,特别是 The Event Dispatch Thread,它规定所有摆动组件都通过 SwingUtillities.invokeXXX(..) 块在 EDT 上创建:

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

5) 你扩展 JFrame 类并创建一个实例,这不是你想要的,而是从类声明中删除 extends JFrame

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

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

【讨论】:

  • 谢谢大卫。我已经用逻辑更新了上面的代码,以检测何时绘制精灵以至于它在 Cam 的视图中。那是对的吗?另外,既然我已经添加了super.paintComponent(g);,现在按右或左键会移动JPanel 的灰色背景。这应该发生吗?代码编辑和图片见上
  • @Growler 很好,如果我了解相机正在 JPanel 周围移动到以前未见过/未绘制的 区域 我认为 repaint() 调用 @ 987654342@ 在每个动作发生后(即关键事件后)是必需的
  • 你可以在上面看到repaint()keyPressed(KeyEvent e)中按下键后被调用
  • @Growler 对不起,我错过了,让我测试一下。下次请发SSCCE
  • 好吧,当我按下箭头键以移动相机时,我确实增加/减少了camX, camY...这是实际的 JPanel 坐标。由于该区域被涂成灰色,它会移动它......但它应该保持静止,因为 JPanel 在世界上“移动”
【解决方案2】:

您的世界是一个比屏幕(或重要的 jpanel)更大的虚拟区域。所有对象的位置都是相对于世界的。我们称它们为绝对坐标。

您的相机是世界的一小部分(您的面板)。通过移动它,您可以看到不同的世界部分。如果您可以像链接到的帖子中那样移动相机,那么在某些时候您将既看不到玩家也看不到其他精灵。

既然你的目标是让玩家在屏幕上居中,这对我们的世界意味着什么?这意味着玩家和相机一起移动相对于世界。

鉴于上述情况,像在您的第一个屏幕截图中那样绘制相机精灵是没有意义的。相机精灵应该是不可见的,或者它应该与玩家精灵绘制在相同的位置。在不改变玩家的情况下改变相机的绝对坐标也没有意义。这两个人正在一起移动。 (在您的 keyPressed() 方法中考虑到这一点)

现在,当您进行绘图时,您是从相机的角度进行绘图(或者换句话说,在相机的坐标系中)。从这个角度来看,相机总是看到一个(0, 0, cameraWidth, cameraHeight) 的矩形。这就是清除灰色区域时应该使用的方法。这将解决您的移动背景问题。因为相机和玩家总是有相同的绝对坐标,所以玩家总是在同一个地方(这就是我们想要的)。其余的精灵将相对于相机看到。

对于它们中的每一个,当您执行 (sprite.x - cam.x) 和 (sprite.y - cam.y) 时,您可以在相机的坐标系中平移它们。由于它们已被翻译,您只需要检查它们是否在相机的矩形(0, 0, cameraWidth, cameraHeight) 内。如果他们是你继续画他们。如果不忽略它们。

希望对你有帮助

注意:cameraWidth, cameraHeight 是您的 jpanel 的尺寸

【讨论】:

    猜你喜欢
    • 2021-09-23
    • 2020-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多