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';