evotars 0.0.1

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.
Files changed (49) hide show
  1. package/.editorconfig +13 -0
  2. package/.prettierrc.json +3 -0
  3. package/LICENSE +21 -0
  4. package/README.md +40 -0
  5. package/index.html +13 -0
  6. package/package.json +20 -0
  7. package/public/fonts/Rubik.ttf +0 -0
  8. package/public/sounds/jump.mp3 +0 -0
  9. package/public/sprites/agent/agent.json +166 -0
  10. package/public/sprites/agent/agent.png +0 -0
  11. package/public/sprites/cat/cat.json +231 -0
  12. package/public/sprites/cat/cat.png +0 -0
  13. package/public/sprites/dude/dude.json +231 -0
  14. package/public/sprites/dude/dude.png +0 -0
  15. package/public/sprites/girl/girl.json +231 -0
  16. package/public/sprites/girl/girl.png +0 -0
  17. package/public/sprites/nerd/nerd.json +231 -0
  18. package/public/sprites/nerd/nerd.png +0 -0
  19. package/public/sprites/senior/senior.json +231 -0
  20. package/public/sprites/senior/senior.png +0 -0
  21. package/public/sprites/sith/sith.json +166 -0
  22. package/public/sprites/sith/sith.png +0 -0
  23. package/src/app.ts +63 -0
  24. package/src/config/config.ts +133 -0
  25. package/src/config/constants.ts +1 -0
  26. package/src/dudes.ts +71 -0
  27. package/src/entities/Dude.ts +453 -0
  28. package/src/entities/DudeEmoteSpitter.ts +60 -0
  29. package/src/entities/DudeMessage.ts +163 -0
  30. package/src/entities/DudeName.ts +39 -0
  31. package/src/entities/DudeSpriteContainer.ts +68 -0
  32. package/src/entities/DudeTrailEffect.ts +72 -0
  33. package/src/helpers/timer.ts +43 -0
  34. package/src/helpers/types.ts +11 -0
  35. package/src/index.ts +75 -0
  36. package/src/services/assetsLoader.ts +27 -0
  37. package/src/services/dudesManager.ts +305 -0
  38. package/src/services/soundService.ts +28 -0
  39. package/src/services/spriteProvider.ts +93 -0
  40. package/src/styles.css +16 -0
  41. package/src/types/dto/action.ts +68 -0
  42. package/src/types/dto/chatter.ts +4 -0
  43. package/src/types/dto/event.ts +9 -0
  44. package/src/types/dto/index.ts +6 -0
  45. package/src/types/dto/message.ts +8 -0
  46. package/src/types/dto/settings.ts +5 -0
  47. package/src/types/dto/user.ts +13 -0
  48. package/src/types/index.ts +1 -0
  49. package/tsconfig.json +23 -0
