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
@@ -0,0 +1,60 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { Point } from '../helpers/types';
3
+ import { Timer } from '../helpers/timer';
4
+ import { FIXED_DELTA_TIME } from '../config/constants';
5
+
6
+ export type DudeEmoteSpitterProps = {
7
+ position: Point;
8
+ };
9
+
10
+ export class DudeEmoteSpitter {
11
+ public container: PIXI.Container = new PIXI.Container();
12
+
13
+ private emotes: PIXI.Sprite[] = [];
14
+
15
+ private moveSpeed = 50;
16
+ private alphaSpeed = 1;
17
+ private scaleSpeed = 0.5;
18
+
19
+ private timer?: Timer;
20
+
21
+ public add(url: string): void {
22
+ const sprite = PIXI.Sprite.from(url);
23
+ sprite.anchor.set(0.5, 0.5);
24
+ sprite.scale.set(0, 0);
25
+ this.emotes.push(sprite);
26
+ }
27
+
28
+ public update(props: DudeEmoteSpitterProps): void {
29
+ this.timer?.tick();
30
+
31
+ if (!this.timer || this.timer.isCompleted) {
32
+ if (this.emotes.length > 0) {
33
+ this.timer = new Timer(2000, () => {
34
+ const sprite = this.emotes.shift();
35
+
36
+ if (sprite) {
37
+ this.container.addChild(sprite);
38
+ }
39
+ });
40
+ }
41
+ }
42
+
43
+ this.container.position.x = props.position.x ?? this.container.position.x;
44
+ this.container.position.y = props.position.y ?? this.container.position.y;
45
+
46
+ for (const child of this.container.children) {
47
+ child.position.y -= (FIXED_DELTA_TIME * this.moveSpeed) / 1000;
48
+ child.scale.x += (FIXED_DELTA_TIME * this.scaleSpeed) / 1000;
49
+ child.scale.y += (FIXED_DELTA_TIME * this.scaleSpeed) / 1000;
50
+
51
+ if (child.scale.x > 1) {
52
+ child.alpha -= (FIXED_DELTA_TIME * this.alphaSpeed) / 1000;
53
+ }
54
+
55
+ if (child.alpha <= 0) {
56
+ this.container.removeChild(child);
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,163 @@
1
+ import { Timer } from '../helpers/timer';
2
+ import { Point } from '../helpers/types';
3
+ import { Tween } from '@tweenjs/tween.js';
4
+ import * as PIXI from 'pixi.js';
5
+
6
+ export type DudeMesageProps = {
7
+ position: Point;
8
+ };
9
+
10
+ export class DudeMessage {
11
+ public container: PIXI.Container = new PIXI.Container();
12
+ public animated: PIXI.Container = new PIXI.Container();
13
+
14
+ private text: PIXI.Text = new PIXI.Text(undefined, {
15
+ fontFamily: 'Rubik',
16
+ fontSize: 20,
17
+ fill: 0x222222,
18
+ align: 'left',
19
+ breakWords: true,
20
+ wordWrap: true,
21
+ wordWrapWidth: 200,
22
+ });
23
+
24
+ private padding = 10;
25
+ private borderRadius = 10;
26
+ private boxColor = 0xeeeeee;
27
+
28
+ private fadeShift = 10;
29
+
30
+ private isChecking = true;
31
+
32
+ private messageQueue: string[] = [];
33
+
34
+ private showTween?: Tween<PIXI.Container>;
35
+ private hideTween?: Tween<PIXI.Container>;
36
+
37
+ private timer?: Timer;
38
+
39
+ constructor(private beforeShow: () => void) {
40
+ this.container.addChild(this.animated);
41
+
42
+ this.text.anchor.set(0.5, 1);
43
+ this.text.position.set(0, -this.padding);
44
+ }
45
+
46
+ private trim(text: PIXI.Text): string {
47
+ const metrics = PIXI.TextMetrics.measureText(text.text, text.style);
48
+
49
+ return metrics.lines.length > 4
50
+ ? metrics.lines.slice(0, 4).join(' ').slice(0, -3) + '...'
51
+ : text.text;
52
+ }
53
+
54
+ public update(props: DudeMesageProps): void {
55
+ this.timer?.tick();
56
+
57
+ this.showTween?.update();
58
+ this.hideTween?.update();
59
+
60
+ if (this.isChecking && this.messageQueue.length > 0) {
61
+ this.isChecking = false;
62
+
63
+ const message = this.messageQueue.shift();
64
+
65
+ if (message) {
66
+ this.beforeShow();
67
+ this.show(message);
68
+ }
69
+ }
70
+
71
+ this.container.position.x = props.position.x ?? this.container.position.x;
72
+ this.container.position.y = props.position.y ?? this.container.position.y;
73
+
74
+ const isAnimationPlaying =
75
+ this.showTween?.isPlaying() || this.hideTween?.isPlaying();
76
+
77
+ if (!this.isChecking && !isAnimationPlaying && this.timer) {
78
+ this.animated.position.y = Math.sin(this.timer.current * 0.0025) * 4 - 2;
79
+ }
80
+ }
81
+
82
+ public add(message: string): void {
83
+ this.messageQueue.push(message);
84
+ }
85
+
86
+ private drawBox(text: PIXI.Text) {
87
+ const roundedRect = {
88
+ x: text.x - this.padding - text.width * text.anchor.x,
89
+ y: text.y - this.padding - text.height * text.anchor.y,
90
+ w: text.width + this.padding * 2,
91
+ h: text.height + this.padding * 2,
92
+ };
93
+
94
+ const box = new PIXI.Graphics();
95
+ box.beginFill(this.boxColor);
96
+
97
+ box.drawRoundedRect(
98
+ roundedRect.x,
99
+ roundedRect.y,
100
+ roundedRect.w,
101
+ roundedRect.h,
102
+ this.borderRadius
103
+ );
104
+
105
+ box.drawPolygon(
106
+ {
107
+ x: roundedRect.x + roundedRect.w / 2 - 10,
108
+ y: roundedRect.y + roundedRect.h,
109
+ },
110
+ {
111
+ x: roundedRect.x + roundedRect.w / 2 + 10,
112
+ y: roundedRect.y + roundedRect.h,
113
+ },
114
+ {
115
+ x: roundedRect.x + roundedRect.w / 2,
116
+ y: roundedRect.y + roundedRect.h + 10 - 4,
117
+ }
118
+ );
119
+
120
+ box.endFill();
121
+
122
+ return box;
123
+ }
124
+
125
+ private show(message: string): void {
126
+ this.text.text = message;
127
+ this.text.text = this.trim(this.text);
128
+
129
+ this.animated.alpha = 0;
130
+ this.animated.position.y = this.fadeShift;
131
+
132
+ const box = this.drawBox(this.text);
133
+
134
+ this.animated.removeChildren();
135
+ this.animated.addChild(box, this.text);
136
+
137
+ const showProps = {
138
+ alpha: 1,
139
+ y: this.animated.y - this.fadeShift,
140
+ };
141
+
142
+ const hideProps = {
143
+ alpha: 0,
144
+ y: this.animated.y + this.fadeShift,
145
+ };
146
+
147
+ this.hideTween = new Tween(this.animated)
148
+ .to(hideProps, 500)
149
+ .onComplete(() => {
150
+ this.isChecking = true;
151
+ });
152
+
153
+ this.showTween = new Tween(this.animated)
154
+ .to(showProps, 500)
155
+ .onComplete(() => {
156
+ this.timer = new Timer(10000, () => {
157
+ this.hideTween?.start();
158
+ });
159
+ });
160
+
161
+ this.showTween.start();
162
+ }
163
+ }
@@ -0,0 +1,39 @@
1
+ import { Point, Rect } from '../helpers/types';
2
+ import * as PIXI from 'pixi.js';
3
+
4
+ export type DudeNameProps = {
5
+ name: string;
6
+ position: Point;
7
+ isVisible: boolean;
8
+ };
9
+
10
+ export class DudeName {
11
+ public text: PIXI.Text = new PIXI.Text(undefined, {
12
+ fontFamily: 'Rubik',
13
+ fontSize: 18,
14
+ fill: 0xffffff,
15
+ align: 'center',
16
+ lineJoin: 'round',
17
+ strokeThickness: 4,
18
+ stroke: 'black',
19
+ });
20
+
21
+ constructor() {
22
+ this.text.anchor.set(0.5, 1);
23
+ this.text.zIndex = 100;
24
+ }
25
+
26
+ update(props: DudeNameProps) {
27
+ this.text.text = props.name ?? '';
28
+ this.text.visible = props.isVisible;
29
+ this.text.position.x = props.position.x ?? this.text.position.x;
30
+ this.text.position.y = props.position.y ?? this.text.position.y;
31
+ }
32
+
33
+ rect = (): Rect => ({
34
+ x: this.text.x,
35
+ y: this.text.y,
36
+ w: this.text.width,
37
+ h: this.text.height,
38
+ });
39
+ }
@@ -0,0 +1,68 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { FIXED_DELTA_TIME } from '../config/constants';
3
+ import { Point } from '../helpers/types';
4
+ import {
5
+ DudeLayerAnimatedSprite,
6
+ DudeSpriteLayers,
7
+ } from '../services/spriteProvider';
8
+ import { DEFAULT_DUDE_SCALE } from './Dude';
9
+
10
+ export type DudeSpriteContainerProps = {
11
+ color: {
12
+ [key in DudeSpriteLayers]?: PIXI.Color;
13
+ };
14
+ scale: Point;
15
+ play: boolean;
16
+ };
17
+
18
+ export class DudeSpriteContainer {
19
+ public container: PIXI.Container = new PIXI.Container();
20
+
21
+ public sprites: DudeLayerAnimatedSprite[];
22
+
23
+ constructor(sprites: DudeLayerAnimatedSprite[]) {
24
+ this.sprites = sprites;
25
+
26
+ this.container.sortableChildren = true;
27
+
28
+ let zIndex = 0;
29
+
30
+ for (const layer of this.sprites) {
31
+ const sprite = layer.sprite;
32
+ this.container.addChild(sprite);
33
+
34
+ sprite.zIndex = ++zIndex;
35
+ sprite.anchor.set(0.5, 0.5);
36
+ sprite.autoUpdate = false;
37
+ sprite.play();
38
+ }
39
+ }
40
+
41
+ public update(props: DudeSpriteContainerProps): void {
42
+ for (const layer of this.sprites) {
43
+ if (props.play) {
44
+ layer.sprite.update(FIXED_DELTA_TIME * 0.06);
45
+ }
46
+
47
+ if (props.color) {
48
+ for (const colorLayer in props.color) {
49
+ if (colorLayer == layer.layer) {
50
+ const color = props.color[colorLayer];
51
+
52
+ if (color) {
53
+ layer.sprite.alpha = color.alpha;
54
+ layer.sprite.tint = color;
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ if (props.scale.x) {
61
+ layer.sprite.animationSpeed =
62
+ DEFAULT_DUDE_SCALE / Math.abs(props.scale.x);
63
+ }
64
+ }
65
+
66
+ this.container.scale.set(props.scale.x, props.scale.y);
67
+ }
68
+ }
@@ -0,0 +1,72 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { app } from '../app';
3
+ import { Timer } from '../helpers/timer';
4
+ import { DudeSpriteContainer } from './DudeSpriteContainer';
5
+
6
+ export type DudeTrailEffectProps = {
7
+ play: boolean;
8
+ };
9
+
10
+ export class DudeTrailEffect {
11
+ public container: PIXI.Container = new PIXI.Container();
12
+
13
+ private timer?: Timer;
14
+
15
+ private sprite?: DudeSpriteContainer;
16
+
17
+ private opacityStep = 0.1;
18
+
19
+ private shadowStep = 50;
20
+
21
+ constructor() {
22
+ app.stage.addChild(this.container);
23
+ }
24
+
25
+ public setSprite(sprite: DudeSpriteContainer) {
26
+ this.sprite = sprite;
27
+ }
28
+
29
+ public update(props: DudeTrailEffectProps): void {
30
+ for (const child of this.container.children) {
31
+ if (child.alpha > 0) {
32
+ child.alpha = child.alpha - this.opacityStep;
33
+ }
34
+ }
35
+
36
+ if (this.timer && !this.timer.isCompleted) {
37
+ this.timer.tick();
38
+ } else if (props.play) {
39
+ this.timer = new Timer(this.shadowStep);
40
+
41
+ const container = new PIXI.Container();
42
+
43
+ if (this.sprite) {
44
+ for (const layer of this.sprite.sprites) {
45
+ const texture = layer.sprite.texture.clone();
46
+ const sprite = new PIXI.Sprite(texture);
47
+
48
+ sprite.anchor.set(0.5, 0.5);
49
+ sprite.alpha = layer.sprite.alpha;
50
+ sprite.tint = layer.sprite.tint;
51
+
52
+ container.addChild(sprite);
53
+
54
+ container.scale.set(
55
+ this.sprite.container.scale.x,
56
+ this.sprite.container.scale.y
57
+ );
58
+
59
+ container.pivot.set(
60
+ this.sprite.container.pivot.x,
61
+ this.sprite.container.pivot.y
62
+ );
63
+
64
+ const globalPosition = this.sprite.container.getGlobalPosition();
65
+ container.position.set(globalPosition.x, globalPosition.y);
66
+ }
67
+ }
68
+
69
+ this.container.addChild(container);
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,43 @@
1
+ import { FIXED_DELTA_TIME } from '../config/constants';
2
+
3
+ export class Timer {
4
+ public isCompleted = false;
5
+
6
+ constructor(public current = 0, private onComplete?: () => void) {}
7
+
8
+ tick(delta: number = FIXED_DELTA_TIME) {
9
+ if (this.isCompleted) {
10
+ return;
11
+ }
12
+
13
+ this.current -= delta;
14
+
15
+ if (this.current < 0) {
16
+ this.isCompleted = true;
17
+ this.onComplete?.();
18
+ }
19
+ }
20
+ }
21
+
22
+ class Timers {
23
+ private timers: Timer[] = [];
24
+
25
+ public add(current?: number, onComplete?: () => void) {
26
+ this.timers.push(new Timer(current, onComplete));
27
+ }
28
+
29
+ public tick(delta: number = FIXED_DELTA_TIME) {
30
+ const uncompleted = [];
31
+
32
+ for (const timer of this.timers) {
33
+ if (!timer.isCompleted) {
34
+ timer.tick(delta);
35
+ uncompleted.push(timer);
36
+ }
37
+ }
38
+
39
+ this.timers = uncompleted;
40
+ }
41
+ }
42
+
43
+ export const timers = new Timers();
@@ -0,0 +1,11 @@
1
+ export type Point = {
2
+ x?: number;
3
+ y?: number;
4
+ };
5
+
6
+ export type Rect = {
7
+ x: number;
8
+ y: number;
9
+ w: number;
10
+ h: number;
11
+ };
package/src/index.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { Dudes } from "./dudes";
2
+ import { SettingsEntity } from "./types";
3
+ import "./styles.css";
4
+
5
+ const manifest = {
6
+ bundles: [
7
+ {
8
+ name: "main",
9
+ assets: [
10
+ {
11
+ alias: "dude",
12
+ src: "/sprites/dude/dude.json",
13
+ },
14
+ {
15
+ alias: "sith",
16
+ src: "/sprites/sith/sith.json",
17
+ },
18
+ {
19
+ alias: "agent",
20
+ src: "/sprites/agent/agent.json",
21
+ },
22
+ {
23
+ alias: "girl",
24
+ src: "/sprites/girl/girl.json",
25
+ },
26
+ {
27
+ alias: "senior",
28
+ src: "/sprites/senior/senior.json",
29
+ },
30
+ {
31
+ alias: "cat",
32
+ src: "/sprites/cat/cat.json",
33
+ },
34
+ {
35
+ alias: "nerd",
36
+ src: "/sprites/nerd/nerd.json",
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ name: "fonts",
42
+ assets: [
43
+ {
44
+ alias: "Rubik",
45
+ src: "/fonts/Rubik.ttf",
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ };
51
+
52
+ const sound = { jump: "/sounds/jump.mp3" };
53
+ const settings: SettingsEntity = {
54
+ fallingDudes: true,
55
+ };
56
+
57
+ const root = document.getElementById("root");
58
+
59
+ if (root) {
60
+ const dudes = new Dudes(root);
61
+ await dudes.run({ manifest, sound });
62
+ dudes.updateSettings(settings);
63
+
64
+ setTimeout(() => {
65
+ dudes.processMessage({
66
+ message: "Hello!",
67
+ userId: "1",
68
+ emotes: [],
69
+ info: {
70
+ color: "pink",
71
+ displayName: "Dude",
72
+ },
73
+ });
74
+ }, 500);
75
+ }
@@ -0,0 +1,27 @@
1
+ import * as PIXI from 'pixi.js';
2
+
3
+ export interface AsepriteFrameData extends PIXI.ISpritesheetFrameData {
4
+ duration: number;
5
+ }
6
+
7
+ export interface AsepriteData extends PIXI.ISpritesheetData {
8
+ frames: Record<string, AsepriteFrameData>;
9
+ duration: number;
10
+ }
11
+
12
+ export class AssetsLoader {
13
+ private isLoaded = false;
14
+
15
+ public sheets: Record<string, PIXI.Spritesheet<AsepriteData>> = {};
16
+
17
+ async load(manifest: PIXI.AssetsManifest): Promise<void> {
18
+ if (!this.isLoaded) {
19
+ this.isLoaded = true;
20
+ await PIXI.Assets.init({ manifest });
21
+ this.sheets = await PIXI.Assets.loadBundle('main');
22
+ await PIXI.Assets.loadBundle('fonts');
23
+ }
24
+ }
25
+ }
26
+
27
+ export const assetsLoader = new AssetsLoader();