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.

<think>嗯,我现在需要解决用户的问题:"incompatibleconfigurationfileerrorsolution",用户提到参考了引用[1]的内容,但重点是要根据用户的实际问题来处理。用户的问题是关于指定文件包含不兼容的新旧配置导致的错误。引用[1]中提到,避免使用不兼容的EnterpriseLibrary副本,并建议统一版本。所以核心应该围绕如何解决配置文件的版本冲突问题。首先,我应该分析错误的原因。用户可能在不同环境中使用了不同版本的配置文件,比如旧版系统生成的和新版工具保存的,导致格式或属性不兼容。或者配置文件的部分内容被手动修改过,导致不一致。引用[1]强调选择单一版本的库并清除其他副本,这点可以借鉴到配置管理上,即统一配置文件的版本来源。接下来,解决方案需要分步骤。第一步是定位冲突来源,用diff工具对比两个版本的文件,找出差异部分。用户需要确定哪些配置项是关键的,比如连接字符串、特性开关等。第二步,合并配置时需要区分环境,比如开发和生产环境的不同配置。建议使用环境变量或占位符来动态注入值,避免文件被覆盖。同时,回滚到旧版本进行测试,确保不会引发新错误。然后,考虑使用版本控制工具管理配置文件,如Git,这样每次变更都有记录,方便回溯。另外,自动化工具如Ansible或Puppet可以确保配置一致性,减少手动修改带来的错误。对于企业库应用,必须确保所有实例使用相同的预编译二进制文件或源代码版本,避免混合使用造成冲突。还要检查配置文件的结构。比如XML或JSON文件中可能存在语法错误、嵌套错误,或者属性名在新旧版本中被重命名或弃用。用户需要参考官方文档的变更日志,确认这些差异并进行相应调整。如果使用如app.config或web.config,可能需要查看<configSections>是否正确定义。最后,建立规范流程:每次更新配置前备份原文件,测试环境中验证配置,再部署到生产。定期清理废弃配置项,保持文件精简。同时,使用工具验证配置的有效性,比如企业库自带的配置工具或第三方验证器,确保文件在运行时不会出错。用户可能的应用场景包括企业应用部署、持续集成流程中的配置管理,或者多环境下的应用配置同步。相关问题需要覆盖版本管理工具的使用、配置加密、自动化验证等方面,确保用户全面解决当前问题并预防未来错误。</think> 解决“不兼容配置文件错误”的核心是**确保配置文件的格式、结构或属性与当前运行的软件版本完全兼容**。以下是系统性解决方案和操作步骤: --- ### **解决方案步骤** 1. **定位冲突来源** - **对比配置文件**: 用 diff 工具(如 WinMerge、Beyond Compare)对比新旧配置文件,找出格式差异、废弃属性或新增必填项。 *示例*:旧版用 `<appSetting key="Timeout">`,新版可能要求 `<add key="Timeout" value="10" />`。 - **检查版本标签**: 配置文件顶部的版本声明(如 `<configuration version="2.0">`)必须与软件版本匹配。 2. **统一配置文件来源** - **清理多版本库文件**: 删除项目中冗余的旧版本二进制文件(如 `EnterpriseLibrary.*.dll`),仅保留**与当前运行时匹配的唯一版本**(引用 [^1])。 ```powershell # 示例:清理旧版 NuGet 包 Remove-Package EnterpriseLibrary.Common -Force -ProjectName YourProject Install-Package EnterpriseLibrary.Common -Version 6.0.0 # 统一版本 ``` - **重建配置文件**: 若配置文件曾手动修改,使用官方配置工具(如 Enterprise Library 配置控制台)重新生成兼容格式。 3. **适配配置结构** - **处理废弃属性**: 查阅新版软件的**配置迁移文档**(如 Microsoft Docs),将旧属性替换为新语法。 *示例*: ```xml <!-- 旧版: --> <logging enabled="true" /> <!-- 新版: --> <logging> <add name="default" enabled="true" /> </logging> ``` - **分区合并**: 若配置文件合并了多个来源(如多环境配置),使用 `<include>` 或环境变量拆分逻辑,避免嵌套冲突: ```xml <appSettings file=".\environment\dev.config" /> <!-- 分环境加载 --> ``` 4. **版本控制与自动化** - **Git 版本回退**: 若错误在提交后发生,回退到最近兼容版本: ```bash git checkout HEAD~1 -- App.config # 恢复上一版配置文件 ``` - **自动化验证**: 在 CI/CD 流程中添加配置校验步骤(如 PowerShell 脚本检查 XML 合法性): ```powershell Test-Xml -Path .\App.config # 抛错则中止部署 ``` --- ### **关键预防措施** - **锁定依赖版本**: 在 NuGet / Maven 等工具中固定版本范围,禁止自动升级破坏配置(引用 [^1]): ```xml <!-- NuGet 示例 --> <PackageReference Include="EnterpriseLibrary.Common" Version="6.0.0" /> ``` - **环境隔离配置**: 使用 `appsettings.Development.json` 或 Spring Cloud Config 分离环境,避免人工误改生产配置。 - **配置加密敏感数据**: 用 AES/DPAPI 加密数据库密码等字段,避免明文导致解析失败: ```bash aspnet_regiis -pef "connectionStrings" .\ # 加密连接字符串 ``` --- ### **相关问题延伸** 1. **如何自动检测配置文件与代码版本的兼容性?** *提示*:集成单元测试读取配置并实例化关键对象,捕获初始化异常。 2. **多环境(开发/测试/生产)配置冲突的最佳实践?** *提示*:12-Factor App 的环境变量优先原则 + 配置中心(Consul/ZooKeeper)。 3. **企业库升级时配置文件迁移的自动化工具?** *提示*:Enterprise Library 的 `Migrate.exe` 或自定义 XSLT 换脚本。 4. **加密配置导致解析失败的应急方案?** *提示*:恢复备份副本 + 密钥轮换机制(Azure Key Vault)。 > 配置文件错误本质是**版本一致性失控**。通过锁定依赖、环境隔离和自动化校验,可根治兼容性问题 [^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值