extra-game-loop 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/lib/{es2015/game-loop.d.ts → game-loop.d.ts} +1 -1
- package/lib/game-loop.js.map +1 -0
- package/lib/index.js.map +1 -0
- package/package.json +9 -25
- package/src/game-loop.ts +148 -0
- package/src/index.ts +1 -0
- package/dist/es2015/index.min.mjs +0 -2
- package/dist/es2015/index.min.mjs.map +0 -1
- package/dist/es2015/index.mjs +0 -13970
- package/dist/es2015/index.mjs.map +0 -1
- package/dist/es2015/index.umd.js +0 -13980
- package/dist/es2015/index.umd.js.map +0 -1
- package/dist/es2015/index.umd.min.js +0 -2
- package/dist/es2015/index.umd.min.js.map +0 -1
- package/dist/es2018/index.min.mjs +0 -2
- package/dist/es2018/index.min.mjs.map +0 -1
- package/dist/es2018/index.mjs +0 -13970
- package/dist/es2018/index.mjs.map +0 -1
- package/dist/es2018/index.umd.js +0 -13980
- package/dist/es2018/index.umd.js.map +0 -1
- package/dist/es2018/index.umd.min.js +0 -2
- package/dist/es2018/index.umd.min.js.map +0 -1
- package/lib/es2015/game-loop.js.map +0 -1
- package/lib/es2015/index.js.map +0 -1
- package/lib/es2018/game-loop.d.ts +0 -28
- package/lib/es2018/game-loop.js +0 -71
- package/lib/es2018/game-loop.js.map +0 -1
- package/lib/es2018/index.d.ts +0 -1
- package/lib/es2018/index.js +0 -18
- package/lib/es2018/index.js.map +0 -1
- /package/lib/{es2015/game-loop.js → game-loop.js} +0 -0
- /package/lib/{es2015/index.d.ts → index.d.ts} +0 -0
- /package/lib/{es2015/index.js → index.js} +0 -0
package/README.md
CHANGED
|
@@ -45,7 +45,11 @@ class GameLoop<FixedDeltaTime extends number = number> {
|
|
|
45
45
|
|
|
46
46
|
start(): void
|
|
47
47
|
stop(): void
|
|
48
|
-
|
|
49
48
|
getFramesOfSecond(): number
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* This method allows you to manually advance to the next frame.
|
|
52
|
+
*/
|
|
53
|
+
nextFrame(deltaTime: number): void
|
|
50
54
|
}
|
|
51
55
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-loop.js","sourceRoot":"","sources":["../src/game-loop.ts"],"names":[],"mappings":"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"}
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA2B"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "extra-game-loop",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"files": [
|
|
7
7
|
"lib",
|
|
8
|
-
"
|
|
8
|
+
"src"
|
|
9
9
|
],
|
|
10
|
-
"main": "lib/
|
|
11
|
-
"types": "lib/
|
|
10
|
+
"main": "lib/index.js",
|
|
11
|
+
"types": "lib/index.d.ts",
|
|
12
12
|
"sideEffects": false,
|
|
13
13
|
"repository": "git@github.com:BlackGlory/extra-game-loop.git",
|
|
14
14
|
"author": "BlackGlory <woshenmedoubuzhidao@blackglory.me>",
|
|
@@ -18,18 +18,11 @@
|
|
|
18
18
|
"test": "jest --runInBand --config jest.config.js",
|
|
19
19
|
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
|
|
20
20
|
"test:coverage": "jest --coverage --config jest.config.js",
|
|
21
|
-
"prepublishOnly": "run-s clean build
|
|
22
|
-
"clean": "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"build": "
|
|
26
|
-
"build:es2015": "run-s build:es2015:*",
|
|
27
|
-
"build:es2015:compile": "tsc --project tsconfig.build.json --module commonjs --target es2015 --outDir lib/es2015",
|
|
28
|
-
"build:es2015:patch": "tscpaths -p tsconfig.build.json -s ./src -o ./lib/es2015",
|
|
29
|
-
"build:es2018": "run-s build:es2018:*",
|
|
30
|
-
"build:es2018:compile": "tsc --project tsconfig.build.json --module commonjs --target es2018 --outDir lib/es2018",
|
|
31
|
-
"build:es2018:patch": "tscpaths -p tsconfig.build.json -s ./src -o ./lib/es2018",
|
|
32
|
-
"bundle": "rollup --config rollup.config.js",
|
|
21
|
+
"prepublishOnly": "run-s clean build",
|
|
22
|
+
"clean": "rimraf lib",
|
|
23
|
+
"build": "run-s build:*",
|
|
24
|
+
"build:compile": "tsc --project tsconfig.build.json --module commonjs --target es2018 --outDir lib",
|
|
25
|
+
"build:patch": "tscpaths -p tsconfig.build.json -s ./src -o ./lib",
|
|
33
26
|
"release": "standard-version"
|
|
34
27
|
},
|
|
35
28
|
"husky": {
|
|
@@ -39,14 +32,8 @@
|
|
|
39
32
|
}
|
|
40
33
|
},
|
|
41
34
|
"devDependencies": {
|
|
42
|
-
"@blackglory/jest-matchers": "^0.3.1",
|
|
43
35
|
"@commitlint/cli": "^17.1.2",
|
|
44
36
|
"@commitlint/config-conventional": "^17.1.0",
|
|
45
|
-
"@rollup/plugin-commonjs": "^22.0.2",
|
|
46
|
-
"@rollup/plugin-json": "^4.1.0",
|
|
47
|
-
"@rollup/plugin-node-resolve": "^14.1.0",
|
|
48
|
-
"@rollup/plugin-replace": "^4.0.0",
|
|
49
|
-
"@rollup/plugin-typescript": "^8.5.0",
|
|
50
37
|
"@types/jest": "^27.4.1",
|
|
51
38
|
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
|
52
39
|
"@typescript-eslint/parser": "^5.37.0",
|
|
@@ -57,9 +44,6 @@
|
|
|
57
44
|
"jest": "^27.5.1",
|
|
58
45
|
"npm-run-all": "^4.1.5",
|
|
59
46
|
"rimraf": "^3.0.2",
|
|
60
|
-
"rollup": "^2.79.0",
|
|
61
|
-
"rollup-plugin-analyzer": "^4.0.0",
|
|
62
|
-
"rollup-plugin-terser": "^7.0.2",
|
|
63
47
|
"standard-version": "^9.5.0",
|
|
64
48
|
"ts-jest": "^27.1.4",
|
|
65
49
|
"tscpaths": "^0.0.9",
|
package/src/game-loop.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { FiniteStateMachine, IFiniteStateMachineSchema } from 'extra-fsm'
|
|
2
|
+
import { assert } from '@blackglory/prelude'
|
|
3
|
+
|
|
4
|
+
interface IGameLoopOptions<FixedDeltaTime extends number = number> {
|
|
5
|
+
/**
|
|
6
|
+
* 物理帧一帧经过的时间, 可以通过`1000/fps`得来, 或物理引擎指示的最小deltaTime.
|
|
7
|
+
*/
|
|
8
|
+
fixedDeltaTime: FixedDeltaTime
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 将渲染帧的deltaTime拆分成物理帧时, 允许的最大渲染帧deltaTime,
|
|
12
|
+
* 如果实际的渲染帧deltaTime超过此值, 则它会被重置为此值.
|
|
13
|
+
* 这可以在性能较差的设备上解决因物理帧执行需要带时间过长而导致渲染帧率无限下降的"死亡螺旋"问题.
|
|
14
|
+
*/
|
|
15
|
+
maximumDeltaTime: number
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 每帧运行一次, 运行早于fixedUpdate.
|
|
19
|
+
* 在此处理用户输入等与物理计算无关的操作, 在update里改变的状态直到下一物理帧时才会反应.
|
|
20
|
+
*/
|
|
21
|
+
update: (deltaTime: number) => void
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 每物理帧运行一次, 运行晚于update, 早于lateUpdate.
|
|
25
|
+
* deltaTime固定等于fixedDeltaTime.
|
|
26
|
+
* 一帧中可能运行零次, 一次, 或数十次, 不应在此处理物理计算以外的繁重操作.
|
|
27
|
+
*/
|
|
28
|
+
fixedUpdate: (deltaTime: FixedDeltaTime) => void
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 每帧运行一次, 运行晚于fixedUpdate, 能获得alpha.
|
|
32
|
+
* 出于各种原因, 你可能会想使用它.
|
|
33
|
+
*/
|
|
34
|
+
lateUpdate: (deltaTime: number, alpha: number) => void
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 每帧运行一次, 总是运行在fixedUpdate和udpate之后.
|
|
38
|
+
*/
|
|
39
|
+
render: (alpha: number) => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
enum State {
|
|
43
|
+
Stopped = 'stopped'
|
|
44
|
+
, Running = 'running'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Event =
|
|
48
|
+
| 'start'
|
|
49
|
+
| 'stop'
|
|
50
|
+
|
|
51
|
+
const schema: IFiniteStateMachineSchema<State, Event> = {
|
|
52
|
+
[State.Stopped]: { start: State.Running }
|
|
53
|
+
, [State.Running]: { stop: State.Stopped }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class GameLoop<FixedDeltaTime extends number> {
|
|
57
|
+
private readonly fsm = new FiniteStateMachine(schema, State.Stopped)
|
|
58
|
+
private readonly fixedDeltaTime: FixedDeltaTime
|
|
59
|
+
private readonly maximumDeltaTime: number
|
|
60
|
+
private readonly update: (deltaTime: number) => void
|
|
61
|
+
private readonly fixedUpdate: (fixedDeltaTime: FixedDeltaTime) => void
|
|
62
|
+
private readonly lateUpdate: (deltaTime: number, alpha: number) => void
|
|
63
|
+
private readonly render: (alpha: number) => void
|
|
64
|
+
private requstId?: number
|
|
65
|
+
private lastTimestamp?: number
|
|
66
|
+
private deltaTimeAccumulator = 0
|
|
67
|
+
private lastDeltaTime = 0
|
|
68
|
+
|
|
69
|
+
constructor(options: IGameLoopOptions<FixedDeltaTime>) {
|
|
70
|
+
this.fixedDeltaTime = options.fixedDeltaTime
|
|
71
|
+
this.maximumDeltaTime = options.maximumDeltaTime
|
|
72
|
+
assert(
|
|
73
|
+
this.maximumDeltaTime >= this.fixedDeltaTime
|
|
74
|
+
, 'maximumDeltaTime must be greater than or equal to fixedDeltaTime'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
this.update = options.update
|
|
78
|
+
this.fixedUpdate = options.fixedUpdate
|
|
79
|
+
this.lateUpdate = options.lateUpdate
|
|
80
|
+
this.render = options.render
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
start(): void {
|
|
84
|
+
this.fsm.send('start')
|
|
85
|
+
|
|
86
|
+
this.lastTimestamp = performance.now()
|
|
87
|
+
this.loop()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
stop(): void {
|
|
91
|
+
this.fsm.send('stop')
|
|
92
|
+
|
|
93
|
+
cancelAnimationFrame(this.requstId!)
|
|
94
|
+
|
|
95
|
+
delete this.requstId
|
|
96
|
+
delete this.lastTimestamp
|
|
97
|
+
this.lastDeltaTime = 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getFramesOfSecond(): number {
|
|
101
|
+
if (this.fsm.matches(State.Running)) {
|
|
102
|
+
return this.lastDeltaTime !== 0
|
|
103
|
+
? 1000 / this.lastDeltaTime
|
|
104
|
+
: 0
|
|
105
|
+
} else {
|
|
106
|
+
return 0
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private loop = (): void => {
|
|
111
|
+
// requestAnimationFrame的实现存在陷阱, 它提供的timestamp参数有时会比`performance.now()`早.
|
|
112
|
+
// 因此主动调用`performance.now()`来获取时间戳.
|
|
113
|
+
// https://stackoverflow.com/questions/50895206/exact-time-of-display-requestanimationframe-usage-and-timeline
|
|
114
|
+
const timestamp = performance.now()
|
|
115
|
+
const deltaTime = timestamp - this.lastTimestamp!
|
|
116
|
+
this.lastDeltaTime = deltaTime
|
|
117
|
+
this.lastTimestamp = timestamp
|
|
118
|
+
|
|
119
|
+
this.nextFrame(deltaTime)
|
|
120
|
+
|
|
121
|
+
this.requstId = requestAnimationFrame(this.loop)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* nextFrameUpdateFirst依序做以下几件事:
|
|
126
|
+
* 1. 响应用户输入, 为这一帧的游戏世界做出非物理方面的更新, 例如角色转向, 改变武器瞄准角度等.
|
|
127
|
+
* 2. 响应游戏世界从上一帧更新后到这一帧更新之前发生的物理变化.
|
|
128
|
+
* 注意, 如果渲染帧率比物理帧率快, 且运行性能良好, 则物理帧不一定会在此帧更新.
|
|
129
|
+
* 3. 渲染经过更新后的最新状态, 渲染器可以根据alpha参数执行插值渲染.
|
|
130
|
+
*/
|
|
131
|
+
nextFrame(deltaTime: number): void {
|
|
132
|
+
this.deltaTimeAccumulator = Math.min(
|
|
133
|
+
this.deltaTimeAccumulator + deltaTime
|
|
134
|
+
, this.maximumDeltaTime
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
this.update(deltaTime)
|
|
138
|
+
|
|
139
|
+
while (this.deltaTimeAccumulator >= this.fixedDeltaTime) {
|
|
140
|
+
this.fixedUpdate(this.fixedDeltaTime)
|
|
141
|
+
this.deltaTimeAccumulator -= this.fixedDeltaTime
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const alpha = this.deltaTimeAccumulator / this.fixedDeltaTime
|
|
145
|
+
this.lateUpdate(deltaTime, alpha)
|
|
146
|
+
this.render(alpha)
|
|
147
|
+
}
|
|
148
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './game-loop'
|