Annoying lags/stutters in an android game

后端 未结 5 1277
轮回少年
轮回少年 2020-12-03 07:42

I just started with game development in android, and I\'m working on a super simple game.

The game is basically like flappy bird.

I managed to get everyt

5条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-12-03 07:56

    Without ever having made a game in Android, I do have made 2D-games in Java/AWT using Canvas and bufferStrategy...

    If you experience flickering, you could always go for a manual double-buffer (get rid of flickering) by rendering to an offscreen Image, and then just page-flip / drawImage with the new pre-rendered contents directly.

    But, I get the feeling that you're more concerned about "smoothness" in your animation, in which case I'd recommend that you extend your code with interpolation in-between the different animation ticks;

    Currently, your rendering loop update logical state (move things logically) in the same pace as you render, and measure with some reference time and try to keep track of passed time.

    Instead, you should update in whichever frequency you feel is desirable for the "logics" in your code to work -- typically 10 or 25Hz is just fine (I call it "update ticks", which is completely different from the actual FPS), whereas the rendering is done by keeping high-resolution track of time for measuring "how long" your actual rendering takes (I've used nanoTime and that has been quite sufficient, whereas currentTimeInMillis is rather useless...),

    In that way, you can interpolate between ticks and render as many frames as possible until the next tick by calculating fine-grained positions based on how much time has passed since the last tick, compared to how much time it "should" be between two ticks (since you always know where you are - position, and where you're heading -- velocity)

    That way, you will get the same "animation speed" regardless of CPU/platform, but more or less smoothness since faster CPUs will perform more renders between different ticks.

    EDIT

    Some copy-paste/conceptual code -- but do note that this was AWT and J2SE, no Android. However, as a concept and with some Androidification I'm sure this approach should render smoothly unless the calculus done in your logic/update is too heavy (e.g., N^2 algorithms for collision detection and N grows big with particle systems and the like).

    Active render loop

    Instead of relying on repaint to do the painting for you (which might take different time, depending on what OS is doing), the first step is to take active control over the rendering loop and use a BufferStrategy where you render and then actively "show" the contents when you're done, before going back at it again.

    Buffer strategy

    Might require som special Android-stuff to get going, but it's fairly straight forward. I use 2-pages for the bufferStrategy to create a "page-flipping" mechanism.

    try
    {
         EventQueue.invokeAndWait(new Runnable() {
            public void run()
            {
                canvas.createBufferStrategy(2);
            }
        });
    }    
    catch(Exception x)
    {
        //BufferStrategy creation interrupted!        
    }
    

    Main animation loop

    Then, in your main loop, get the strategy and take active control (don't use repaint)!

    long previousTime = 0L;
    long passedTime = 0L;
    
    BufferStrategy strategy = canvas.getBufferStrategy();
    
    while(...)
    {
        Graphics2D bufferGraphics = (Graphics2D)strategy.getDrawGraphics();
    
        //Ensure that the bufferStrategy is there..., else abort loop!
        if(strategy.contentsLost())
            break;
    
        //Calc interpolation value as a double value in the range [0.0 ... 1.0] 
        double interpolation = (double)passedTime / (double)desiredInterval;
    
        //1:st -- interpolate all objects and let them calc new positions
        interpolateObjects(interpolation);
    
        //2:nd -- render all objects
        renderObjects(bufferGraphics);
    
        //Update knowledge of elapsed time
        long time = System.nanoTime();
        passedTime += time - previousTime;
        previousTime = time;
    
        //Let others work for a while...
        Thread.yield();
    
        strategy.show();
        bufferGraphics.dispose();
    
        //Is it time for an animation update?
        if(passedTime > desiredInterval)
        {
            //Update all objects with new "real" positions, collision detection, etc... 
            animateObjects();
    
            //Consume slack...
            for(; passedTime > desiredInterval; passedTime -= desiredInterval);
        }
    }
    

    An object managed be the above main loop would then look something along the lines of;

    public abstract class GfxObject
    {
        //Where you were
        private GfxPoint oldCurrentPosition;
    
        //Current position (where you are right now, logically)
        protected GfxPoint currentPosition;
    
        //Last known interpolated postion (
        private GfxPoint interpolatedPosition;
    
        //You're heading somewhere?
        protected GfxPoint velocity;
    
        //Gravity might affect as well...?
        protected GfxPoint gravity;
    
        public GfxObject(...)
        {
            ...
        }
    
        public GfxPoint getInterpolatedPosition()
        {
            return this.interpolatedPosition;
        }
    
        //Time to move the object, taking velocity and gravity into consideration
        public void moveObject()
        {
            velocity.add(gravity);
            oldCurrentPosition.set(currentPosition);
            currentPosition.add(velocity);
        }
    
        //Abstract method forcing subclasses to define their own actual appearance, using "getInterpolatedPosition" to get the object's current position for rendering smoothly...
        public abstract void renderObject(Graphics2D graphics, ...);
    
        public void animateObject()
        {
            //Well, move as default -- subclasses can then extend this behavior and add collision detection etc depending on need
            moveObject();
        }
    
        public void interpolatePosition(double interpolation)
        {
            interpolatedPosition.set(
                                     (currentPosition.x - oldCurrentPosition.x) * interpolation + oldCurrentPosition.x,
                                     (currentPosition.y - oldCurrentPosition.y) * interpolation + oldCurrentPosition.y);
        }
    }
    

    All 2D positions are managed using a GfxPoint utility class with double precision (since the interpolated movements might be very fine and rounding is typically not wanted until rendering the actual graphics). To simplify the math stuff needed and making code more readable, I've also added various methods.

    public class GfxPoint
    {
        public double x;
        public double y;
    
        public GfxPoint()
        {
            x = 0.0;
            y = 0.0;
        }
    
        public GfxPoint(double init_x, double init_y)
        {
            x = init_x;
            y = init_y;
        }
    
        public void add(GfxPoint p)
        {
            x += p.x;
            y += p.y;
        }
    
        public void add(double x_inc, double y_inc)
        {
            x += x_inc;
            y += y_inc;
        }
    
        public void sub(GfxPoint p)
        {
            x -= p.x;
            y -= p.y;
        }
    
        public void sub(double x_dec, double y_dec)
        {
            x -= x_dec;
            y -= y_dec;
        }
    
        public void set(GfxPoint p)
        {
            x = p.x;
            y = p.y;
        }
    
        public void set(double x_new, double y_new)
        {
            x = x_new;
            y = y_new;
        }
    
        public void mult(GfxPoint p)
        {
            x *= p.x;
            y *= p.y;
        }
    
    
    
        public void mult(double x_mult, double y_mult)
        {
            x *= x_mult;
            y *= y_mult;
        }
    
        public void mult(double factor)
        {
            x *= factor;
            y *= factor;
        }
    
        public void reset()
        {
            x = 0.0D;
            y = 0.0D;
        }
    
        public double length()
        {
            double quadDistance = x * x + y * y;
    
            if(quadDistance != 0.0D)
                return Math.sqrt(quadDistance);
            else
                return 0.0D;
        }
    
        public double scalarProduct(GfxPoint p)
        {
            return scalarProduct(p.x, p.y);
        }
    
        public double scalarProduct(double x_comp, double y_comp)
        {
            return x * x_comp + y * y_comp;
        }
    
        public static double crossProduct(GfxPoint p1, GfxPoint p2, GfxPoint p3)
        {
            return (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y);
        }
    
        public double getAngle()
        {
            double angle = 0.0D;
    
            if(x > 0.0D)
                angle = Math.atan(y / x);
            else if(x < 0.0D)
                angle = Math.PI + Math.atan(y / x);
            else if(y > 0.0D)
                angle = Math.PI / 2;
            else
                angle = - Math.PI / 2;
    
            if(angle < 0.0D)
                angle += 2 * Math.PI;
            if(angle > 2 * Math.PI)
                angle -= 2 * Math.PI;
    
            return angle;
        }
    }
    

提交回复
热议问题