/
physics-system.ts
341 lines (296 loc) · 9.67 KB
/
physics-system.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/**
* @packageDocumentation
* @module physics2d
*/
import { EDITOR, DEBUG } from 'internal:constants';
import { System, Vec2, director, Director, game, error, IVec2Like, Rect, Eventify } from '../../core';
import { IPhysicsWorld } from '../spec/i-physics-world';
import { createPhysicsWorld } from './instance';
import { physicsEngineId } from './physics-selector';
import { DelayEvent } from './physics-internal-types';
import { IPhysicsConfig, ICollisionMatrix } from '../../physics/framework/physics-config';
import { CollisionMatrix } from '../../physics/framework/collision-matrix';
import { ERaycast2DType, RaycastResult2D, PHYSICS_2D_PTM_RATIO } from './physics-types';
import { Collider2D } from './components/colliders/collider-2d';
let instance: PhysicsSystem2D | null = null;
export class PhysicsSystem2D extends Eventify(System) {
/**
* @en
* Gets or sets whether the physical system is enabled, which can be used to pause or continue running the physical system.
* @zh
* 获取或设置是否启用物理系统,可以用于暂停或继续运行物理系统。
*/
get enable (): boolean {
return this._enable;
}
set enable (value: boolean) {
this._enable = value;
}
/**
* @zh
* Gets or sets whether the physical system allows automatic sleep, which defaults to true.
* @zh
* 获取或设置物理系统是否允许自动休眠,默认为 true。
*/
get allowSleep (): boolean {
return this._allowSleep;
}
set allowSleep (v: boolean) {
this._allowSleep = v;
if (!EDITOR) {
this.physicsWorld.setAllowSleep(v);
}
}
/**
* @en
* Gets or sets the value of gravity in the physical world, which defaults to (0, -10).
* @zh
* 获取或设置物理世界的重力数值,默认为 (0, -10)。
*/
get gravity (): Vec2 {
return this._gravity;
}
set gravity (gravity: Vec2) {
this._gravity.set(gravity);
if (!EDITOR) {
this.physicsWorld.setGravity(new Vec2(gravity.x / PHYSICS_2D_PTM_RATIO, gravity.y / PHYSICS_2D_PTM_RATIO));
}
}
/**
* @en
* Gets or sets the maximum number of simulated substeps per frame.
* @zh
* 获取或设置每帧模拟的最大子步数。
*/
get maxSubSteps () {
return this._maxSubSteps;
}
set maxSubSteps (value: number) {
this._maxSubSteps = value;
}
/**
* @en
* Gets or sets the fixed delta time consumed by each simulation step.
* @zh
* 获取或设置每步模拟消耗的固定时间。
*/
get fixedTimeStep () {
return this._fixedTimeStep;
}
set fixedTimeStep (value: number) {
this._fixedTimeStep = value;
}
/**
* @en
* Turn on or off the automatic simulation.
* @zh
* 获取或设置是否自动模拟。
*/
get autoSimulation () {
return this._autoSimulation;
}
set autoSimulation (value: boolean) {
this._autoSimulation = value;
}
get debugDrawFlags () {
return this.physicsWorld.debugDrawFlags;
}
set debugDrawFlags (v) {
this.physicsWorld.debugDrawFlags = v;
}
/**
* @en
* The velocity iterations for the velocity constraint solver.
* @zh
* 速度更新迭代数
*/
public velocityIterations = 10;
/**
* @en
* The position Iterations for the position constraint solver.
* @zh
* 位置迭代更新数
*/
public positionIterations = 10;
/**
* @en
* Gets the wrappered object of the physical world through which you can access the actual underlying object.
* @zh
* 获取物理世界的封装对象,通过它你可以访问到实际的底层对象。
*/
readonly physicsWorld: IPhysicsWorld;
/**
* @en
* Gets the ID of the system.
* @zh
* 获取此系统的ID。
*/
static readonly ID = 'PHYSICS_2D';
static get PHYSICS_NONE () {
return !physicsEngineId;
}
static get PHYSICS_BUILTIN () {
return physicsEngineId === 'builtin';
}
static get PHYSICS_BOX2D () {
return physicsEngineId === 'box2d';
}
/**
* @en
* Gets the physical system instance.
* @zh
* 获取物理系统实例。
*/
static get instance (): PhysicsSystem2D {
if (!instance) {
instance = new PhysicsSystem2D();
}
return instance;
}
/**
* @en
* Gets the collision matrix。
* @zh
* 获取碰撞矩阵。
*/
readonly collisionMatrix: ICollisionMatrix = new CollisionMatrix() as unknown as ICollisionMatrix;
private _enable = true;
private _allowSleep = true;
private _maxSubSteps = 1;
private _fixedTimeStep = 1.0 / 60.0;
private _autoSimulation = true;
private _accumulator = 0;
private _steping = false;
private readonly _gravity = new Vec2(0, -10 * PHYSICS_2D_PTM_RATIO);
private _delayEvents: DelayEvent[] = [];
get stepping () {
return this._steping;
}
private constructor () {
super();
const config = game.config ? game.config.physics as IPhysicsConfig : null;
if (config) {
Vec2.copy(this._gravity, config.gravity as IVec2Like);
this._gravity.multiplyScalar(PHYSICS_2D_PTM_RATIO);
this._allowSleep = config.allowSleep;
this._fixedTimeStep = config.fixedTimeStep;
this._maxSubSteps = config.maxSubSteps;
this._autoSimulation = config.autoSimulation;
if (config.collisionMatrix) {
for (const i in config.collisionMatrix) {
const bit = parseInt(i);
const value = 1 << parseInt(i);
this.collisionMatrix[`${value}`] = config.collisionMatrix[bit];
}
}
}
this.physicsWorld = createPhysicsWorld();
this.gravity = this._gravity;
this.allowSleep = this._allowSleep;
}
/**
* @en
* Perform a simulation of the physics system, which will now be performed automatically on each frame.
* @zh
* 执行一次物理系统的模拟,目前将在每帧自动执行一次。
* @param deltaTime 与上一次执行相差的时间,目前为每帧消耗时间
*/
postUpdate (deltaTime: number) {
if (!this._enable) {
return;
}
if (!this._autoSimulation) {
return;
}
director.emit(Director.EVENT_BEFORE_PHYSICS);
this._steping = true;
const fixedTimeStep = this._fixedTimeStep;
const velocityIterations = this.velocityIterations;
const positionIterations = this.positionIterations;
this._accumulator += deltaTime;
let substepIndex = 0;
while (substepIndex++ < this._maxSubSteps && this._accumulator > fixedTimeStep) {
this.physicsWorld.step(fixedTimeStep, velocityIterations, positionIterations);
this._accumulator -= fixedTimeStep;
}
const events = this._delayEvents;
for (let i = 0, l = events.length; i < l; i++) {
const event = events[i];
event.func.call(event.target);
}
events.length = 0;
this.physicsWorld.syncPhysicsToScene();
if (this.debugDrawFlags) {
this.physicsWorld.drawDebug();
}
this._steping = false;
director.emit(Director.EVENT_AFTER_PHYSICS);
}
_callAfterStep (target: object, func: Function) {
if (this._steping) {
this._delayEvents.push({
target,
func,
});
} else {
func.call(target);
}
}
/**
* @en
* Reset the accumulator of time to given value.
* @zh
* 重置时间累积总量为给定值。
*/
resetAccumulator (time = 0) {
this._accumulator = time;
}
/**
* @en
* Perform simulation steps for the physics world.
* @zh
* 执行物理世界的模拟步进。
* @param fixedTimeStep
*/
step (fixedTimeStep: number) {
this.physicsWorld.step(fixedTimeStep, this.velocityIterations, this.positionIterations);
}
/**
* @en
* Raycast the world for all colliders in the path of the ray.
* The raycast ignores colliders that contain the starting point.
* @zh
* 检测哪些碰撞体在给定射线的路径上,射线检测将忽略包含起始点的碰撞体。
* @method rayCast
* @param {Vec2} p1 - start point of the raycast
* @param {Vec2} p2 - end point of the raycast
* @param {RayCastType} type - optional, default is RayCastType.Closest
* @param {number} mask - optional, default is 0xffffffff
* @return {[PhysicsRayCastResult]}
*/
raycast (p1: IVec2Like, p2: IVec2Like, type: ERaycast2DType = ERaycast2DType.Closest, mask = 0xffffffff): readonly Readonly<RaycastResult2D>[] {
return this.physicsWorld.raycast(p1, p2, type, mask);
}
/**
* @en Test which colliders contain the point.
* @zh 检测给定点在哪些碰撞体内。
*/
testPoint (p: Vec2): readonly Collider2D[] {
return this.physicsWorld.testPoint(p);
}
/**
* @en Test which colliders contain the point.
* @zh 检测给定点在哪些碰撞体内。
*/
testAABB (rect: Rect): readonly Collider2D[] {
return this.physicsWorld.testAABB(rect);
}
}
director.once(Director.EVENT_INIT, () => {
initPhysicsSystem();
});
function initPhysicsSystem () {
if (!PhysicsSystem2D.PHYSICS_NONE && !EDITOR) {
director.registerSystem(PhysicsSystem2D.ID, PhysicsSystem2D.instance, 0);
}
}