原文:https://github.com/SilverTiger/lwjgl3-tutorial/wiki/Game-loops
译注:翻译仅供参考,请以原文为准。代码请看原文中的链接。括号里的内容一般也是译注,供理解参考用。
PS:Markdown还挺好用的,给CSDN点个赞。

How does a game work? 一个游戏如何工作?

从编程角度来讲,一个游戏可以用一个简单的方法来描述:

public void startGame() {init();gameLoop();dispose();
}

这就是游戏全部必须要做的事,init()包含初始化的全部内容,比如创建窗口、读取资源。dispose()包含释放游戏资源的全部代码。

A simple game loop 一个简单的游戏循环

游戏循环的关键部分是处理输入、更新游戏逻辑、渲染游戏。

public void gameLoop() {while (running) {input();update();render();}
}

这样的游戏循环会耗尽全部的CPU,因为它运行得太快了。通常你并不需要它这样,因此需要让循环适当停歇。

public void gameLoop() {long sleepTime = 1000L / 60L;while (running) {input();update();render();sleep(sleepTime);}
}

在这个例子里,加入了16毫秒的休眠时间,游戏每秒将只更新62.5次。
虽然这已经比毫无停歇要好一些了,但是其实你需要有一个可变的休眠时间以保证游戏有稳定的FPS。为此,我们应该看看时间运算教程。
根据时间计数器的选择,有两种形式的游戏循环:
- 可变时步游戏循环
- 固定时步游戏循环

Variable timestep 可变时步

用可变时步,你不需要关心你的游戏是不是跑得太快或太慢了,因为会使用时间增量(delta time)来更新游戏。(意思是,如果跑太慢了,时间增量超长,那么就一次更新许多内容,太快也是同理)
在这种循环,我们首先就得拿到时间增量

float delta = timer.getDelta();

时间增量用来更新游戏。所以给update()方法加上参数。

public void update(float delta) {/* do updates */
}

之后也不需要再加什么了,最后是这样的:

public void gameLoop() {while (running) {float delta = timer.getDelta();input();update(delta);render();window.update();}
}

你可能想问,为啥不休眠了。因为你可以让GLFW去激活垂直同步,这样你不需要再关心休眠的问题了。
但是如果我们在循环里加入休眠,也不需要改太多东西。

public void gameLoop() {long targetTime = 1000L / targetFPS;while (running) {/* Note that you have to multiply by 1000 to get milliseconds */long startTime = timer.getTime() * 1000;float delta = timer.getDelta();input();update(delta);render();window.update();/* Same as above, multiply time in seconds by 1000 */long endTime = timer.getTime() * 1000;sleep(startTime + targetTime - endTime);}
}

但是这个循环有一个瑕疵:你的更新受限于你的帧率,在简单的游戏里这还OK,但是如果是想做一个逼真的物理情况模拟,这就不是你想要的了。(意思是,假如FPS很低的话,那更新次数也很低,时间增量也超长,如果模拟铁球下落,会看到铁球在空中是断断续续的,一次还落超长距离……效果很糟糕。大部分情况下虽然不会这么明显,但是也会让玩家有一种违和感。)

Fixed timestep 固定时步

固定时步的话,你的游戏循环不能再受限于帧率了,而是基于时间,比起可变时步,它更具有确定性。
固定时步除了时间增量以外还需要有更多的变量,需要一个累加器(accumulator)记录经过的时间,一个表示更新之间的时间应该是多少的间隔量(interval),还需要有一个aplha值来充当插值。

float delta;
float accumulator = 0f;
float interval = 1f / 30f;
float alpha;

在这个例子里,我们希望每秒有30次更新,所以间隔量大概是33.33毫秒。
变量有了,开始循环。跟可变循环一样,先要拿到时间增量,但在此循环里,还要把增量加在累加器上。

delta = timer.getDelta();
accumulator += delta;

接着处理输入。再之后要检查经过的时间是否应该做更新操作了。(累加器时间超过设定的间隔量,按间隔量来做更新)

while (accumulator >= interval) {update(interval);accumulator -= interval;
}

之后我们其实还剩下一些多出来的时间,举个例子。
比如现在游戏已经开始了48毫秒,但是我们下一次更新应该是在66.67毫秒的时候,因为我们说好是每33.33毫秒更新一次的(之前设定的间隔量)。这时累加器的值应该是48-33.33=14.67毫秒。
所以我们当前的游戏状态其实是14.67毫秒以前的游戏状态,现在我们可以再渲染一次相同的屏幕内容,但是那样的话我们的游戏看起来就像是以30FPS来渲染而不是我们预期的那样,帧在做重复无意义的废渲染。为了能表现出两次更新间的某种状态,我们引入了alpha这个插值。(意思是,我们虽然希望一秒更新30次,但是却期望能更精确地连续渲染画面,而不是离散地去重复渲染这30次更新时的画面,那样就算一秒渲染了100次,其实也是在反复重复地渲染这30个更新瞬间的画面而已。)

alpha = accumulator / interval;

在上面说的例子里,alpha值应该是14.67/33.33=0.44,所以我们距下次更新大概行进到了44%的阶段。为了渲染,我们需要保持上一次和本次的状态插值,具体在另一篇教程里再说。
最后,固定时步循环应该是这样的:

