@jacekpietal / One For All

One For All

All Might from Boku No Hero Academia holds One For All in his palm

TypeScript gamedev library inspired by Unity

npm version build status license: MIT

Demo

[1x Scene]
├──[1x HTML Canvas]
├──[1x Collision Detection]
└──[50x GameObject (Player)]
├──[1x CircleBody]
└──[1x Animator]
└──[1x StateMachine]

Tiny code, big results! Check out the demo to see below code in action.

Also, here is the documentation.

Demo Code

src/demo/index.ts

async function start(): Promise<void> {
const queryParams = Scene.getQueryParams();
// create main Scene
const scene: Scene = new Scene({
visible: true,
autoSort: true,
showFPS: 'fps' in queryParams,
debug: 'debug' in queryParams
});

// initialize scene async - new since pixi 7/8
await scene.init({
resizeTo: window,
autoDensity: true,
autoStart: false,
sharedTicker: false
});

// wait to load cave-boy.json and cave-boy.png, uses PIXI.Loader inside
const data = await Resources.loadResource('./cave-boy.json');
const texture = await Resources.loadResource(data.tileset);

// create 50 sprites from template
Array.from({ length: Number(queryParams.limit || 50) }, () => {
createSprite({ scene, data, texture });
});

scene.start();
scene.update$.pipe(takeUntil(scene.destroy$)).subscribe(() => {
scene.physics.separate();
});
}

start();

src/demo/sprite.prefab.ts

export function createSprite({ scene, data, texture }): TGameObject {
// create game object
const gameObject = new GameObject('Player') as TGameObject;

// create body
gameObject.body = new CircleBody(gameObject, 20, 14);
gameObject.body.setPosition(
Math.random() * innerWidth,
Math.random() * innerHeight
);

// insert body to physics and game object to scene
scene.physics.insert(gameObject.body);
scene.addChild(gameObject);

// create animator with few animations from json + texture
gameObject.sprite = new Animator(gameObject, data, texture);
gameObject.sprite.setState('idle');

// subscribe to *own* update function until *own* destroy
gameObject.update$
.pipe(takeUntil(gameObject.destroy$))
.subscribe((deltaTime) => {
updateSprite(gameObject, deltaTime);
});

return gameObject;
}

export function updateSprite(gameObject: TGameObject, deltaTime: number): void {
const scale = gameObject.scene.stage.scale;
const gameObjects = gameObject.scene.children as TGameObject[];
const safeDelta = Math.min(60, deltaTime);
const chance = safeDelta * 0.01;

if (Math.random() < chance) {
// goto random place
gameObject.target = {
x: (Math.random() * innerWidth) / scale.x,
y: (Math.random() * innerHeight) / scale.y
};
} else if (Math.random() < chance) {
// goto random target
gameObject.target =
gameObjects[Math.floor(Math.random() * gameObjects.length)];
} else if (Math.random() < chance) {
// stop
gameObject.target = null;
}

if (gameObject.target && distance(gameObject.target, gameObject) < 9) {
gameObject.target = null;
}

if (!gameObject.target) {
gameObject.sprite.setState('idle');
} else {
gameObject.sprite.setState('run');

const angle: number = Math.atan2(
gameObject.target.y - gameObject.y,
gameObject.target.x - gameObject.x
);
if (!isNaN(angle)) {
const offsetX: number = Math.cos(angle);
const offsetY: number = Math.sin(angle);

if (gameObject.sprite instanceof Animator) {
const flipX: number =
Math.sign(offsetX || gameObject.sprite.scale.x) *
Math.abs(gameObject.sprite.scale.x);
// flip x so there is no need to duplicate sprites
gameObject.sprite.setScale(flipX, gameObject.sprite.scale.y);
}

// update body which updates parent game object
gameObject.body.setPosition(
gameObject.body.x + safeDelta * offsetX,
gameObject.body.y + safeDelta * offsetY
);
}
}
}

Features

Classes this library exports

  • Resources: Handles loading game assets like images and JSON files.
  • Scene: Sets the stage for gameplay, where all the action takes place.
  • GameObject: Represents characters, objects, or items in the game world.
  • Prefab: Instantiates ready-made templates for creating game elements.
  • Sprite: Displays static 2D graphics in the game.
  • Container: Organizes and manages groups of game objects for easier handling.
  • Animator: Useful JSON to Container with AnimatedSprite children.
  • StateMachine: Controls how game objects transition between actions.
  • CircleBody: Adds physics properties and interactions for round-shaped objects.
  • PolygonBody: Adds physics properties and interactions for polygonal objects.
  • TextureAtlas: Allows easy slicing of texture into smaller cached slices.

Installation

npm i @jacekpietal/oneforall --save

We also have tests

 PASS  src/state-machine.spec.ts
GIVEN StateMachine
THEN you can set validators (4 ms)
THEN you can't change state to invalid state
THEN you can change state to valid state

PASS src/component.spec.ts
GIVEN Component
THEN update publishes update$ (1 ms)
THEN destroy publishes destroy$

PASS src/circle-body.spec.ts
GIVEN CircleBody
THEN it has set property radius (5 ms)
THEN it can't have zero radius (9 ms)
THEN update propagates x/y changes

PASS src/scene-base.spec.ts
GIVEN SceneBase
THEN it works (3 ms)
THEN it can have children (1 ms)
THEN scene propagates update to gameobject to component (1 ms)

PASS src/scene.spec.ts
GIVEN Scene
THEN it works (2 ms)
THEN it can have children
THEN scene propagates update to gameobject to component (1 ms)

PASS src/sprite.spec.ts
GIVEN Sprite
THEN update propagates x/y changes (3 ms)
THEN removeChild works
THEN destroy works (1 ms)
THEN destroy works extended (1 ms)

PASS src/prefab.spec.ts
GIVEN Prefab
THEN can be instantiated (3 ms)
THEN can create 100 instances (10 ms)

PASS src/game-object.spec.ts
GIVEN GameObject
THEN you can add component (5 ms)
THEN update propagates to components (1 ms)
THEN you can remove component
THEN destroy removes component (1 ms)
THEN you can get component by label
THEN you can get components by label
THEN you can destroy 1000 bodies without problem (106 ms)

PASS src/polygon-body.spec.ts
GIVEN PolygonBody
THEN update propagates x/y changes

PASS src/container.spec.ts
GIVEN Container
THEN update propagates x/y changes
THEN destroy works (1 ms)

PASS src/resources.spec.ts
GIVEN Resources
THEN it silently fails and proceeds (5 ms)

PASS src/index.spec.ts
GIVEN index.ts
THEN basic imports work (1 ms)

PASS src/application.spec.ts
GIVEN Application
THEN it works

Test Suites: 13 passed, 13 total
Tests: 33 passed, 33 total