package/src/app.ts ADDED
@@ -0,0 +1,63 @@
1
+ import * as PIXI from "pixi.js";
2
+ import { Dude } from "./entities/Dude";
3
+ import { timers } from "./helpers/timer";
4
+ import { assetsLoader } from "./services/assetsLoader";
5
+ import { dudesManager } from "./services/dudesManager";
6
+ import { SoundManifest, soundService } from "./services/soundService";
7
+ import { SettingsEntity } from "./types";
8
+
9
+ export type AppOptions = {
10
+ manifest: PIXI.AssetsManifest;
11
+ sound: SoundManifest;
12
+ };
13
+
14
+ export class App {
15
+ public stage: PIXI.Container = new PIXI.Container();
16
+ public dudes: Record<string, Dude> = {};
17
+
18
+ public chatterIds: string[] = [];
19
+
20
+ public settings: SettingsEntity = {};
21
+
22
+ public renderer!: PIXI.Renderer;
23
+
24
+ public async init(options: AppOptions, element: HTMLElement): Promise<void> {
25
+ await assetsLoader.load(options.manifest);
26
+ soundService.init(options.sound);
27
+
28
+ this.renderer = new PIXI.Renderer({
29
+ width: element.clientWidth,
30
+ height: element.clientHeight,
31
+ backgroundAlpha: 0,
32
+ });
33
+
34
+ element.appendChild(this.renderer.view as HTMLCanvasElement);
35
+
36
+ window.onresize = (): void => {
37
+ this.renderer.resize(element.clientWidth, element.clientHeight);
38
+ };
39
+
40
+ this.stage.sortableChildren = true;
41
+
42
+ dudesManager.subscribe({
43
+ onAdd: (dude: Dude) => this.stage.addChild(dude.container),
44
+ onDelete: (dude: Dude) => {
45
+ this.stage.removeChild(dude.container);
46
+ this.stage.removeChild(dude.trail.container);
47
+ },
48
+ });
49
+ }
50
+
51
+ public updateSettings(data: SettingsEntity): void {
52
+ this.settings = data;
53
+ }
54
+
55
+ public update(): void {
56
+ timers.tick();
57
+ dudesManager.update();
58
+
59
+ this.renderer.render(this.stage);
60
+ }
61
+ }
62
+
63
+ export const app = new App();
@@ -0,0 +1,133 @@
1
+ export type Sprites = {
2
+ [key: string]: SpriteConfig;
3
+ };
4
+
5
+ export type Config = {
6
+ sprites: Sprites;
7
+ };
8
+
9
+ export type SpriteConfig = {
10
+ name: string;
11
+ w: number;
12
+ h: number;
13
+ collider: {
14
+ x: number;
15
+ y: number;
16
+ w: number;
17
+ h: number;
18
+ };
19
+ pivot: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ };
24
+
25
+ export const config: Config = {
26
+ sprites: {
27
+ sith: {
28
+ name: 'sith',
29
+ w: 32,
30
+ h: 32,
31
+ collider: {
32
+ x: 8,
33
+ y: 5,
34
+ w: 16,
35
+ h: 20,
36
+ },
37
+ pivot: {
38
+ x: 0,
39
+ y: 9,
40
+ },
41
+ },
42
+ agent: {
43
+ name: 'agent',
44
+ w: 32,
45
+ h: 32,
46
+ collider: {
47
+ x: 8,
48
+ y: 7,
49
+ w: 16,
50
+ h: 18,
51
+ },
52
+ pivot: {
53
+ x: 0,
54
+ y: 9,
55
+ },
56
+ },
57
+ nerd: {
58
+ name: 'nerd',
59
+ w: 32,
60
+ h: 32,
61
+ collider: {
62
+ x: 8,
63
+ y: 7,
64
+ w: 16,
65
+ h: 18,
66
+ },
67
+ pivot: {
68
+ x: 0,
69
+ y: 9,
70
+ },
71
+ },
72
+ girl: {
73
+ name: 'girl',
74
+ w: 32,
75
+ h: 32,
76
+ collider: {
77
+ x: 8,
78
+ y: 5,
79
+ w: 16,
80
+ h: 20,
81
+ },
82
+ pivot: {
83
+ x: 0,
84
+ y: 9,
85
+ },
86
+ },
87
+ cat: {
88
+ name: 'cat',
89
+ w: 32,
90
+ h: 32,
91
+ collider: {
92
+ x: 8,
93
+ y: 4,
94
+ w: 16,
95
+ h: 21,
96
+ },
97
+ pivot: {
98
+ x: 0,
99
+ y: 9,
100
+ },
101
+ },
102
+ senior: {
103
+ name: 'senior',
104
+ w: 32,
105
+ h: 32,
106
+ collider: {
107
+ x: 8,
108
+ y: 4,
109
+ w: 16,
110
+ h: 21,
111
+ },
112
+ pivot: {
113
+ x: 0,
114
+ y: 9,
115
+ },
116
+ },
117
+ sponge: {
118
+ name: 'sponge',
119
+ w: 32,
120
+ h: 32,
121
+ collider: {
122
+ x: 8,
123
+ y: 7,
124
+ w: 16,
125
+ h: 18,
126
+ },
127
+ pivot: {
128
+ x: 0,
129
+ y: 9,
130
+ },
131
+ },
132
+ },
133
+ };
@@ -0,0 +1 @@
1
+ export const FIXED_DELTA_TIME = 0.02 * 1000;
package/src/dudes.ts ADDED
@@ -0,0 +1,71 @@
1
+ import {
2
+ MessageEntity,
3
+ RaidData,
4
+ SettingsEntity,
5
+ TwitchChatterEntity,
6
+ UserActionEntity,
7
+ } from "./types";
8
+ import { AppOptions, app } from "./app";
9
+ import { dudesManager } from "./services/dudesManager";
10
+
11
+ export class Dudes {
12
+ private isRendered = false;
13
+
14
+ constructor(private readonly root: HTMLElement) {}
15
+
16
+ processMessage = (data: MessageEntity) => dudesManager.processMessage(data);
17
+
18
+ processAction = (data: UserActionEntity) => dudesManager.processAction(data);
19
+
20
+ processChatters = (data: TwitchChatterEntity[]) =>
21
+ dudesManager.processChatters(data);
22
+
23
+ processRaid = (data: RaidData) => dudesManager.processRaid(data);
24
+
25
+ updateSettings(data: SettingsEntity) {
26
+ app.updateSettings(data);
27
+ }
28
+
29
+ async run(options: AppOptions) {
30
+ if (this.isRendered) {
31
+ return;
32
+ }
33
+
34
+ this.isRendered = true;
35
+
36
+ const init = async (): Promise<void> => {
37
+ await app.init(options, this.root);
38
+
39
+ let lastTime = performance.now();
40
+ let lastFrame = -1;
41
+
42
+ const maxFps = 60;
43
+
44
+ const minElapsedMS = 1000 / maxFps;
45
+ const maxElapsedMS = 100;
46
+
47
+ requestAnimationFrame(animate);
48
+
49
+ function animate(currentTime = performance.now()): void {
50
+ let elapsedMS = currentTime - lastTime;
51
+
52
+ if (elapsedMS > maxElapsedMS) {
53
+ elapsedMS = maxElapsedMS;
54
+ }
55
+
56
+ const delta = (currentTime - lastFrame) | 0;
57
+
58
+ if (delta > minElapsedMS) {
59
+ lastFrame = currentTime - (delta % minElapsedMS);
60
+ lastTime = currentTime;
61
+
62
+ app.update();
63
+ }
64
+
65
+ requestAnimationFrame(animate);
66
+ }
67
+ };
68
+
69
+ await init();
70
+ }
71
+ }
@@ -0,0 +1,453 @@
1
+ import * as TWEEN from '@tweenjs/tween.js';
2
+ import * as PIXI from 'pixi.js';
3
+ import { app } from '../app';
4
+ import { SpriteConfig } from '../config/config';
5
+ import { FIXED_DELTA_TIME } from '../config/constants';
6
+ import { Timer } from '../helpers/timer';
7
+ import { dudesManager } from '../services/dudesManager';
8
+ import { soundService } from '../services/soundService';
9
+ import {
10
+ DudeSpriteLayers,
11
+ DudeSpriteTags,
12
+ DudeTagAnimatedSprites,
13
+ spriteProvider,
14
+ } from '../services/spriteProvider';
15
+ import { DudeEmoteSpitter } from './DudeEmoteSpitter';
16
+ import { DudeMessage } from './DudeMessage';
17
+ import { DudeName } from './DudeName';
18
+ import { DudeSpriteContainer } from './DudeSpriteContainer';
19
+ import { DudeTrailEffect } from './DudeTrailEffect';
20
+
21
+ export type DudeProps = {
22
+ name?: string;
23
+ sprite?: SpriteConfig;
24
+ color?: PIXI.Color;
25
+ direction?: number;
26
+ scale?: number;
27
+ isAnonymous?: boolean;
28
+ zIndex?: number;
29
+ };
30
+
31
+ export type DudeSpawnProps = {
32
+ isFalling?: boolean;
33
+ positionX?: number;
34
+ onComplete?: () => void;
35
+ };
36
+
37
+ export type DudeDepawnProps = {
38
+ onComplete?: () => void;
39
+ };
40
+
41
+ type DudeState = Required<DudeProps>;
42
+
43
+ type UserProps = {
44
+ color?: PIXI.Color;
45
+ };
46
+
47
+ export const DEFAULT_DUDE_SCALE = 4;
48
+
49
+ export class Dude {
50
+ public container: PIXI.Container = new PIXI.Container();
51
+
52
+ private userState: UserProps = {};
53
+
54
+ private state: DudeState = {
55
+ name: '',
56
+ sprite: {
57
+ name: 'dude',
58
+ w: 32,
59
+ h: 32,
60
+ collider: {
61
+ x: 8,
62
+ y: 7,
63
+ w: 16,
64
+ h: 18,
65
+ },
66
+ pivot: {
67
+ x: 0,
68
+ y: 9,
69
+ },
70
+ },
71
+ color: new PIXI.Color('#969696'),
72
+ direction: 1,
73
+ scale: DEFAULT_DUDE_SCALE,
74
+ isAnonymous: false,
75
+ zIndex: 0,
76
+ };
77
+
78
+ private animationState?: DudeSpriteTags;
79
+
80
+ // TODO: Refactor sprite initializing, move logic to separate file
81
+ private sprite?: DudeSpriteContainer;
82
+
83
+ private tagSprites: DudeTagAnimatedSprites =
84
+ spriteProvider.createTagAnimatedSprites(this.state.sprite.name);
85
+
86
+ public trail: DudeTrailEffect = new DudeTrailEffect();
87
+
88
+ private name: DudeName = new DudeName();
89
+
90
+ private message: DudeMessage = new DudeMessage(() => {
91
+ this.state.zIndex = dudesManager.zIndexDudeMax(this.container.zIndex);
92
+ });
93
+
94
+ private emoteSpitter: DudeEmoteSpitter = new DudeEmoteSpitter();
95
+
96
+ private velocity: PIXI.IPointData = {
97
+ x: 0,
98
+ y: 0,
99
+ };
100
+
101
+ private runSpeed = 0.05;
102
+
103
+ private gravity = 0.2;
104
+
105
+ private dashAcc = 3;
106
+
107
+ private isJumping = false;
108
+
109
+ private isDashing = false;
110
+
111
+ private isDespawned = false;
112
+
113
+ landTimer?: Timer;
114
+
115
+ stateTimer?: Timer;
116
+
117
+ scaleTimer?: Timer;
118
+
119
+ spawnTween?: TWEEN.Tween<PIXI.Container>;
120
+
121
+ fadeTween?: TWEEN.Tween<PIXI.Container>;
122
+
123
+ scaleTween?: TWEEN.Tween<Dude>;
124
+
125
+ constructor(props: DudeProps = {}) {
126
+ this.container.addChild(this.name.text);
127
+ this.container.addChild(this.emoteSpitter.container);
128
+ this.container.addChild(this.message.container);
129
+
130
+ void this.setAnimationState(DudeSpriteTags.Idle);
131
+
132
+ if (props.sprite) {
133
+ this.setSprite(props.sprite);
134
+ }
135
+
136
+ this.state = { ...this.state, ...props };
137
+
138
+ this.stateTimer = new Timer(5000, () => {
139
+ if (!this.isJumping) {
140
+ this.setAnimationState(DudeSpriteTags.Run);
141
+ }
142
+ });
143
+ }
144
+
145
+ spawn(props: DudeSpawnProps = { isFalling: false }): void {
146
+ const collider = this.state.sprite.collider;
147
+ const fallingStartY = -(collider.y + collider.h - this.state.sprite.h / 2);
148
+ const spawnY = props.isFalling ? fallingStartY : app.renderer.height;
149
+ const spriteWidth = this.state.sprite.w * this.state.scale;
150
+
151
+ const x =
152
+ props.positionX != undefined
153
+ ? props.positionX * app.renderer.width
154
+ : Math.random() * (app.renderer.width - spriteWidth) + spriteWidth / 2;
155
+ const y = spawnY * this.state.scale;
156
+
157
+ this.container.x = x;
158
+ this.container.y = y;
159
+
160
+ this.state.direction = Math.random() > 0.5 ? 1 : -1;
161
+
162
+ if (!props.isFalling) {
163
+ const zIndex = dudesManager.zIndexDudeMin(this.container.zIndex);
164
+
165
+ this.state.zIndex = zIndex;
166
+ this.container.alpha = 0;
167
+
168
+ this.spawnTween = new TWEEN.Tween(this.container)
169
+ .to({ alpha: 1 }, 500)
170
+ .onComplete(props.onComplete)
171
+ .start();
172
+ }
173
+ }
174
+
175
+ despawn(props: DudeDepawnProps = {}): void {
176
+ if (this.fadeTween && this.fadeTween.isPlaying()) {
177
+ return;
178
+ }
179
+
180
+ this.fadeTween = new TWEEN.Tween(this.container)
181
+ .to({ alpha: 0 }, 1000)
182
+ .onComplete(() => {
183
+ this.isDespawned = true;
184
+ props.onComplete && props.onComplete();
185
+ })
186
+ .start();
187
+ }
188
+
189
+ scale(options: { value: number; duration: number; cooldown: number }): void {
190
+ if (this.scaleTween && this.scaleTween.isPlaying()) {
191
+ return;
192
+ }
193
+
194
+ if (this.scaleTimer && !this.scaleTimer.isCompleted) {
195
+ return;
196
+ }
197
+
198
+ this.scaleTween = new TWEEN.Tween(this)
199
+ .to({ state: { scale: DEFAULT_DUDE_SCALE * (options.value ?? 2) } }, 2000)
200
+ .onComplete(() => {
201
+ this.scaleTimer = new Timer((options.duration ?? 10) * 1000, () => {
202
+ this.scaleTween = new TWEEN.Tween(this)
203
+ .to({ state: { scale: DEFAULT_DUDE_SCALE } }, 2000)
204
+ .start();
205
+ });
206
+ })
207
+ .start();
208
+ }
209
+
210
+ jump(options: {
211
+ velocityX: number;
212
+ velocityY: number;
213
+ cooldown: number;
214
+ }): void {
215
+ if (this.isDespawned) {
216
+ return;
217
+ }
218
+
219
+ if (!this.isJumping) {
220
+ this.isJumping = true;
221
+
222
+ this.velocity.x = this.state.direction * (options.velocityX ?? 3.5);
223
+ this.velocity.y = options.velocityY ?? -8;
224
+
225
+ this.setAnimationState(DudeSpriteTags.Jump);
226
+
227
+ soundService.play('jump');
228
+ }
229
+ }
230
+
231
+ dash(options: { force: number; cooldown: number }): void {
232
+ if (this.isDespawned) {
233
+ return;
234
+ }
235
+
236
+ if (!this.isDashing) {
237
+ this.isDashing = true;
238
+ this.velocity.x = this.state.direction * (options.force ?? 14);
239
+ }
240
+ }
241
+
242
+ setSprite(sprite: SpriteConfig) {
243
+ if (sprite.name && sprite.name != this.state.sprite.name) {
244
+ this.tagSprites = spriteProvider.createTagAnimatedSprites(sprite.name);
245
+
246
+ if (this.animationState) {
247
+ this.setAnimationState(this.animationState, true);
248
+ }
249
+ }
250
+ }
251
+
252
+ setProps(props: DudeProps) {
253
+ if (props.sprite) {
254
+ this.setSprite(props.sprite);
255
+ }
256
+
257
+ this.state = { ...this.state, ...props };
258
+ }
259
+
260
+ setUserProps(props: UserProps) {
261
+ this.userState = { ...this.userState, ...props };
262
+ }
263
+
264
+ private move() {
265
+ const collider = this.state.sprite.collider;
266
+
267
+ const position = {
268
+ x: this.container.position.x,
269
+ y: this.container.position.y,
270
+ };
271
+
272
+ if (this.isDashing) {
273
+ const currentVelocitySign = Math.sign(this.velocity.x);
274
+
275
+ this.velocity.x =
276
+ this.velocity.x -
277
+ (currentVelocitySign * this.dashAcc) / Math.abs(this.velocity.x);
278
+
279
+ if (currentVelocitySign != Math.sign(this.velocity.x)) {
280
+ this.isDashing = false;
281
+ this.velocity.x = 0;
282
+ } else {
283
+ position.x += this.velocity.x;
284
+ }
285
+ } else {
286
+ if (this.stateTimer?.isCompleted) {
287
+ this.stateTimer = new Timer(Math.random() * 5000, () => {
288
+ if (this.animationState == DudeSpriteTags.Idle) {
289
+ this.setAnimationState(DudeSpriteTags.Run);
290
+ } else if (this.animationState == DudeSpriteTags.Run) {
291
+ this.setAnimationState(DudeSpriteTags.Idle);
292
+ }
293
+ });
294
+ }
295
+
296
+ if (this.animationState == DudeSpriteTags.Run) {
297
+ const speed = this.runSpeed * this.state.direction;
298
+ this.velocity.x = speed * FIXED_DELTA_TIME;
299
+ }
300
+
301
+ this.velocity.y = this.velocity.y + this.gravity;
302
+
303
+ position.x += this.velocity.x;
304
+ position.y += this.velocity.y;
305
+
306
+ if (this.isOnGround(position.y)) {
307
+ this.velocity.y = 0;
308
+ this.velocity.x = 0;
309
+
310
+ position.y = app.renderer.height;
311
+
312
+ if (this.animationState == DudeSpriteTags.Fall) {
313
+ this.setAnimationState(DudeSpriteTags.Land);
314
+ this.landTimer = new Timer(200, () => {
315
+ this.setAnimationState(DudeSpriteTags.Idle);
316
+ this.isJumping = false;
317
+ });
318
+ }
319
+ }
320
+
321
+ if (this.velocity.y > 0) {
322
+ this.setAnimationState(DudeSpriteTags.Fall);
323
+ }
324
+ }
325
+
326
+ if (this.animationState != DudeSpriteTags.Idle || this.isDashing) {
327
+ const halfSpriteWidth = (collider.w / 2) * this.state.scale;
328
+
329
+ const left = this.container.x - halfSpriteWidth < 0;
330
+ const right = this.container.x + halfSpriteWidth > app.renderer.width;
331
+
332
+ if (left || right) {
333
+ this.state.direction = -this.state.direction;
334
+ this.velocity.x = -this.velocity.x;
335
+
336
+ if (left) {
337
+ position.x = halfSpriteWidth;
338
+ }
339
+
340
+ if (right) {
341
+ position.x = app.renderer.width - halfSpriteWidth;
342
+ }
343
+ }
344
+ }
345
+
346
+ this.container.position.set(position.x, position.y);
347
+ }
348
+
349
+ update(): void {
350
+ if (this.isDespawned) {
351
+ return;
352
+ }
353
+
354
+ this.landTimer?.tick();
355
+ this.stateTimer?.tick();
356
+ this.scaleTimer?.tick();
357
+
358
+ this.fadeTween?.update();
359
+ this.spawnTween?.update();
360
+ this.scaleTween?.update();
361
+
362
+ this.container.zIndex = this.state.zIndex;
363
+
364
+ const collider = this.state.sprite.collider;
365
+
366
+ this.name.update({
367
+ name: this.state.name,
368
+ isVisible: !this.state.isAnonymous,
369
+ position: {
370
+ y:
371
+ -(this.state.sprite.h / 2 - collider.y + this.state.sprite.pivot.y) *
372
+ this.state.scale,
373
+ },
374
+ });
375
+
376
+ this.message.update({
377
+ position: {
378
+ y: this.name.text.position.y - this.name.text.height - 6,
379
+ },
380
+ });
381
+
382
+ this.emoteSpitter.update({
383
+ position: {
384
+ y: this.message.container.position.y - this.message.container.height,
385
+ },
386
+ });
387
+
388
+ this.move();
389
+
390
+ if (this.sprite) {
391
+ this.sprite.update({
392
+ color: {
393
+ [DudeSpriteLayers.Body]: this.userState.color
394
+ ? this.userState.color
395
+ : this.state.color,
396
+ },
397
+ scale: {
398
+ x: this.state.direction * this.state.scale,
399
+ y: this.state.scale,
400
+ },
401
+ play: !this.isDashing,
402
+ });
403
+ }
404
+
405
+ this.trail.update({
406
+ play: this.isDashing,
407
+ });
408
+ }
409
+
410
+ isOnGround(y: number): boolean {
411
+ return y > app.renderer.height;
412
+ }
413
+
414
+ addMessage(message: string): void {
415
+ this.message.add(message);
416
+ this.state.isAnonymous = false;
417
+ this.fadeTween?.stop();
418
+ this.fadeTween = undefined; // https://github.com/tweenjs/tween.js/issues/665
419
+ this.container.alpha = 1;
420
+ }
421
+
422
+ spitEmotes(emotes: string[]): void {
423
+ for (const emote of emotes) {
424
+ this.emoteSpitter.add(emote);
425
+ }
426
+ }
427
+
428
+ async setAnimationState(state: DudeSpriteTags, force = false): Promise<void> {
429
+ if (this.animationState == state && !force) {
430
+ return;
431
+ }
432
+
433
+ this.animationState = state;
434
+
435
+ if (this.sprite) {
436
+ this.container.removeChild(this.sprite.container);
437
+ }
438
+
439
+ // TODO: Refactor sprite initializing, move logic to separate file
440
+ const layerAnimatedSprites = this.tagSprites[this.animationState];
441
+
442
+ this.sprite = new DudeSpriteContainer(layerAnimatedSprites);
443
+
444
+ this.trail.setSprite(this.sprite);
445
+
446
+ this.sprite.container.pivot.set(
447
+ this.state.sprite.pivot.x,
448
+ this.state.sprite.pivot.y,
449
+ );
450
+
451
+ this.container.addChild(this.sprite.container);
452
+ }
453
+ }