Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/tiny-engine/llms.txt

Use this file to discover all available pages before exploring further.

TinyScript lets you move game logic out of JSX component functions and into dedicated class instances. Each script is bound to one node, receives a typed reference to it, and can subscribe to that node’s lifecycle events — all without coupling rendering concerns to behaviour code.

Creating a script

Extend TinyScript and supply the PrimaryNode enum value for the node type the script will be attached to:
import { TinyScript } from 'tiny-engine/scripts'
import { PrimaryNode } from 'tiny-engine'

class PlayerScript extends TinyScript<PrimaryNode.Transform> {
  health = 100

  setup() {
    // Called once when the node initialises
  }
}
The generic parameter constrains the type of this.me so that property access and method calls are fully type-checked.

The setup() lifecycle method

setup() is the single lifecycle hook you must implement. It is called once when the script’s node finishes initialising. Register event listeners, set up subscriptions, and perform one-time configuration here.
class EnemyScript extends TinyScript<PrimaryNode.Transform> {
  setup() {
    // Subscribe to node events
    this.connect('started', () => {
      console.log('Enemy entered the scene')
    })

    this.connect('updated', (delta) => {
      this.patrol(delta)
    })

    this.connect('destroyed', () => {
      console.log('Enemy removed from scene')
    })
  }

  private patrol(delta: number) {
    this.me.position.x += delta * 50
  }
}

this.me — typed node reference

this.me returns the node this script is attached to, typed to the PrimaryNode you declared. Accessing it before init() is called (i.e. before the node starts) throws a NodeNotInitializedError.
class MarkerScript extends TinyScript<PrimaryNode.Sprite> {
  setup() {
    // this.me is typed as Sprite here
    console.log(this.me.position)
  }
}
Always access this.me inside setup() or inside callbacks registered in setup() — never in the constructor.

this.connect(eventName, callback) — type-safe event subscription

connect subscribes to an event on the attached node. The method is type-safe: TypeScript infers valid event names and matching callback signatures from the node type.
ParameterTypeDescription
eventNamekeyof NodeEvents<T>Event name string, e.g. 'started', 'destroyed', 'updated'.
callbackMatching event function signatureCallback invoked when the event fires.
Common events available on most nodes:
EventCallback signatureDescription
'started'() => voidNode entered the scene tree.
'destroyed'() => voidNode was removed from the scene tree.
'updated'(delta: number) => voidCalled every frame with elapsed seconds.

Full example: PlayerScript with health management

import { TinyScript } from 'tiny-engine/scripts'
import { PrimaryNode, Signal } from 'tiny-engine'

class PlayerScript extends TinyScript<PrimaryNode.Transform> {
  health = new Signal(100)

  setup() {
    this.connect('started', () => {
      console.log('Player spawned with', this.health.value, 'HP')
    })

    this.connect('destroyed', () => {
      this.health.clearSubs()
    })

    this.connect('updated', (delta) => {
      this.handleMovement(delta)
    })
  }

  applyDamage(amount: number): boolean {
    this.health.value -= amount
    if (this.health.value <= 0) {
      this.me.destroy()
      return true
    }
    return false
  }

  private handleMovement(delta: number) {
    // movement logic using this.me
  }
}

Attaching a script to a node

Pass a script instance to the script prop on any JSX node:
function Player() {
  return (
    <transform script={new PlayerScript()}>
      <sprite textureId={PLAYER_TEXTURE} sourceSize={new Vector2(16, 16)} />
    </transform>
  )
}
The script prop is accepted on every node type. The engine calls script.init(node) and then script.setup() automatically when the node starts.

Accessing a script from outside — useScript

Use the useScript hook to retrieve the script instance from a node reference. This is useful when a sibling or parent component needs to call methods on the script.
import { useRefNode, useScript } from 'tiny-engine/hooks'
import { PrimaryNode } from 'tiny-engine'

function GameHUD() {
  const playerTransform = useRefNode(PrimaryNode.Transform)
  const playerScript = useScript<PlayerScript>(playerTransform)

  useEvent(someButton, 'clicked', () => {
    // Imperatively call a method on the script
    playerScript.current?.applyDamage(10)
  })

  return (
    <transform ref={playerTransform} script={new PlayerScript()}>
      {/* ... */}
    </transform>
  )
}
useScript returns a Reference object. Access the live script instance via .current.
Keep game logic — health, state machines, physics responses — inside TinyScript subclasses and keep visual updates — position, texture swaps, animation signals — inside JSX components. Scripts can expose reactive Signal properties that components read, giving you a clean one-way data flow from logic to presentation without entangling the two.

Build docs developers (and LLMs) love