-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from twirapp/feat/dudes-demo
feat(playground): dudes catalog
- Loading branch information
Showing
28 changed files
with
1,351 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"name": "@twirapp/dudes-demo", | ||
"version": "0.0.0", | ||
"type": "module", | ||
"files": [ | ||
"dist" | ||
], | ||
"exports": { | ||
".": { | ||
"types": "./dist/index.d.ts", | ||
"import": "./dist/dudes-demo.js", | ||
"require": "./dist/dudes-demo.umd.cjs" | ||
}, | ||
"./types": { | ||
"types": "./dist/types.d.ts" | ||
} | ||
}, | ||
"scripts": { | ||
"dev": "vite build --watch", | ||
"build": "vite build" | ||
}, | ||
"devDependencies": { | ||
"vite": "5.1.6", | ||
"vite-plugin-dts": "3.7.3" | ||
}, | ||
"dependencies": { | ||
"pixi.js": "7.4.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
export const ROUND = 1000 | ||
export const DELTA_TIME = 0.02 * ROUND | ||
export const SPRITE_SIZE = 32 | ||
|
||
type Collider = { | ||
X: number | ||
Y: number | ||
Width: number | ||
Height: number | ||
} | ||
|
||
export const Direction = { | ||
Left: -1, | ||
Right: 1 | ||
} | ||
|
||
export const Collider: Collider = { | ||
X: 8, | ||
Y: 3, | ||
Width: 16, | ||
Height: 22 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { deepMerge } from '../helpers.js' | ||
import type { DudePartialSettings, DudesTypes } from '../types.js' | ||
|
||
export type DudeInternalSettings = { | ||
dude: DudesTypes.DudeStyles | ||
} | ||
|
||
export class DudeSettings { | ||
private dudesStyles: DudeInternalSettings = { | ||
dude: { | ||
maxLifeTime: 1000 * 60 * 30, | ||
scale: 4 | ||
} | ||
} | ||
|
||
get settings() { | ||
return this.dudesStyles | ||
} | ||
|
||
update(styles: DudePartialSettings): void { | ||
this.dudesStyles = deepMerge(this.dudesStyles, styles) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { AnimatedSprite, Container } from 'pixi.js' | ||
|
||
import { DudesLayersKeys } from './texture-provider.js' | ||
import type { DudesLayer } from './texture-provider.js' | ||
|
||
export class DudeSpriteContainer { | ||
readonly view = new Container() | ||
|
||
private body?: AnimatedSprite | ||
private eyes?: AnimatedSprite | ||
private mouth?: AnimatedSprite | ||
private hat?: AnimatedSprite | ||
private cosmetics?: AnimatedSprite | ||
|
||
constructor(animatedSprites: AnimatedSprite[]) { | ||
animatedSprites.forEach((sprite, index) => { | ||
if (!sprite) return | ||
|
||
const layer = sprite.name as DudesLayer | ||
sprite.zIndex = index + 1 | ||
sprite.anchor.set(0.5) | ||
sprite.play() | ||
this.view.addChild(sprite) | ||
this[layer] = sprite | ||
}) | ||
} | ||
|
||
update(delta: number): void { | ||
for (const layer of DudesLayersKeys) { | ||
const layerKey = layer as DudesLayer | ||
this[layerKey]?.update(delta) | ||
} | ||
} | ||
|
||
setColor(layer: DudesLayer, color: string): void { | ||
const dudeLayer = this[layer] | ||
if (!dudeLayer) return | ||
dudeLayer.tint = color | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { Container } from 'pixi.js' | ||
import type { IPointData } from 'pixi.js' | ||
|
||
import { | ||
Collider, | ||
DELTA_TIME, | ||
Direction, | ||
ROUND, | ||
SPRITE_SIZE | ||
} from '../constants.js' | ||
import { isValidColor } from '../helpers.js' | ||
import { DudeSpriteContainer } from './dude-sprite-container.js' | ||
import { | ||
DudesFrameTags, | ||
DudesLayers, | ||
DudesLayersKeys | ||
} from './texture-provider.js' | ||
import type { DudesTypes } from '../types.js' | ||
import type { DudeSettings } from './dude-settings.js' | ||
import type { SpriteLoader } from './sprite-loader.js' | ||
import type { | ||
DudesLayer, | ||
DudeSpriteFrameTag, | ||
TextureProvider | ||
} from './texture-provider.js' | ||
|
||
export class Dude { | ||
readonly view = new Container() | ||
|
||
private colors: Record<DudesLayer, string> = { | ||
body: '#fff', | ||
eyes: '#fff', | ||
mouth: '#fff', | ||
hat: '#fff', | ||
cosmetics: '#fff' | ||
} | ||
private direction = Direction.Right | ||
private currentFrameTag?: DudeSpriteFrameTag | ||
private sprite?: DudeSpriteContainer | ||
private scale: number | ||
|
||
constructor( | ||
public readonly config: DudesTypes.DudeConfig, | ||
private readonly canvas: HTMLCanvasElement, | ||
private readonly textureProvider: TextureProvider, | ||
private readonly spriteLoader: SpriteLoader, | ||
private readonly settings: DudeSettings | ||
) {} | ||
|
||
async init(): Promise<void> { | ||
if (this.sprite) return | ||
|
||
await this.spriteLoader.loadSprite(this.config.sprite) | ||
|
||
this.scale = this.settings.settings.dude.scale | ||
this.view.y = -(Collider.Y + Collider.Height - SPRITE_SIZE / 2) * this.scale | ||
this.view.x = | ||
Math.random() * (this.canvas.clientWidth - SPRITE_SIZE * this.scale) + | ||
(SPRITE_SIZE / 2) * this.scale | ||
|
||
this.playAnimation(DudesFrameTags.idle) | ||
} | ||
|
||
async playAnimation( | ||
frameTag: DudeSpriteFrameTag, | ||
force = false | ||
): Promise<void> { | ||
const dudeSprite = this.textureProvider.getTexture( | ||
this.config.sprite.name, | ||
frameTag | ||
) | ||
if (!dudeSprite) return | ||
|
||
if (this.currentFrameTag === frameTag && !force) return | ||
this.currentFrameTag = frameTag | ||
|
||
if (this.sprite) { | ||
this.view.removeChild(this.sprite.view) | ||
} | ||
|
||
this.sprite = new DudeSpriteContainer([ | ||
dudeSprite[DudesLayers.body], | ||
dudeSprite[DudesLayers.eyes], | ||
dudeSprite[DudesLayers.mouth], | ||
dudeSprite[DudesLayers.hat], | ||
dudeSprite[DudesLayers.cosmetics] | ||
]) | ||
this.sprite.view.scale.set(this.direction * this.scale, this.scale) | ||
|
||
for (const layer of DudesLayersKeys) { | ||
const layerKey = layer as DudesLayer | ||
this.sprite?.setColor(DudesLayers[layerKey], this.colors[layerKey]) | ||
} | ||
|
||
this.view.addChild(this.sprite.view) | ||
} | ||
|
||
update(): void { | ||
const x = this.canvas.clientWidth / 2 | ||
const y = | ||
this.canvas.clientHeight - | ||
(Collider.Y + Collider.Height - SPRITE_SIZE / 2) * this.scale | ||
|
||
this.view.position.set(x, y) | ||
this.sprite?.update((DELTA_TIME / ROUND) * 60) | ||
} | ||
|
||
updateColor(layer: DudesLayer, color: string): void { | ||
if (!isValidColor(color) || !this.sprite?.[layer]) return | ||
this.colors[layer] = color | ||
this.sprite.setColor(layer, color) | ||
} | ||
|
||
updateScale(scale?: number, force = false): void { | ||
if (scale) { | ||
if (force) { | ||
this.scale = scale | ||
} else { | ||
this.scale += scale | ||
} | ||
} | ||
|
||
this.sprite?.view.scale.set(this.direction * this.scale, this.scale) | ||
} | ||
|
||
async updateSpriteData(spriteData: DudesTypes.SpriteData): Promise<void> { | ||
await this.spriteLoader.loadSprite(spriteData) | ||
this.textureProvider.unloadTextures(spriteData.name) | ||
this.config.sprite = spriteData | ||
this.playAnimation(DudesFrameTags.idle, true) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export class RequestAnimationFrame { | ||
private maxFps = 60 | ||
private minElapsedMS = 1000 / this.maxFps | ||
private maxElapsedMS = 100 | ||
private lastTime = performance.now() | ||
private lastFrame = -1 | ||
private rafId: number | null = null | ||
|
||
constructor(private callback: () => void) { | ||
this.render = this.render.bind(this) | ||
} | ||
|
||
private render(currentTime = performance.now()): void { | ||
let elapsedMS = currentTime - this.lastTime | ||
|
||
if (elapsedMS > this.maxElapsedMS) { | ||
elapsedMS = this.maxElapsedMS | ||
} | ||
|
||
const delta = (currentTime - this.lastFrame) | 0 | ||
|
||
if (delta > this.minElapsedMS) { | ||
this.lastFrame = currentTime - (delta % this.minElapsedMS) | ||
this.lastTime = currentTime | ||
this.callback() | ||
} | ||
|
||
this.rafId = requestAnimationFrame(this.render) | ||
} | ||
|
||
start(): void { | ||
this.stop() | ||
this.render() | ||
} | ||
|
||
stop(): void { | ||
if (this.rafId) { | ||
cancelAnimationFrame(this.rafId) | ||
} | ||
} | ||
} |
Oops, something went wrong.