# phaser-jsx

[![NPM](https://nodei.co/npm/phaser-jsx.png)](https://nodei.co/npm/phaser-jsx/)

[![NPM version](https://img.shields.io/npm/v/phaser-jsx.svg)](https://www.npmjs.com/package/phaser-jsx)
[![build](https://github.com/remarkablegames/phaser-jsx/actions/workflows/build.yml/badge.svg)](https://github.com/remarkablegames/phaser-jsx/actions/workflows/build.yml)
[![codecov](https://codecov.io/gh/remarkablegames/phaser-jsx/graph/badge.svg?token=EZEOFDL9ME)](https://codecov.io/gh/remarkablegames/phaser-jsx)

📝 Use [JSX](https://facebook.github.io/jsx/) in [Phaser](https://phaser.io/).

[Examples](https://github.com/remarkablegames/phaser-jsx/tree/master/examples) | [StackBlitz](https://stackblitz.com/edit/phaser-jsx) | [CodeSandbox](https://codesandbox.io/p/devbox/phaser-jsx-9ldp6n) | [JSFiddle](https://jsfiddle.net/remarkablemark/dLhvuo42/)

## Quick Start

With JSX:

```tsx
// index.jsx
import Phaser from 'phaser';
import { Text, render } from 'phaser-jsx';

new Phaser.Game({
  scene: {
    create() {
      render(<Text text="Hello, world!" />, this);
    },
  },
});
```

Without JSX:

```ts
// index.js
import Phaser from 'phaser';
import { jsx, render } from 'phaser-jsx';

new Phaser.Game({
  scene: {
    create() {
      render(jsx(Phaser.GameObjects.Text, { text: 'Hello, world!' }), this);
    },
  },
});
```

## Install

[NPM](https://www.npmjs.com/package/phaser-jsx):

```sh
npm install phaser-jsx
```

[Yarn](https://yarnpkg.com/package/phaser-jsx):

```sh
yarn add phaser-jsx
```

[CDN](https://unpkg.com/browse/phaser-jsx/):

```html
<script src="https://unpkg.com/phaser@latest/dist/phaser.min.js"></script>
<script src="https://unpkg.com/phaser-jsx@latest/umd/phaser-jsx.min.js"></script>
```

## Usage

ES Modules:

```ts
import { createElement, render } from 'phaser-jsx';
```

CommonJS:

```ts
const { createElement, render } = require('phaser-jsx');
```

UMD:

```html
<script src="https://unpkg.com/phaser@latest/dist/phaser.min.js"></script>
<script src="https://unpkg.com/phaser-jsx@latest/umd/phaser-jsx.min.js"></script>
<script>
  const { render, jsx } = window.PhaserJSX;
</script>
```

## TypeScript

For better type support, install [@types/react](https://www.npmjs.com/package/@types/react):

```sh
npm install @types/react --save-dev
```

Import the [GameObject](https://docs.phaser.io/phaser/concepts/gameobjects) from `phaser-jsx` instead of `phaser`:

```ts
import { Text } from 'phaser-jsx';
```

> [!TIP]
> All GameObjects exported from `phaser-jsx` are aliases of the GameObjects from `phaser`.

Update your `tsconfig.json`:

```json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "phaser-jsx"
  }
}
```

## Vite

Update your Vite config:

```ts
// vite.config.mjs
import { defineConfig } from 'vite';

export default defineConfig({
  esbuild: {
    jsxImportSource: 'phaser-jsx',
  },
});
```

### JSX Pragma

If you're not using `jsxImportSource`, you can set a JSX pragma at the top of your file:

```tsx
/** @jsx jsx */
import { jsx } from 'phaser-jsx';
```

## How Does It Work?

This package follows [React](https://react.dev/) conventions like having `createElement` and `jsx-runtime`.

The `render` function renders game objects inside a scene.

If you need nesting and relative positioning, use `Container`:

```tsx
<Container>
  <Text text="Child 1" />
  <Text text="Child 2" />
</Container>
```

Use `Fragment` to render children without a parent:

```tsx
import { Fragment } from 'phaser-jsx';

<Fragment>
  <Text text="Item 1" />
  <Text text="Item 2" />
</Fragment>;
```

> [!NOTE]
> The shorthand syntax `<></>` doesn't work here.

## Hooks

### `useRef`

Create a ref for your game object:

```ts
import { useRef } from 'phaser-jsx';

function MyComponent() {
  const textRef = useRef<Phaser.GameObjects.Text>();
  // ...
}
```

Pass your ref to the JSX element:

```tsx
// ...
return <Text ref={textRef} onPointerDown={handleClick} />;
```

Access your game object:

```ts
function handleClick() {
  textRef.current?.text = 'Clicked';
}
```

Alternatively, you can get the game object with a callback ref:

```tsx
<Text
  ref={(gameObject) => {
    gameObject.text = 'Hello, world!';
  }}
/>
```

### `useScene`

Retrieve the current Scene with the `useScene` hook:

```ts
import { useScene } from 'phaser-jsx';

function MyComponent() {
  const scene = useScene();
  // ...
}
```

> [!WARNING]
> Don't use the `useScene` hook if you start multiple Scenes.

Type your Scene with TypeScript:

```ts
class MyScene extends Phaser.Scene {
  // ...
}

const scene = useScene<MyScene>();
```

### `useState`

Manage state that triggers a re-render of your game object:

```ts
import { useState } from 'phaser-jsx';

function ScoreDisplay() {
  const [score, setScore] = useState(0);

  return (
    <Container>
      <Text text={`Score: ${score}`} />
      <Text
        text="Add Point"
        onPointerDown={() => setScore(score + 1)}
      />
    </Container>
  );
}
```

The setter also accepts a function updater:

```ts
setScore((prev) => prev + 1);
```

### `useEffect`

Run a side effect after render:

```ts
import { useEffect } from 'phaser-jsx';

function MyComponent() {
  useEffect(() => {
    console.log('mounted');
  }, []);
}
```

The second argument is a dependency array. The effect re-runs whenever a dependency changes:

```ts
const [score, setScore] = useState(0);

useEffect(() => {
  console.log('score:', score);
}, [score]);
```

Omit the dependency array to run the effect after every render:

```ts
useEffect(() => {
  console.log('rendered');
});
```

Return a cleanup function to run before the next effect or on unmount:

```ts
useEffect(() => {
  const listener = scene.input.on('pointerdown', handleClick);
  return () => listener.destroy();
}, []);
```

## Release

Release is automated with [Release Please](https://github.com/googleapis/release-please).

## License

[MIT](https://github.com/remarkablegames/phaser-jsx/blob/master/LICENSE)