public void gameLoop() {float delta;float accumulator = 0f;float interval = 1f / targetUPS;float alpha;while (running) {delta = timer.getDelta();accumulator += delta;input();while (accumulator >= interval) {update();accumulator -= interval;}alpha = accumulator / interval;render(alpha);window.update();}
}

关于udpate方法,有一点:如果间隔量不打算改的话,其实没必要再把间隔量放在update方法里了。(因为它是固定的值)
本教程里,update()写作update(delta),render()写作render(alpha),所以你用哪种时步都可以。

public void update() {update(1f / targetUPS);
}public void render() {render(1f);
}

下一篇学习用shader来渲染。

【LWJGL官方教程】Game loops 游戏循环相关推荐

  1. Unity官方教程滚球游戏实现(Roll A Ball)带工程源码

    记学习unity之后做出的第一款游戏   第一次使用Unity,在学成C#基础之后,迫不及待的照着教程做出了这个游戏,第一课最主要学习的东西就是Unity API的使用及场景中各个界面面板的主要功能, ...

  2. 噩梦射手 安装包资源包提供下载 Unity官方教程 Survival Shooter 资源已经失效了!? Unity3D休闲射击类游戏《Survival Shooter》完整源码

    Unity官方教程 (Survival Shooter)  资源已经失效了! 可能是版本太老了 中文名叫噩梦射手? 找了半天找了这个版本 的 放到这里吧 [这个游戏主角是必死的,就看能坚持多久啦] 网 ...

  3. unity官方教程 太空射击---问题填坑 之 计分以及游戏胜利

    (本文仅供自己参考,文中代码可能有误,毕竟手打没有VS的帮助,请仅供理解,切莫复制粘贴)原来的代码还是不理解为什么,但现在有了新的方法,前排提醒,一下方法会与官方教程出现巨大误差,请理解后使用 首先我 ...

  4. Unity官方教程Ruby大冒险的自学笔记

    Unity官方教程Ruby大冒险的自学笔记 一. //正确例子: void Update(){//获取运动矢量moveX = Input.GetAxisRaw("Horizontal&quo ...

  5. Unity3D官方教程爬坑

    全是在学官教时遇到的坑,然后数小时后爬出来.同时会添加到处学来的的Unity技巧 ---------------------------------------------------------- ...

  6. 2D Toolkit官方教程翻译

    系统综述 2D Toolkit分为两个系统:运行时组件(runtime components)和脚本编辑器.脚本编辑器在Assets目录下产生资源,运行时脚本在场景中产生objects. 两者关系如下 ...

  7. UE官方教程笔记01-实时渲染基础上

    对官方教程视频[官方培训]01-实时渲染基础上 | 陈拓 Epic的笔记 部分没听懂的地方就按自己的理解瞎写了 介绍 实时渲染(Real-Time Rendering,RTR)是指在计算机上快速生成图 ...

  8. Caffe官方教程翻译(9):Multilabel Classification with Python Data Layer

    前言 最近打算重新跟着官方教程学习一下caffe,顺便也自己翻译了一下官方的文档.自己也做了一些标注,都用斜体标记出来了.中间可能额外还加了自己遇到的问题或是运行结果之类的.欢迎交流指正,拒绝喷子! ...

  9. Caffe官方教程翻译(8):Brewing Logistic Regression then Going Deeper

    前言 最近打算重新跟着官方教程学习一下caffe,顺便也自己翻译了一下官方的文档.自己也做了一些标注,都用斜体标记出来了.中间可能额外还加了自己遇到的问题或是运行结果之类的.欢迎交流指正,拒绝喷子! ...

最新文章

  1. 杂七杂八的前端基础01——函数作用域
  2. 控制-频域操作-傅里叶级数和傅里叶变换
  3. Entity Framework Core 2.0的突破性变更
  4. redis的string类型和bitmap
  5. 基于Unity3d 引擎的Android游戏优化
  6. 在数据库什么是主键与外键
  7. 京东AI研究院何晓冬:将先进的技术和模型落地到产业
  8. BundlePhobia
  9. Leetcode--671. 合并二叉树
  10. 编写组件,使用JavaScript更新UpdatePanel
  11. 小k娱乐网php,zblog仿小k资源模板Zblogphp系统精仿小k资源网主题模板面世啦!特惠福利...
  12. CRT中的sftp上传文件出现中文路乱码
  13. oozie控制台命令
  14. Ubuntu下利用QSS、WPS破解wpa/wpa2加密
  15. 网络规划设计师水平考试备考资料(11.分析总结)
  16. 如何学习PLC编程,有没有什么好的方法?
  17. R2S铝合金外壳散热测试
  18. 三八节送什么礼物比较好?这四款数码好物别错过了!
  19. 密室逃脱2 古堡迷城
  20. python实现矢量分级渲染_PyQGIS开发 -- 聊聊矢量图层渲染(一)

热门文章

  1. 转:管理者做到这三件事,员工就能高歌猛进
  2. 点击页面出现富强、民主这类文字动画效果
  3. 中級から学ぶ日本語_なぞなぞ
  4. 问题 L: 本原勾股数
  5. p116数据查询作业
  6. 不良的生活习惯对身体健康的影响
  7. 数组 对象 超实用方法自己整理
  8. foward和redirect的区别
  9. warning: #940-D: missing return statement at end of non-void function “fgetc“解决方案
  10. 机器学习算法--python--sklearn--后续神经网络