Handling Older Devices in a Newer Android Game(转)

 

ALL the codes

Thoughts on coding and game development, by Eric Karl.



  • Handling Older Devices in a Newer Android Game 

    While finishing up my first mobile game (shameless plug), one of the issues that came up was how to optimize performance, especially on Android, where different hardware abounds. While there are many many android devices with high-resolution displays (800x480 up to 1280x800), the graphics processing power of these devices is not evenly matched. For instance, the Motorola Droid and the Nexus S have almost the same resolution (~800x480), but the Nexus S has a graphics chip that is about twice as fast as the Droid. If my application is designed to take full advantage of a current-gen Android phone like the Nexus S, there’s no way it will be able to perform identically on a two year old phone like the Droid. This means that something has to change for the Droid version.

    For my game, I considered two approaches to scaling down to worse hardware: limiting the game’s framerate, and limiting the game’s resolution. Read how after the break.

    Limiting The Framerate

    This is a fairly common thing to sacrifice as hardware performance decreases. In my current game, newer devices such as the Nexus S will run at 60fps, while older devices may degrade to 30fps. Both of these framerates are absolutely playable, and in fact many devices are capped at 30fps to begin with (although users seem to dislike this :D)

    Unfortunately, Android doesn’t provide a completely trivial way to cap framerate in a typical game loop. However, a number of people have posted possible solutions online. A typical solution is (found many places, for instance):

    • In your main game loop, keep track of how long it has taken to render the last frame. 
    • If the time taken is less than the target framerate (say, 1/30 seconds), then sleep for the difference.

    While this solution works acceptably, I’ve found that using sleeping (usleep, etc…) is not precise, and introduces a lot of noise into the framerate. To work around this, I use sleeping to get near but not too near to my target FPS, then I manually spin in the application to hit the exact fps. The code to accomplish this would be something like the following:

     
       
    void Game::Tick() {
        // Your rendering / update code goes here.
        // *Insert your code to ensure that we only set this on first run*
        if (_isFirstRun) {
            _lastFrame = GameTime::GetTime();
            _isFirstRun = false;
        }
    
        // We want to run at 30fps
        const double SECONDS_PER_FRAME = 1.0 / 30.0;
    
        // timeDiff is the time since the last frame.
        // GameTime::GetTime() is a helper which returns current time in fractions of a second.
        // _lastFrame is a member variable that stores the time the last frame completed.
        double timeDiff = GameTime::GetTime() - _lastFrame;
      
        // If timeDiff this is faster than our target fps, we need to wait. Sleeping is imprecise, but
        // frees up the CPU and helps the app run smoothly, so we sleep until we are near our
        // target FPS, but not too near (I chose to sleep until we hit ~40fps).
        while (timeDiff < (1.0f / 40.0f)) {
            usleep(5 * 1000);
            timeDiff = GameTime::GetTime() - _lastFrame;
        } 
        
        // Next, we want to very precisely wait until we hit exactly 30fps. To do this I just spin 
        // a while loop. This will max-out the CPU, so it should be kept to a minimum
        while (timeDiff < SECONDS_PER_FRAME)
        {
            timeDiff = GameTime::GetTime() - _lastFrame;
        }
       
        // Finally, record our current time, and exit the main game loop, allowing Android to
        // push our frame to the screen.
        _lastFrame = GameTime::GetTime();
    }



    Rendering Fewer Pixels

    If your game is high-movement, and you think 30fps is not acceptable, or if you are already at 30fps and can’t degrade framerate any more, there is another simple option available: Render less pixels to the screen. This is commonly seen on other platforms (PC games usually let you chose the resolution, many console games chose to run at 720p, even when 1080p is available), and I wanted to achieve a similar effect on Android.

    When your application renders in Android, it frequently (always?) renders to an off-screen buffer. The OS then copies this buffer to the actual screen. What I wanted to achieve was to render to a smaller off-screen buffer (say 400x240), and have the OS use its hardware-scaler to scale this up to 800x480 when it presented my data to the screen. This results in a less sharp image, but allows me to run full screen while doing around ¼ the rendering work.

    For the longest time I couldn’t figure out how to achieve this on Android. After digging around for quite some time, I found the magic incantation. The code below makes use of the NDK sample GL2JNI’s GL2JNIView as the Android view which renders OpenGL.

     

     

     mView = new GL2JNIView(getApplication(), false, 16, 0);
    
    //  Get your application’s width/height. This assumes that you are running full screen with
    //  mView as your only content view. Other arrangements will require more calculation.
     int height = getHeight(); 
     int width = getWidth();
    
    // Decide on a factor by which to scale down your rendering. I chose 0.5f here. Remember that
    // the reduction in work is factor squared, so 0.5 results in 0.25 the work.
     float factor = 0.5f; 
    
    // Get your view’s holder. This corresponds to the actual surface that will be rendered to, and is 
    // scaled up to match the size of the mView when presented. Therefore, changing the size of the 
    // holder doesn’t affect layout or screen-size, just resolution.
     android.view.SurfaceHolder holder = mView.getHolder();
     holder.setFixedSize((int)(width * factor), (int)((height) * factor));
    
     // Finally set your content view. The view size is unchanged (will fill the parent), but 
     // the size of the surface rendered to has been reduced.
     setContentView(mView);


     

    Conclusion:

  • I ended up just going with framerate limiting, as I think my game works best when very sharp and crisp. However, either of these techniques may work well for you, so I wanted to share them.

  • 你可能感兴趣的:(Handling Older Devices in a Newer Android Game(转))