Вопрос

I'm working on a simple game using a Java Applet, and I can't seem to get the screen to move properly with my player object. The problem seems to be the result of the so-called "double-buffer" that is recommended in many guides for use with Applets. I copied the implementation of the double buffer from one such guide but as far as I understand it the point is to have two separate objects of the Java Graphics class, where one draws all the game objects etc. offscreen and then the other displays that image on screen.

The relevant parts of code from the Level class which is the "main" class of my Applet:

public class Level extends Applet implements Runnable, KeyListener {

    public Image offscreen;
    public Graphics d;
    private Thread th;
    // also a bunch of game mechanic related stuff I'm not including here

    public void init(){
        th = new Thread(this);
        th.start();
        offscreen = createImage(480,800);
        d = offscreen.getGraphics();
        initObjects(); // a method that intialises all game objects to be drawn
        // again, a bunch of other nonimportant stuff
    }

    public void run(){}  // run is the method an Applet automatically calls after init() is done, for me it basically contains the game
    //loop which works just fine apart from the camera and is ran 60 times per second using Thread.sleep()

    // offSetY is set here as the player moves up or down (the camera only needs to scroll on the y-axis)
    //as offSetY = player.y -750; The player is supposed to always be 50 pixels from the bottom of the screen which is 800 pixels high.
    //The purpose of offSetY is to provide the delta of the players position on y axis since the last update in order to
    //determine in which direction and how much we need to move the screen.

    repaint() // calls update(), which is where the truly relevant part starts.


    public void update(Graphics g){
        d.clearRect(0, 0, 480, 800); // clears the offscreen area of whatever was there last time update() was called
        d.drawImage(background, 0, 0, this); // draws the background image on the offscreen area
        g.translate(0, -offSetY); // translate to change coordinates according to player movement, offSetY is determined by player position
        //in the game loop and is getting the "correct" value (as many units as the player has moved on the Y axis so far)
        paint(d);
        g.drawImage(offscreen, 0, 0, this); // after paint() has drawn everything offscreen, the graphics object g draws that image on the screen
    }

    public void paint(Graphics d){
        d.translate(0, -offSetY); // this is where things get complicated, I'll discuss this translate in more detail below the code
        d.drawImage(background, 0, 0, this); // I'm not entirely sure why the background is drawn both here and in the "on screen" Graphics object,
        //it's inherited from the double buffer example. Removing it seems to have no effect on anything.

        // I have a for-loop drawing all my game objects here. I've tested this separately and it works just fine without the translates
        //(e.g. if I'm moving the objects down instead of moving the "screen".

    }
}

So, essentially my problem is that if I only have the translate() in update() which moves the origin of the "on-screen" stuff it works perfectly for everything that is visible on the original area of the screen but as soon as objects out of that area get drawn things get funky. Also if the player object is moved outside these boundaries it appears only as a "ghost", getting drawn at the coordinates where it entered every frame instead of where it actually is (sideways movement is not shown but if you come back to the working area the player position has changed on the x-axis). Imgur album with some images here, orange balloon is the player object, everything else is other game objects.

On the other hand, if I add the other translate() inside paint(), the problem becomes apparent "double-dipping" of the translate function, meaning that the screen is moved far faster than the player moves and the movement does not stop even if the player is no longer moving. This is frankly quite perplexing to me, as the value of offSetY should always be dependant on the players position and therefore self-correcting if the player is "left behind" of the screen. Imgur album with some pics here.

Any ideas on how to implement this properly are welcome, or if someone knows a different way to implement the same functionality. Feel free to ask any further questions and I'll try to provide answers ASAP.

Это было полезно?

Решение

So today, after staring at the problem for another half an hour, I finally managed to fix it. Turns out there were two separate problems, I'll try to explain both of them here.

The first problem was the "camera" scrolling way faster than the player was moving. This was caused by my failure to understand how exactly the translate() function works. The offSetY variable, which tracked how much the player had moved, was getting its value calculated as an absolute value of how much the player had moved from its original position. This meant that for the first pixel moved, the camera was also moved one pixel, but for the second one the camera was moved two pixels and so on. The cumulative effect caused the camera to scroll up/down uncontrollably with extended periods of movement. I fixed this by adding a variable int offSetHelper, the value of which is always the value of offSetY on the previous iteration of the game loop. The translate() function is then called with the parameters (0, -offSetY + offSetHelper), meaning that its parameters can only ever be either (0,0) or (0,1), depending on whether the players absolute position (a float) has changed one full pixels worth or not.

The second problem was objects outside the initially visible screen area (0,0) to (480,800) getting drawn multiple times in the same image, aka the offscreen area where the paint() method runs not getting cleared properly. The fix to this was simple: instead of d.clearRect(0, 0, 480, 800); I used d.clearRect(0, -100000, 480, 100800);, which covers the entire levels area. It's likely there is a more elegant solution which only clears the area that will actually get drawn, but this solution works for me.

Thank you to the commenters, I hope I didn't waste too much of your time. I'd already been staring at this problem about three evenings before posting here, just happened to finally look at it the right way today.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top