Skip to content

Commit

Permalink
chore: update README.md, add cover
Browse files Browse the repository at this point in the history
  • Loading branch information
isaac-mason committed Nov 2, 2023
1 parent 87319cf commit 61448db
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 62 deletions.
Binary file added cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 67 additions & 62 deletions packages/arancini/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
# arancini
![cover](https://raw.githubusercontent.com/isaac-mason/arancini/main/packages/arancini/cover.png)

Arancini is a JavaScript object based Entity Component System.
[![Version](https://img.shields.io/npm/v/arancini?style=for-the-badge)](https://www.npmjs.com/package/arancini)
![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/isaac-mason/arancini/release.yml?style=for-the-badge)
[![Downloads](https://img.shields.io/npm/dt/arancini.svg?style=for-the-badge)](https://www.npmjs.com/package/arancini)
[![Bundle Size](https://img.shields.io/bundlephobia/min/arancini?style=for-the-badge&label=bundle%20size)](https://bundlephobia.com/result?p=arancini)

# arancini - an object-based Entity Component System (ECS) library for JavaScript

```
> npm i arancini
```

- 💙 ‎ TypeScript friendly
- 💪 ‎ Flexible and extensible
- 🍱 ‎ Entities are regular objects, components are properties
- 🔍 ‎ Fast reactive queries powered by bitsets
- 🍃 ‎ Zero dependencies
- 🖇 ‎ [Easy integration with React](https://github.com/isaac-mason/arancini/tree/main/packages/arancini-react)

- 🧠 ‎ Define Systems with arancini, or bring your own system logic
- 🧩 ‎ Framework agnostic, plug arancini into whatever you like
- ⚛️ ‎ [Easy integration with React](https://github.com/isaac-mason/arancini/tree/main/packages/arancini-react)
- 💙 ‎ TypeScript friendly

## Introduction

Arancini is an Entity Component System (ECS) library for JavaScript. It aims to strike a balance between ease of use and performance.

In arancini, entities are regular javascript objects, and components are properties on those objects. You can use arancini to structure demanding applications such as games and simulations.
You can use arancini to structure demanding applications such as games and simulations.

If you aren't familiar with Entity Component Systems, this is a good read: https://github.com/SanderMertens/ecs-faq

Expand All @@ -30,23 +35,23 @@ TL;DR - Entity Component Systems are a data-oriented approach to structuring app
A world represents your game or simulation. It contains entities, updates queries, and runs systems.

```ts
import { World } from "arancini";
import { World } from 'arancini'

// (optional) define a type for entities in the world
type Entity = {
position?: { x: number; y: number };
health?: number;
velocity?: { x: number; y: number };
inventory?: { items: string[] };
};
position?: { x: number; y: number }
health?: number
velocity?: { x: number; y: number }
inventory?: { items: string[] }
}

// create a world
const world = new World<Entity>({
components: ["position", "health", "velocity", "inventory"],
});
components: ['position', 'health', 'velocity', 'inventory'],
})

// initialise the world
world.init();
world.init()
```

> **Note:**
Expand All @@ -59,9 +64,9 @@ In arancini, entities are regular objects, and components are properties on thos
You can use `world.create` to create an entity from any object. This adds the entity to the world, and adds any components that are defined on the object.

```ts
const playerEntity = { position: { x: 0, y: 0 } };
const playerEntity = { position: { x: 0, y: 0 } }

world.create(playerEntity);
world.create(playerEntity)
```

### 📦 Adding and Removing Components
Expand All @@ -70,35 +75,35 @@ To add and remove components from an entity, you can use `world.add`, `world.rem

```ts
/* add a component */
world.add(playerEntity, "health", 100);
world.add(playerEntity, 'health', 100)

/* remove a component */
world.remove(playerEntity, "health");
world.remove(playerEntity, 'health')

/* add and remove multiple components with a partial entity */
world.update(playerEntity, {
// add a component
velocity: { x: 1, y: 0 },
// remove a component
poisioned: undefined,
});
})

/* add and remove multiple components with an update callback */
world.update(playerEntity, (e) => {
// add a component
e.velocity = { x: 1, y: 0 };
e.velocity = { x: 1, y: 0 }

// remove a component
delete e.poisioned;
});
delete e.poisioned
})
```

### 🗑 Destroying Entities

To destroy an entity, use `world.destroy`.

```ts
world.destroy(playerEntity);
world.destroy(playerEntity)
```

> **Note:** Destroying an entity does not remove any properties/components from the entity object, it just removes the entity from the world and all queries.
Expand All @@ -108,7 +113,7 @@ world.destroy(playerEntity);
You can query entities based on their components with `world.query`. Queries are reactive, they will update as entities in the world change.

```ts
const monsters = world.query((q) => q.all("health", "position", "velocity"));
const monsters = world.query((q) => q.all('health', 'position', 'velocity'))
```

> **Note:** Arancini dedupe queries with the same filters, so you can create multiple of the same query without performance penalty!
Expand All @@ -117,35 +122,35 @@ Arancini supports `all`, `any`, and `none` filters. The query builder has some a

```ts
const monsters = world.query((q) =>
q.all("health", "position").any("skeleton", "zombie").none("dead"),
);
q.all('health', 'position').any('skeleton', 'zombie').none('dead')
)

const monsters = world.query((entities) =>
entities
.with("health", "position")
.and.any("skeleton", "zombie")
.but.not("dead"),
);
.with('health', 'position')
.and.any('skeleton', 'zombie')
.but.not('dead')
)
```

You can iterate over queries using a `for...of` loop (via `Symbol.iterator`). This will iterate over entities in reverse order, which must be done to avoid issues when making changes that will remove entities from queries. You can also use `query.entities` directly.

```ts
const monsters = world.query((q) => q.all("health", "position", "velocity"));
const monsters = world.query((q) => q.all('health', 'position', 'velocity'))

const updateMonsters = () => {
/* iterates over entities in reverse order */
for (const monster of monsters) {
if (monster.health <= 0) {
world.destroy(monster);
world.destroy(monster)
}

monster.position.x += monster.velocity.x;
monster.position.y += monster.velocity.y;
monster.position.x += monster.velocity.x
monster.position.y += monster.velocity.y
}
};
}

console.log(monsters.entities);
console.log(monsters.entities)
```

### 📡 Query Events
Expand All @@ -155,17 +160,17 @@ Queries emit events when entities are added or removed.
These events are be emitted after internal structures are updated to reflect the change, but before destructive changes are made to entities, e.g. removing components.

```ts
const query = world.query((e) => e.has("position"));
const query = world.query((e) => e.has('position'))

const handler = (entity: Entity) => {
// ...
};
}

query.onEntityAdded.add(handler);
query.onEntityAdded.remove(handler);
query.onEntityAdded.add(handler)
query.onEntityAdded.remove(handler)

query.onEntityRemoved.add(handler);
query.onEntityRemoved.remove(handler);
query.onEntityRemoved.add(handler)
query.onEntityRemoved.remove(handler)
```

### 👀 Ad-hoc Queries
Expand All @@ -175,9 +180,9 @@ You can use `world.filter` and `world.find` to get ad-hoc query results.
This is useful for cases where you want to get results infrequently, without the cost of evaluating a reactive query as the world changes.

```ts
const monsters = world.filter((e) => e.has("health", "position", "velocity"));
const monsters = world.filter((e) => e.has('health', 'position', 'velocity'))

const player = world.find((e) => e.has("player"));
const player = world.find((e) => e.has('player'))
```

### 🧠 Systems
Expand All @@ -190,15 +195,15 @@ While arancini has built-in support for systems, it's worth noting that there's

```ts
class MovementSystem extends System<Entity> {
moving = this.query((e) => e.has("position", "velocity"));
moving = this.query((e) => e.has('position', 'velocity'))

onUpdate(delta: number, time: number) {
for (const entity of this.moving) {
const position = entity.get("position");
const velocity = entity.get("velocity");
const position = entity.get('position')
const velocity = entity.get('velocity')

position.x += velocity.x;
position.y += velocity.y;
position.x += velocity.x
position.y += velocity.y
}
}

Expand All @@ -212,19 +217,19 @@ class MovementSystem extends System<Entity> {
}

// Register the system
world.registerSystem(ExampleSystem);
world.registerSystem(ExampleSystem)

// Use `world.step()` to run all registered systems
world.step(1 / 60);
world.step(1 / 60)
```

#### System priority

Systems can be registered with a priority. The order systems run in is first determined by priority, then by the order systems were registered.

```ts
const priority = 10;
world.registerSystem(MovementSystem, priority);
const priority = 10
world.registerSystem(MovementSystem, priority)
```

#### Required system queries
Expand All @@ -233,10 +238,10 @@ System queries can be marked as 'required', which will cause `onUpdate` to only

```ts
class ExampleSystem extends System {
requiredQuery = this.query((q) => q.has("position"), { required: true });
requiredQuery = this.query((q) => q.has('position'), { required: true })

onUpdate() {
const { x, y } = this.requiredQuery.first;
const { x, y } = this.requiredQuery.first
}
}
```
Expand All @@ -249,10 +254,10 @@ Singletons are useful for accessing components that are expected to exist on a s

```ts
class ExampleSystem extends System {
player = this.singleton("player", { required: true });
player = this.singleton('player', { required: true })

onUpdate() {
console.log(player);
console.log(player)
}
}
```
Expand All @@ -265,10 +270,10 @@ Systems can be attached to other systems with `this.attach`. This is useful for

```ts
class ExampleSystem extends System {
otherSystem = this.attach(OtherSystem);
otherSystem = this.attach(OtherSystem)

onUpdate() {
this.otherSystem.foo();
this.otherSystem.foo()
}
}
```
Expand Down
Binary file added packages/arancini/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

1 comment on commit 61448db

@vercel
Copy link

@vercel vercel bot commented on 61448db Nov 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.