diff --git a/apps/docs/assets/person.png b/apps/docs/assets/person.png new file mode 100644 index 0000000..75bed59 Binary files /dev/null and b/apps/docs/assets/person.png differ diff --git a/apps/docs/docs/examples/2d/animated-sprite-component.mdx b/apps/docs/docs/examples/2d/animated-sprite-component.mdx new file mode 100644 index 0000000..13fecff --- /dev/null +++ b/apps/docs/docs/examples/2d/animated-sprite-component.mdx @@ -0,0 +1,27 @@ +# Animated Sprite Component + +import useBrowserOnly from '@docusaurus/useIsBrowser'; +import { TGame } from '@tedengine/ted'; + +export const Game = () => { + const onBrowser = useBrowserOnly(); + if (!onBrowser) { + return null; + } + return ( + + ); +}; + + diff --git a/apps/docs/src/examples/2d/animated-sprite-component.ts b/apps/docs/src/examples/2d/animated-sprite-component.ts new file mode 100644 index 0000000..e2bc692 --- /dev/null +++ b/apps/docs/src/examples/2d/animated-sprite-component.ts @@ -0,0 +1,66 @@ +import asteroidTexture from '@assets/person.png'; +import { vec3 } from 'gl-matrix'; +import type { TResourcePackConfig } from '@tedengine/ted'; +import { + TGameState, + TActor, + TOriginPoint, + TResourcePack, + TEngine, + TAnimatedSpriteComponent, + TSpriteLayer, + TOrthographicCamera, +} from '@tedengine/ted'; + +class Sprite extends TActor { + public static resources: TResourcePackConfig = { + textures: [asteroidTexture], + }; + + constructor(engine: TEngine) { + super(); + + const box = new TAnimatedSpriteComponent( + engine, + this, + 12, + 24, + TOriginPoint.Center, + TSpriteLayer.Foreground_0, + { + frameCount: 9, + frameRate: 10, + }, + ); + box.applyTexture(engine, asteroidTexture); + + this.rootComponent.transform.translation = vec3.fromValues(0, 0, -3); + } +} + +class SpriteState extends TGameState { + public async onCreate(engine: TEngine) { + const rp = new TResourcePack(engine, Sprite.resources); + + await rp.load(); + + this.onReady(engine); + } + + public onReady(engine: TEngine) { + const asteroid = new Sprite(engine); + this.addActor(asteroid); + + const camera = new TOrthographicCamera(engine); + this.activeCamera = camera; + } +} + +const config = { + states: { + game: SpriteState, + }, + defaultState: 'game', +}; + +new TEngine(config, self as DedicatedWorkerGlobalScope); diff --git a/packages/ted/src/actor-components/animated-sprite-component.ts b/packages/ted/src/actor-components/animated-sprite-component.ts new file mode 100644 index 0000000..b415dfb --- /dev/null +++ b/packages/ted/src/actor-components/animated-sprite-component.ts @@ -0,0 +1,68 @@ +import type TActor from '../core/actor'; +import type TEngine from '../engine/engine'; +import type { TPhysicsBodyOptions } from '../physics/physics-world'; +import type { TSerializedSpriteInstance } from '../renderer/frame-params'; +import type { TActorComponentWithOnUpdate } from './actor-component'; +import TSpriteComponent, { + TOriginPoint, + TSpriteLayer, +} from './sprite-component'; + +export interface TSpriteSheetOptions { + frameCount: number; + frameRate: number; +} + +export default class TAnimatedSpriteComponent + extends TSpriteComponent + implements TActorComponentWithOnUpdate +{ + public frame = 0; + + private time = 0; + + constructor( + engine: TEngine, + actor: TActor, + width: number, + height: number, + origin = TOriginPoint.Center, + layer = TSpriteLayer.Foreground_0, + private spriteSheetOptions: TSpriteSheetOptions, + bodyOptions?: TPhysicsBodyOptions, + ) { + super(engine, actor, width, height, origin, layer, bodyOptions); + } + + public async onUpdate(_: TEngine, delta: number): Promise { + this.time += delta; + if (this.time > 1 / this.spriteSheetOptions.frameRate) { + this.time = 0; + this.frame = (this.frame + 1) % this.spriteSheetOptions.frameCount; + } + } + + public getRenderTask(): TSerializedSpriteInstance | undefined { + const task = super.getRenderTask(); + if (!task) { + return undefined; + } + + // @todo modify the instance UVs based on the frame + const startX = this.frame * (1 / this.spriteSheetOptions.frameCount); + const endX = startX + 1 / this.spriteSheetOptions.frameCount; + + task.material.options.instanceUVs = [ + startX, + 0, + startX, + 1, + endX, + 0, + endX, + 1, + ]; + + return task; + } +} diff --git a/packages/ted/src/index.ts b/packages/ted/src/index.ts index 22a16bd..8dcafd8 100644 --- a/packages/ted/src/index.ts +++ b/packages/ted/src/index.ts @@ -12,6 +12,8 @@ export * from './actor-components/sprite-component'; export { default as TTexturedMeshComponent } from './actor-components/textured-mesh-component'; export { default as TTilemapComponent } from './actor-components/tilemap-component'; export * from './actor-components/tilemap-component'; +export { default as TAnimatedSpriteComponent } from './actor-components/animated-sprite-component'; +export * from './actor-components/animated-sprite-component'; export { default as TAudio } from './audio/audio'; export { default as TSound } from './audio/sound';