Skip to content

Commit

Permalink
Merge pull request #3 from twirapp/feat/dudes-demo
Browse files Browse the repository at this point in the history
feat(playground): dudes catalog
  • Loading branch information
crashmax-dev committed Apr 15, 2024
2 parents 2b4ffb9 + b9a7cc9 commit e18c4b2
Show file tree
Hide file tree
Showing 28 changed files with 1,351 additions and 28 deletions.
29 changes: 29 additions & 0 deletions apps/dudes-demo/package.json
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"
}
}
22 changes: 22 additions & 0 deletions apps/dudes-demo/src/constants.ts
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
}
23 changes: 23 additions & 0 deletions apps/dudes-demo/src/core/dude-settings.ts
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)
}
}
40 changes: 40 additions & 0 deletions apps/dudes-demo/src/core/dude-sprite-container.ts
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
}
}
132 changes: 132 additions & 0 deletions apps/dudes-demo/src/core/dude.ts
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)
}
}
41 changes: 41 additions & 0 deletions apps/dudes-demo/src/core/raf.ts
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)
}
}
}
Loading

0 comments on commit e18c4b2

Please sign in to comment.