kiwiengine 0.0.1-alpha
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/LICENSE +21 -0
- package/README.md +8 -0
- package/assets/logo.png +0 -0
- package/examples/package.json +13 -0
- package/examples/test-dom/index.html +24 -0
- package/examples/test-dom/index.ts +21 -0
- package/examples/tsconfig.json +22 -0
- package/examples/webpack.config.js +31 -0
- package/lib/asset/audio.js +158 -0
- package/lib/asset/audio.js.map +1 -0
- package/lib/asset/loaders/audio.js +35 -0
- package/lib/asset/loaders/audio.js.map +1 -0
- package/lib/asset/loaders/binary.js +28 -0
- package/lib/asset/loaders/binary.js.map +1 -0
- package/lib/asset/loaders/font.js +27 -0
- package/lib/asset/loaders/font.js.map +1 -0
- package/lib/asset/loaders/loader.js +37 -0
- package/lib/asset/loaders/loader.js.map +1 -0
- package/lib/asset/loaders/spritesheet.js +56 -0
- package/lib/asset/loaders/spritesheet.js.map +1 -0
- package/lib/asset/loaders/text.js +27 -0
- package/lib/asset/loaders/text.js.map +1 -0
- package/lib/asset/loaders/texture.js +38 -0
- package/lib/asset/loaders/texture.js.map +1 -0
- package/lib/asset/preload.js +69 -0
- package/lib/asset/preload.js.map +1 -0
- package/lib/game-object/game-object-physics.js +188 -0
- package/lib/game-object/game-object-physics.js.map +1 -0
- package/lib/game-object/game-object-rendering.js +35 -0
- package/lib/game-object/game-object-rendering.js.map +1 -0
- package/lib/game-object/game-object.js +162 -0
- package/lib/game-object/game-object.js.map +1 -0
- package/lib/game-object/transform.js +118 -0
- package/lib/game-object/transform.js.map +1 -0
- package/lib/game-object-ext/animated-sprite.js +117 -0
- package/lib/game-object-ext/animated-sprite.js.map +1 -0
- package/lib/game-object-ext/dom-container.js +56 -0
- package/lib/game-object-ext/dom-container.js.map +1 -0
- package/lib/game-object-ext/rect.js +30 -0
- package/lib/game-object-ext/rect.js.map +1 -0
- package/lib/game-object-ext/spine.js +206 -0
- package/lib/game-object-ext/spine.js.map +1 -0
- package/lib/game-object-ext/sprite.js +46 -0
- package/lib/game-object-ext/sprite.js.map +1 -0
- package/lib/game-object-ext/text.js +68 -0
- package/lib/game-object-ext/text.js.map +1 -0
- package/lib/game-object-ext/tiling-sprite.js +64 -0
- package/lib/game-object-ext/tiling-sprite.js.map +1 -0
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -0
- package/lib/types/asset/audio.d.ts +21 -0
- package/lib/types/asset/audio.d.ts.map +1 -0
- package/lib/types/asset/loaders/audio.d.ts +7 -0
- package/lib/types/asset/loaders/audio.d.ts.map +1 -0
- package/lib/types/asset/loaders/binary.d.ts +7 -0
- package/lib/types/asset/loaders/binary.d.ts.map +1 -0
- package/lib/types/asset/loaders/font.d.ts +7 -0
- package/lib/types/asset/loaders/font.d.ts.map +1 -0
- package/lib/types/asset/loaders/loader.d.ts +13 -0
- package/lib/types/asset/loaders/loader.d.ts.map +1 -0
- package/lib/types/asset/loaders/spritesheet.d.ts +11 -0
- package/lib/types/asset/loaders/spritesheet.d.ts.map +1 -0
- package/lib/types/asset/loaders/text.d.ts +7 -0
- package/lib/types/asset/loaders/text.d.ts.map +1 -0
- package/lib/types/asset/loaders/texture.d.ts +9 -0
- package/lib/types/asset/loaders/texture.d.ts.map +1 -0
- package/lib/types/asset/preload.d.ts +8 -0
- package/lib/types/asset/preload.d.ts.map +1 -0
- package/lib/types/game-object/game-object-physics.d.ts +42 -0
- package/lib/types/game-object/game-object-physics.d.ts.map +1 -0
- package/lib/types/game-object/game-object-rendering.d.ts +15 -0
- package/lib/types/game-object/game-object-rendering.d.ts.map +1 -0
- package/lib/types/game-object/game-object.d.ts +81 -0
- package/lib/types/game-object/game-object.d.ts.map +1 -0
- package/lib/types/game-object/transform.d.ts +43 -0
- package/lib/types/game-object/transform.d.ts.map +1 -0
- package/lib/types/game-object-ext/animated-sprite.d.ts +29 -0
- package/lib/types/game-object-ext/animated-sprite.d.ts.map +1 -0
- package/lib/types/game-object-ext/dom-container.d.ts +16 -0
- package/lib/types/game-object-ext/dom-container.d.ts.map +1 -0
- package/lib/types/game-object-ext/rect.d.ts +17 -0
- package/lib/types/game-object-ext/rect.d.ts.map +1 -0
- package/lib/types/game-object-ext/spine.d.ts +35 -0
- package/lib/types/game-object-ext/spine.d.ts.map +1 -0
- package/lib/types/game-object-ext/sprite.d.ts +14 -0
- package/lib/types/game-object-ext/sprite.d.ts.map +1 -0
- package/lib/types/game-object-ext/text.d.ts +26 -0
- package/lib/types/game-object-ext/text.d.ts.map +1 -0
- package/lib/types/game-object-ext/tiling-sprite.d.ts +20 -0
- package/lib/types/game-object-ext/tiling-sprite.d.ts.map +1 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/utils/debug.d.ts +3 -0
- package/lib/types/utils/debug.d.ts.map +1 -0
- package/lib/types/utils/go.d.ts +26 -0
- package/lib/types/utils/go.d.ts.map +1 -0
- package/lib/types/world/world-debug.d.ts +11 -0
- package/lib/types/world/world-debug.d.ts.map +1 -0
- package/lib/types/world/world-physics.d.ts +16 -0
- package/lib/types/world/world-physics.d.ts.map +1 -0
- package/lib/types/world/world-rendering.d.ts +28 -0
- package/lib/types/world/world-rendering.d.ts.map +1 -0
- package/lib/types/world/world.d.ts +38 -0
- package/lib/types/world/world.d.ts.map +1 -0
- package/lib/utils/debug.js +5 -0
- package/lib/utils/debug.js.map +1 -0
- package/lib/utils/go.js +33 -0
- package/lib/utils/go.js.map +1 -0
- package/lib/world/world-debug.js +89 -0
- package/lib/world/world-debug.js.map +1 -0
- package/lib/world/world-physics.js +45 -0
- package/lib/world/world-physics.js.map +1 -0
- package/lib/world/world-rendering.js +123 -0
- package/lib/world/world-rendering.js.map +1 -0
- package/lib/world/world.js +147 -0
- package/lib/world/world.js.map +1 -0
- package/package.json +23 -0
- package/src/asset/audio.ts +176 -0
- package/src/asset/loaders/audio.ts +39 -0
- package/src/asset/loaders/binary.ts +32 -0
- package/src/asset/loaders/font.ts +27 -0
- package/src/asset/loaders/loader.ts +39 -0
- package/src/asset/loaders/spritesheet.ts +67 -0
- package/src/asset/loaders/text.ts +31 -0
- package/src/asset/loaders/texture.ts +46 -0
- package/src/asset/preload.ts +76 -0
- package/src/game-object/game-object-physics.ts +191 -0
- package/src/game-object/game-object-rendering.ts +27 -0
- package/src/game-object/game-object.ts +190 -0
- package/src/game-object/transform.ts +164 -0
- package/src/game-object-ext/animated-sprite.ts +140 -0
- package/src/game-object-ext/dom-container.ts +67 -0
- package/src/game-object-ext/rect.ts +40 -0
- package/src/game-object-ext/spine.ts +235 -0
- package/src/game-object-ext/sprite.ts +55 -0
- package/src/game-object-ext/text.ts +83 -0
- package/src/game-object-ext/tiling-sprite.ts +73 -0
- package/src/index.ts +14 -0
- package/src/utils/debug.ts +5 -0
- package/src/utils/go.ts +53 -0
- package/src/world/world-debug.ts +114 -0
- package/src/world/world-physics.ts +52 -0
- package/src/world/world-rendering.ts +145 -0
- package/src/world/world.ts +171 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SpritesheetData } from 'pixi.js';
|
|
2
|
+
import { audioLoader } from './loaders/audio';
|
|
3
|
+
import { fontFamilyLoader } from './loaders/font';
|
|
4
|
+
import { getCachedId, spritesheetLoader } from './loaders/spritesheet';
|
|
5
|
+
import { textureLoader } from './loaders/texture';
|
|
6
|
+
|
|
7
|
+
type AssetSource = string | {
|
|
8
|
+
src: string;
|
|
9
|
+
atlas: SpritesheetData;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function isImage(path: string): boolean {
|
|
13
|
+
return path.endsWith('.png') ||
|
|
14
|
+
path.endsWith('.jpg') ||
|
|
15
|
+
path.endsWith('.jpeg') ||
|
|
16
|
+
path.endsWith('.gif') ||
|
|
17
|
+
path.endsWith('.webp');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isAudio(path: string): boolean {
|
|
21
|
+
return path.endsWith('.mp3') ||
|
|
22
|
+
path.endsWith('.wav') ||
|
|
23
|
+
path.endsWith('.ogg');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isFontFamily(fontFamily: string): boolean {
|
|
27
|
+
return !fontFamily.includes('.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function preload(assets: AssetSource[], progressCallback: (progress: number) => void) {
|
|
31
|
+
const total = assets.length;
|
|
32
|
+
let loaded = 0;
|
|
33
|
+
|
|
34
|
+
await Promise.all(assets.map(async (asset) => {
|
|
35
|
+
if (typeof asset === 'string') {
|
|
36
|
+
if (isImage(asset)) {
|
|
37
|
+
await textureLoader.load(asset);
|
|
38
|
+
} else if (isAudio(asset)) {
|
|
39
|
+
await audioLoader.load(asset);
|
|
40
|
+
} else if (isFontFamily(asset)) {
|
|
41
|
+
await fontFamilyLoader.load(asset);
|
|
42
|
+
} else {
|
|
43
|
+
console.error(`Unknown asset type: ${asset}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const id = getCachedId(asset.src, asset.atlas);
|
|
48
|
+
await spritesheetLoader.load(id, asset.src, asset.atlas);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
loaded++;
|
|
52
|
+
progressCallback?.(loaded / total);
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
return () => {
|
|
56
|
+
assets.forEach((asset) => {
|
|
57
|
+
if (typeof asset === 'string') {
|
|
58
|
+
if (isImage(asset)) {
|
|
59
|
+
textureLoader.release(asset);
|
|
60
|
+
} else if (isAudio(asset)) {
|
|
61
|
+
audioLoader.release(asset);
|
|
62
|
+
} else if (isFontFamily(asset)) {
|
|
63
|
+
fontFamilyLoader.release(asset);
|
|
64
|
+
} else {
|
|
65
|
+
console.error(`Unknown asset type: ${asset}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const id = getCachedId(asset.src, asset.atlas);
|
|
70
|
+
spritesheetLoader.release(id);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { AssetSource, preload };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import Matter, { IChamferableBodyDefinition } from 'matter-js';
|
|
2
|
+
import { debugMode } from '../utils/debug';
|
|
3
|
+
import { GameObject } from './game-object';
|
|
4
|
+
import { localOffsetToWorld, worldToLocalWithNewWorld } from './transform';
|
|
5
|
+
|
|
6
|
+
type BaseCollider = { x?: number; y?: number; };
|
|
7
|
+
type RectangleCollider = BaseCollider & { type: 'rect'; width: number; height: number };
|
|
8
|
+
type CircleCollider = BaseCollider & { type: 'circle'; radius: number };
|
|
9
|
+
type VerticesCollider = BaseCollider & { type: 'vert'; vertices: { x: number; y: number }[] };
|
|
10
|
+
export type Collider = RectangleCollider | CircleCollider | VerticesCollider;
|
|
11
|
+
|
|
12
|
+
export class GameObjectPhysics {
|
|
13
|
+
#collider?: Collider;
|
|
14
|
+
#isStatic = false;
|
|
15
|
+
#isSensor = false;
|
|
16
|
+
#velocityX = 0;
|
|
17
|
+
#velocityY = 0;
|
|
18
|
+
#fixedRotation = false;
|
|
19
|
+
|
|
20
|
+
#go: GameObject;
|
|
21
|
+
#matterBody?: Matter.Body;
|
|
22
|
+
|
|
23
|
+
constructor(go: GameObject) {
|
|
24
|
+
this.#go = go;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#removeBody() {
|
|
28
|
+
const world = this.#go._getWorld();
|
|
29
|
+
if (!world || !this.#matterBody) return;
|
|
30
|
+
world._worldPhysics.removeBody(this.#matterBody);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#lastScaleX = 1;
|
|
34
|
+
#lastScaleY = 1;
|
|
35
|
+
#initialInertia?: number;
|
|
36
|
+
|
|
37
|
+
#createBody() {
|
|
38
|
+
const world = this.#go._getWorld();
|
|
39
|
+
if (!this.#collider || !world) return;
|
|
40
|
+
|
|
41
|
+
const wt = this.#go._wt;
|
|
42
|
+
const { x: ox, y: oy } = localOffsetToWorld(wt, this.#collider?.x ?? 0, this.#collider?.y ?? 0);
|
|
43
|
+
const x = wt.x.v + ox;
|
|
44
|
+
const y = wt.y.v + oy;
|
|
45
|
+
|
|
46
|
+
const bodyOpts: IChamferableBodyDefinition = {
|
|
47
|
+
angle: wt.rotation.v,
|
|
48
|
+
isStatic: this.#isStatic,
|
|
49
|
+
isSensor: this.#isSensor,
|
|
50
|
+
velocity: { x: this.#velocityX, y: this.#velocityY },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (this.#collider.type === 'rect') this.#matterBody = Matter.Bodies.rectangle(x, y, this.#collider.width, this.#collider.height, bodyOpts);
|
|
54
|
+
else if (this.#collider.type === 'circle') this.#matterBody = Matter.Bodies.circle(x, y, this.#collider.radius, bodyOpts);
|
|
55
|
+
else if (this.#collider.type === 'vert') this.#matterBody = Matter.Bodies.fromVertices(x, y, [this.#collider.vertices], bodyOpts);
|
|
56
|
+
else throw new Error('Invalid collider type');
|
|
57
|
+
|
|
58
|
+
this.#matterBody.plugin.owner = this.#go;
|
|
59
|
+
this.#lastScaleX = wt.scaleX.v;
|
|
60
|
+
this.#lastScaleY = wt.scaleY.v;
|
|
61
|
+
Matter.Body.scale(this.#matterBody, wt.scaleX.v, wt.scaleY.v);
|
|
62
|
+
|
|
63
|
+
this.#initialInertia = this.#matterBody.inertia;
|
|
64
|
+
if (this.#fixedRotation) {
|
|
65
|
+
Matter.Body.setInertia(this.#matterBody, Infinity);
|
|
66
|
+
Matter.Body.setAngularVelocity(this.#matterBody, 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
world._worldPhysics.addBody(this.#matterBody);
|
|
70
|
+
this.#setDebugRenderStyle();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
applyChanges() {
|
|
74
|
+
if (this.#collider && !this.#matterBody) this.#createBody();
|
|
75
|
+
if (!this.#matterBody) return;
|
|
76
|
+
|
|
77
|
+
const wt = this.#go._wt;
|
|
78
|
+
const { x: ox, y: oy } = localOffsetToWorld(wt, this.#collider?.x ?? 0, this.#collider?.y ?? 0);
|
|
79
|
+
|
|
80
|
+
if (wt.x.dirty || wt.scaleX.dirty || wt.y.dirty || wt.scaleY.dirty) {
|
|
81
|
+
if ((wt.x.dirty || wt.scaleX.dirty) && (wt.y.dirty || wt.scaleY.dirty)) Matter.Body.setPosition(this.#matterBody, { x: wt.x.v + ox, y: wt.y.v + oy });
|
|
82
|
+
else if (wt.x.dirty || wt.scaleX.dirty) Matter.Body.setPosition(this.#matterBody, { x: wt.x.v + ox, y: this.#matterBody.position.y });
|
|
83
|
+
else if (wt.y.dirty || wt.scaleY.dirty) Matter.Body.setPosition(this.#matterBody, { x: this.#matterBody.position.x, y: wt.y.v + oy });
|
|
84
|
+
}
|
|
85
|
+
if (wt.scaleX.dirty || wt.scaleY.dirty) {
|
|
86
|
+
const scaleDiffX = wt.scaleX.v / this.#lastScaleX;
|
|
87
|
+
const scaleDiffY = wt.scaleY.v / this.#lastScaleY;
|
|
88
|
+
Matter.Body.scale(this.#matterBody, scaleDiffX, scaleDiffY);
|
|
89
|
+
this.#lastScaleX = wt.scaleX.v;
|
|
90
|
+
this.#lastScaleY = wt.scaleY.v;
|
|
91
|
+
}
|
|
92
|
+
if (wt.rotation.dirty) Matter.Body.setAngle(this.#matterBody, wt.rotation.v);
|
|
93
|
+
|
|
94
|
+
const lt = this.#go._lt;
|
|
95
|
+
if (!wt.x.dirty || !wt.y.dirty || !wt.rotation.dirty) {
|
|
96
|
+
const { x, y, rotation, newWorldX, newWorldY, newWorldRotation } =
|
|
97
|
+
worldToLocalWithNewWorld(
|
|
98
|
+
wt,
|
|
99
|
+
lt,
|
|
100
|
+
this.#matterBody.position.x - ox,
|
|
101
|
+
this.#matterBody.position.y - oy,
|
|
102
|
+
this.#matterBody.angle
|
|
103
|
+
);
|
|
104
|
+
if (!wt.x.dirty) { lt.x.v = x; wt.x.v = newWorldX; }
|
|
105
|
+
if (!wt.y.dirty) { lt.y.v = y; wt.y.v = newWorldY; }
|
|
106
|
+
if (!wt.rotation.dirty) { lt.rotation.v = rotation; wt.rotation.v = newWorldRotation; }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
destroy() {
|
|
111
|
+
this.#removeBody();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get collider() { return this.#collider; }
|
|
115
|
+
set collider(value: Collider | undefined) {
|
|
116
|
+
this.#removeBody();
|
|
117
|
+
this.#collider = value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get isStatic() {
|
|
121
|
+
if (this.#matterBody) return this.#matterBody.isStatic;
|
|
122
|
+
else return this.#isStatic;
|
|
123
|
+
}
|
|
124
|
+
set isStatic(value: boolean) {
|
|
125
|
+
this.#isStatic = value;
|
|
126
|
+
if (this.#matterBody) Matter.Body.setStatic(this.#matterBody, value);
|
|
127
|
+
this.#setDebugRenderStyle();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get isSensor() {
|
|
131
|
+
if (this.#matterBody) return this.#matterBody.isSensor;
|
|
132
|
+
else return this.#isSensor;
|
|
133
|
+
}
|
|
134
|
+
set isSensor(value: boolean) {
|
|
135
|
+
this.#isSensor = value;
|
|
136
|
+
if (this.#matterBody) this.#matterBody.isSensor = value;
|
|
137
|
+
this.#setDebugRenderStyle();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get velocityX() {
|
|
141
|
+
if (this.#matterBody) return this.#matterBody.velocity.x;
|
|
142
|
+
else return this.#velocityX;
|
|
143
|
+
}
|
|
144
|
+
set velocityX(value: number) {
|
|
145
|
+
this.#velocityX = value;
|
|
146
|
+
if (this.#matterBody) Matter.Body.setVelocity(this.#matterBody, { x: value, y: this.#velocityY });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get velocityY() {
|
|
150
|
+
if (this.#matterBody) return this.#matterBody.velocity.y;
|
|
151
|
+
else return this.#velocityY;
|
|
152
|
+
}
|
|
153
|
+
set velocityY(value: number) {
|
|
154
|
+
this.#velocityY = value;
|
|
155
|
+
if (this.#matterBody) Matter.Body.setVelocity(this.#matterBody, { x: this.#velocityX, y: value });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
get fixedRotation() { return this.#fixedRotation; }
|
|
159
|
+
set fixedRotation(value: boolean) {
|
|
160
|
+
this.#fixedRotation = value;
|
|
161
|
+
if (this.#matterBody) {
|
|
162
|
+
if (value) {
|
|
163
|
+
Matter.Body.setInertia(this.#matterBody, Infinity);
|
|
164
|
+
Matter.Body.setAngularVelocity(this.#matterBody, 0);
|
|
165
|
+
} else {
|
|
166
|
+
Matter.Body.setInertia(this.#matterBody, this.#initialInertia!);
|
|
167
|
+
Matter.Body.setAngularVelocity(this.#matterBody, 0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#setDebugRenderStyle() {
|
|
173
|
+
if (!debugMode || !this.#matterBody) return;
|
|
174
|
+
|
|
175
|
+
if (this.#isSensor) {
|
|
176
|
+
this.#matterBody.render.fillStyle = 'rgba(255,255,0,0.1)';
|
|
177
|
+
this.#matterBody.render.strokeStyle = 'rgba(255,255,0,0.25)';
|
|
178
|
+
this.#matterBody.render.lineWidth = 1;
|
|
179
|
+
}
|
|
180
|
+
else if (this.#isStatic) {
|
|
181
|
+
this.#matterBody.render.fillStyle = 'rgba(255,0,0,0.1)';
|
|
182
|
+
this.#matterBody.render.strokeStyle = 'rgba(255,0,0,0.25)';
|
|
183
|
+
this.#matterBody.render.lineWidth = 1;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
this.#matterBody.render.fillStyle = 'rgba(0,255,0,0.1)';
|
|
187
|
+
this.#matterBody.render.strokeStyle = 'rgba(0,255,0,0.25)';
|
|
188
|
+
this.#matterBody.render.lineWidth = 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Container } from 'pixi.js';
|
|
2
|
+
import { LocalTransform } from './transform';
|
|
3
|
+
|
|
4
|
+
export class GameObjectRendering {
|
|
5
|
+
_container = new Container({ sortableChildren: true });
|
|
6
|
+
#yBasedDrawOrder = false;
|
|
7
|
+
|
|
8
|
+
addChild(child: GameObjectRendering) { this._container.addChild(child._container); }
|
|
9
|
+
removeChild(child: GameObjectRendering) { this._container.removeChild(child._container); }
|
|
10
|
+
destroy() { this._container.destroy({ children: true }); }
|
|
11
|
+
|
|
12
|
+
applyChanges(lt: LocalTransform) {
|
|
13
|
+
if (lt.x.dirty) this._container.x = lt.x.v;
|
|
14
|
+
if (lt.y.dirty) { this._container.y = lt.y.v; if (this.#yBasedDrawOrder) this.drawOrder = lt.y.v; }
|
|
15
|
+
if (lt.pivotX.dirty) this._container.pivot.x = lt.pivotX.v;
|
|
16
|
+
if (lt.pivotY.dirty) this._container.pivot.y = lt.pivotY.v;
|
|
17
|
+
if (lt.scaleX.dirty) this._container.scale.x = lt.scaleX.v;
|
|
18
|
+
if (lt.scaleY.dirty) this._container.scale.y = lt.scaleY.v;
|
|
19
|
+
if (lt.rotation.dirty) this._container.rotation = lt.rotation.v;
|
|
20
|
+
if (lt.alpha.dirty) this._container.alpha = lt.alpha.v;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get drawOrder() { return this._container.zIndex; }
|
|
24
|
+
set drawOrder(v: number) { this._container.zIndex = v; }
|
|
25
|
+
get yBasedDrawOrder() { return this.#yBasedDrawOrder; }
|
|
26
|
+
set yBasedDrawOrder(v: boolean) { this.#yBasedDrawOrder = v; if (v) this.drawOrder = this._container.y; }
|
|
27
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { EventEmitter, EventMap } from '@webtaku/event-emitter';
|
|
2
|
+
import { Container, Sprite } from 'pixi.js';
|
|
3
|
+
import { textureLoader } from '../asset/loaders/texture';
|
|
4
|
+
import { World } from '../world/world';
|
|
5
|
+
import { Collider, GameObjectPhysics } from './game-object-physics';
|
|
6
|
+
import { GameObjectRendering } from './game-object-rendering';
|
|
7
|
+
import { LocalTransform, WorldTransform } from './transform';
|
|
8
|
+
|
|
9
|
+
export class GameObject<E extends EventMap = EventMap> extends EventEmitter<E & {
|
|
10
|
+
update: (dt: number) => void
|
|
11
|
+
}> {
|
|
12
|
+
_lt = new LocalTransform();
|
|
13
|
+
_wt = new WorldTransform();
|
|
14
|
+
|
|
15
|
+
_rendering = new GameObjectRendering();
|
|
16
|
+
#physics = new GameObjectPhysics(this);
|
|
17
|
+
|
|
18
|
+
#world?: World;
|
|
19
|
+
#parent?: GameObject;
|
|
20
|
+
#children: GameObject[] = [];
|
|
21
|
+
|
|
22
|
+
data: Record<string, any> = {};
|
|
23
|
+
|
|
24
|
+
protected _setWorld(world: World) {
|
|
25
|
+
this.#world = world;
|
|
26
|
+
for (const child of this.#children) {
|
|
27
|
+
child._setWorld(world);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
_getWorld() { return this.#world; }
|
|
31
|
+
|
|
32
|
+
add(...children: GameObject[]) {
|
|
33
|
+
for (const child of children) {
|
|
34
|
+
if (child.#parent) {
|
|
35
|
+
const idx = child.#parent.#children.indexOf(child);
|
|
36
|
+
if (idx !== -1) child.#parent.#children.splice(idx, 1);
|
|
37
|
+
child.#parent._rendering.removeChild(child._rendering);
|
|
38
|
+
}
|
|
39
|
+
if (this.#world) child._setWorld(this.#world);
|
|
40
|
+
child.#parent = this;
|
|
41
|
+
|
|
42
|
+
this.#children.push(child);
|
|
43
|
+
this._rendering.addChild(child._rendering);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
remove() {
|
|
48
|
+
if (this.#parent) {
|
|
49
|
+
const idx = this.#parent.#children.indexOf(this);
|
|
50
|
+
if (idx !== -1) this.#parent.#children.splice(idx, 1);
|
|
51
|
+
this.#parent = undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const child of this.#children) {
|
|
55
|
+
child.#parent = undefined;
|
|
56
|
+
child.remove();
|
|
57
|
+
}
|
|
58
|
+
this.#children.length = 0;
|
|
59
|
+
|
|
60
|
+
this._rendering.destroy();
|
|
61
|
+
this.#physics.destroy();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected update(dt: number): void { }
|
|
65
|
+
|
|
66
|
+
protected _afterRender() { }
|
|
67
|
+
_engineUpdate(dt: number, pt: WorldTransform) {
|
|
68
|
+
this.update(dt);
|
|
69
|
+
|
|
70
|
+
this._wt.update(pt, this._lt);
|
|
71
|
+
this.#physics.applyChanges();
|
|
72
|
+
this._rendering.applyChanges(this._lt);
|
|
73
|
+
this._afterRender();
|
|
74
|
+
this._lt.markClean();
|
|
75
|
+
|
|
76
|
+
(this as any).emit('update', dt);
|
|
77
|
+
|
|
78
|
+
for (const child of this.#children) {
|
|
79
|
+
child._engineUpdate(dt, this._wt);
|
|
80
|
+
}
|
|
81
|
+
this._wt.markClean();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
constructor(opts?: GameObjectOptions) {
|
|
85
|
+
super();
|
|
86
|
+
if (opts) {
|
|
87
|
+
if (opts.x !== undefined) this.x = opts.x;
|
|
88
|
+
if (opts.y !== undefined) this.y = opts.y;
|
|
89
|
+
if (opts.pivotX !== undefined) this.pivotX = opts.pivotX;
|
|
90
|
+
if (opts.pivotY !== undefined) this.pivotY = opts.pivotY;
|
|
91
|
+
if (opts.scale !== undefined) this.scale = opts.scale;
|
|
92
|
+
if (opts.scaleX !== undefined) this.scaleX = opts.scaleX;
|
|
93
|
+
if (opts.scaleY !== undefined) this.scaleY = opts.scaleY;
|
|
94
|
+
if (opts.rotation !== undefined) this.rotation = opts.rotation;
|
|
95
|
+
if (opts.alpha !== undefined) this.alpha = opts.alpha;
|
|
96
|
+
|
|
97
|
+
if (opts.drawOrder !== undefined) this.drawOrder = opts.drawOrder;
|
|
98
|
+
if (opts.yBasedDrawOrder !== undefined) this.yBasedDrawOrder = opts.yBasedDrawOrder;
|
|
99
|
+
|
|
100
|
+
if (opts.collider !== undefined) this.collider = opts.collider;
|
|
101
|
+
if (opts.isStatic !== undefined) this.isStatic = opts.isStatic;
|
|
102
|
+
if (opts.isSensor !== undefined) this.isSensor = opts.isSensor;
|
|
103
|
+
if (opts.velocityX !== undefined) this.velocityX = opts.velocityX;
|
|
104
|
+
if (opts.velocityY !== undefined) this.velocityY = opts.velocityY;
|
|
105
|
+
if (opts.fixedRotation !== undefined) this.fixedRotation = opts.fixedRotation;
|
|
106
|
+
|
|
107
|
+
if (opts.image !== undefined) this.image = opts.image;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get x() { return this._lt.x.v; }
|
|
112
|
+
set x(v: number) { this._lt.x.v = v; }
|
|
113
|
+
get y() { return this._lt.y.v; }
|
|
114
|
+
set y(v: number) { this._lt.y.v = v; }
|
|
115
|
+
get pivotX() { return this._lt.pivotX.v; }
|
|
116
|
+
set pivotX(v: number) { this._lt.pivotX.v = v; }
|
|
117
|
+
get pivotY() { return this._lt.pivotY.v; }
|
|
118
|
+
set pivotY(v: number) { this._lt.pivotY.v = v; }
|
|
119
|
+
get scale() { return this._lt.scaleX.v; }
|
|
120
|
+
set scale(v: number) { this._lt.scaleX.v = v; this._lt.scaleY.v = v; }
|
|
121
|
+
get scaleX() { return this._lt.scaleX.v; }
|
|
122
|
+
set scaleX(v: number) { this._lt.scaleX.v = v; }
|
|
123
|
+
get scaleY() { return this._lt.scaleY.v; }
|
|
124
|
+
set scaleY(v: number) { this._lt.scaleY.v = v; }
|
|
125
|
+
get rotation() { return this._lt.rotation.v; }
|
|
126
|
+
set rotation(v: number) { this._lt.rotation.v = v; }
|
|
127
|
+
get alpha() { return this._lt.alpha.v; }
|
|
128
|
+
set alpha(v: number) { this._lt.alpha.v = v; }
|
|
129
|
+
|
|
130
|
+
get drawOrder() { return this._rendering.drawOrder; }
|
|
131
|
+
set drawOrder(v: number) { this._rendering.drawOrder = v; }
|
|
132
|
+
get yBasedDrawOrder() { return this._rendering.yBasedDrawOrder; }
|
|
133
|
+
set yBasedDrawOrder(v: boolean) { this._rendering.yBasedDrawOrder = v; }
|
|
134
|
+
|
|
135
|
+
get collider() { return this.#physics.collider; }
|
|
136
|
+
set collider(v: Collider | undefined) { this.#physics.collider = v; }
|
|
137
|
+
get isStatic() { return this.#physics.isStatic; }
|
|
138
|
+
set isStatic(v: boolean) { this.#physics.isStatic = v; }
|
|
139
|
+
get isSensor() { return this.#physics.isSensor; }
|
|
140
|
+
set isSensor(v: boolean) { this.#physics.isSensor = v; }
|
|
141
|
+
get velocityX() { return this.#physics.velocityX; }
|
|
142
|
+
set velocityX(v: number) { this.#physics.velocityX = v; }
|
|
143
|
+
get velocityY() { return this.#physics.velocityY; }
|
|
144
|
+
set velocityY(v: number) { this.#physics.velocityY = v; }
|
|
145
|
+
get fixedRotation() { return this.#physics.fixedRotation; }
|
|
146
|
+
set fixedRotation(v: boolean) { this.#physics.fixedRotation = v; }
|
|
147
|
+
|
|
148
|
+
_addPixiChild(child: Container) { this._rendering._container.addChild(child); }
|
|
149
|
+
|
|
150
|
+
#image?: string;
|
|
151
|
+
get image() { return this.#image; }
|
|
152
|
+
set image(value: string | undefined) {
|
|
153
|
+
if (value) {
|
|
154
|
+
if (!textureLoader.checkLoaded(value)) {
|
|
155
|
+
console.info(`Image not preloaded. Loading now: ${value}`);
|
|
156
|
+
}
|
|
157
|
+
textureLoader.load(value).then((texture) => {
|
|
158
|
+
if (texture) this._addPixiChild(new Sprite({
|
|
159
|
+
texture,
|
|
160
|
+
anchor: { x: 0.5, y: 0.5 },
|
|
161
|
+
zIndex: -999999,
|
|
162
|
+
}));
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export type GameObjectOptions = {
|
|
169
|
+
x?: number;
|
|
170
|
+
y?: number;
|
|
171
|
+
pivotX?: number;
|
|
172
|
+
pivotY?: number;
|
|
173
|
+
scale?: number;
|
|
174
|
+
scaleX?: number;
|
|
175
|
+
scaleY?: number;
|
|
176
|
+
rotation?: number;
|
|
177
|
+
alpha?: number;
|
|
178
|
+
|
|
179
|
+
drawOrder?: number;
|
|
180
|
+
yBasedDrawOrder?: boolean;
|
|
181
|
+
|
|
182
|
+
collider?: Collider;
|
|
183
|
+
isStatic?: boolean;
|
|
184
|
+
isSensor?: boolean;
|
|
185
|
+
velocityX?: number;
|
|
186
|
+
velocityY?: number;
|
|
187
|
+
fixedRotation?: boolean;
|
|
188
|
+
|
|
189
|
+
image?: string;
|
|
190
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
class DirtyNumber {
|
|
2
|
+
#v: number;
|
|
3
|
+
#dirty: boolean;
|
|
4
|
+
|
|
5
|
+
constructor(v: number) {
|
|
6
|
+
this.#v = v;
|
|
7
|
+
this.#dirty = false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get dirty(): boolean {
|
|
11
|
+
return this.#dirty;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get v(): number {
|
|
15
|
+
return this.#v;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set v(v: number) {
|
|
19
|
+
if (this.#v !== v) this.#dirty = true;
|
|
20
|
+
this.#v = v;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
markClean() {
|
|
24
|
+
this.#dirty = false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class LocalTransform {
|
|
29
|
+
x = new DirtyNumber(0);
|
|
30
|
+
y = new DirtyNumber(0);
|
|
31
|
+
pivotX = new DirtyNumber(0);
|
|
32
|
+
pivotY = new DirtyNumber(0);
|
|
33
|
+
scaleX = new DirtyNumber(1);
|
|
34
|
+
scaleY = new DirtyNumber(1);
|
|
35
|
+
rotation = new DirtyNumber(0);
|
|
36
|
+
alpha = new DirtyNumber(1);
|
|
37
|
+
|
|
38
|
+
markClean() {
|
|
39
|
+
this.x.markClean();
|
|
40
|
+
this.y.markClean();
|
|
41
|
+
this.pivotX.markClean();
|
|
42
|
+
this.pivotY.markClean();
|
|
43
|
+
this.scaleX.markClean();
|
|
44
|
+
this.scaleY.markClean();
|
|
45
|
+
this.rotation.markClean();
|
|
46
|
+
this.alpha.markClean();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class WorldTransform {
|
|
51
|
+
x = new DirtyNumber(0);
|
|
52
|
+
y = new DirtyNumber(0);
|
|
53
|
+
scaleX = new DirtyNumber(1);
|
|
54
|
+
scaleY = new DirtyNumber(1);
|
|
55
|
+
rotation = new DirtyNumber(0);
|
|
56
|
+
alpha = new DirtyNumber(1);
|
|
57
|
+
|
|
58
|
+
update(parent: WorldTransform, local: LocalTransform) {
|
|
59
|
+
const rx = local.x.v * parent.scaleX.v;
|
|
60
|
+
const ry = local.y.v * parent.scaleY.v;
|
|
61
|
+
const pCos = Math.cos(parent.rotation.v);
|
|
62
|
+
const pSin = Math.sin(parent.rotation.v);
|
|
63
|
+
|
|
64
|
+
this.scaleX.v = parent.scaleX.v * local.scaleX.v;
|
|
65
|
+
this.scaleY.v = parent.scaleY.v * local.scaleY.v;
|
|
66
|
+
|
|
67
|
+
const pivotX = local.pivotX.v * this.scaleX.v;
|
|
68
|
+
const pivotY = local.pivotY.v * this.scaleY.v;
|
|
69
|
+
const cos = Math.cos(local.rotation.v);
|
|
70
|
+
const sin = Math.sin(local.rotation.v);
|
|
71
|
+
|
|
72
|
+
this.x.v = parent.x.v + (rx * pCos - ry * pSin) - (pivotX * cos - pivotY * sin);
|
|
73
|
+
this.y.v = parent.y.v + (rx * pSin + ry * pCos) - (pivotX * sin + pivotY * cos);
|
|
74
|
+
|
|
75
|
+
this.rotation.v = parent.rotation.v + local.rotation.v;
|
|
76
|
+
this.alpha.v = parent.alpha.v * local.alpha.v;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
markClean() {
|
|
80
|
+
this.x.markClean();
|
|
81
|
+
this.y.markClean();
|
|
82
|
+
this.scaleX.markClean();
|
|
83
|
+
this.scaleY.markClean();
|
|
84
|
+
this.rotation.markClean();
|
|
85
|
+
this.alpha.markClean();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function localOffsetToWorld(
|
|
90
|
+
world: WorldTransform,
|
|
91
|
+
ox: number,
|
|
92
|
+
oy: number
|
|
93
|
+
): { x: number; y: number } {
|
|
94
|
+
const cos = Math.cos(world.rotation.v);
|
|
95
|
+
const sin = Math.sin(world.rotation.v);
|
|
96
|
+
const sx = ox * world.scaleX.v;
|
|
97
|
+
const sy = oy * world.scaleY.v;
|
|
98
|
+
|
|
99
|
+
const x = sx * cos - sy * sin;
|
|
100
|
+
const y = sx * sin + sy * cos;
|
|
101
|
+
return { x, y };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function worldToLocalWithNewWorld(
|
|
105
|
+
world: WorldTransform,
|
|
106
|
+
local: LocalTransform,
|
|
107
|
+
targetWorldX: number,
|
|
108
|
+
targetWorldY: number,
|
|
109
|
+
targetWorldRotation: number
|
|
110
|
+
): {
|
|
111
|
+
x: number;
|
|
112
|
+
y: number;
|
|
113
|
+
rotation: number;
|
|
114
|
+
newWorldX: number;
|
|
115
|
+
newWorldY: number;
|
|
116
|
+
newWorldRotation: number;
|
|
117
|
+
} {
|
|
118
|
+
const invLocalScaleX = local.scaleX.v !== 0 ? 1 / local.scaleX.v : 0;
|
|
119
|
+
const invLocalScaleY = local.scaleY.v !== 0 ? 1 / local.scaleY.v : 0;
|
|
120
|
+
|
|
121
|
+
const parentScaleX = world.scaleX.v * invLocalScaleX;
|
|
122
|
+
const parentScaleY = world.scaleY.v * invLocalScaleY;
|
|
123
|
+
const parentRot = world.rotation.v - local.rotation.v;
|
|
124
|
+
|
|
125
|
+
const pCos = Math.cos(parentRot);
|
|
126
|
+
const pSin = Math.sin(parentRot);
|
|
127
|
+
|
|
128
|
+
const cosOld = Math.cos(local.rotation.v);
|
|
129
|
+
const sinOld = Math.sin(local.rotation.v);
|
|
130
|
+
|
|
131
|
+
const pivotX = local.pivotX.v * world.scaleX.v;
|
|
132
|
+
const pivotY = local.pivotY.v * world.scaleY.v;
|
|
133
|
+
|
|
134
|
+
const rx0 = local.x.v * parentScaleX;
|
|
135
|
+
const ry0 = local.y.v * parentScaleY;
|
|
136
|
+
|
|
137
|
+
const parentX = world.x.v - (rx0 * pCos - ry0 * pSin) + (pivotX * cosOld - pivotY * sinOld);
|
|
138
|
+
const parentY = world.y.v - (rx0 * pSin + ry0 * pCos) + (pivotX * sinOld + pivotY * cosOld);
|
|
139
|
+
|
|
140
|
+
const rotation = targetWorldRotation - parentRot;
|
|
141
|
+
|
|
142
|
+
const cosNew = Math.cos(rotation);
|
|
143
|
+
const sinNew = Math.sin(rotation);
|
|
144
|
+
|
|
145
|
+
const tx = (targetWorldX - parentX) + (pivotX * cosNew - pivotY * sinNew);
|
|
146
|
+
const ty = (targetWorldY - parentY) + (pivotX * sinNew + pivotY * cosNew);
|
|
147
|
+
|
|
148
|
+
const rx = tx * pCos + ty * pSin;
|
|
149
|
+
const ry = -tx * pSin + ty * pCos;
|
|
150
|
+
|
|
151
|
+
const invParentScaleX = parentScaleX !== 0 ? 1 / parentScaleX : 0;
|
|
152
|
+
const invParentScaleY = parentScaleY !== 0 ? 1 / parentScaleY : 0;
|
|
153
|
+
|
|
154
|
+
const x = rx * invParentScaleX;
|
|
155
|
+
const y = ry * invParentScaleY;
|
|
156
|
+
|
|
157
|
+
const newRx = x * parentScaleX;
|
|
158
|
+
const newRy = y * parentScaleY;
|
|
159
|
+
const newWorldX = parentX + (newRx * pCos - newRy * pSin) - (pivotX * cosNew - pivotY * sinNew);
|
|
160
|
+
const newWorldY = parentY + (newRx * pSin + newRy * pCos) - (pivotX * sinNew + pivotY * cosNew);
|
|
161
|
+
const newWorldRotation = parentRot + rotation;
|
|
162
|
+
|
|
163
|
+
return { x, y, rotation, newWorldX, newWorldY, newWorldRotation };
|
|
164
|
+
}
|