High performance, nestable queries that let you define custom filter functions for Koota.
- All entities that are in range of your player? Easy.
- All entities that are in range of your player that are hostile to the player? Sure.
- All entities that have a yellow pickaxe that was stolen from a Goblin that's currently fishing? No problem.
// Step 1: define your filter, could be anything, just make sure to return a boolean
import {createTreeQuery, createTreeQueryFilter} from "./tree-query";
const Likes = createTreeQueryFilter((entity1, entity2, world) => {
// use any criteria you like, e.g:
// - compute distance between them to see if they're close,
// - check if they have the same target
// - roll a dice,
// you name it.
return likes(entiy1, entity2);
});
// Step 2: Create a query
const myQuery = createTreeQuery(Foo, Bar, Likes(Baz))
// returns: all entities that have foo, bar and
// 'Likes' returns true for at least one entity with Baz!
const entities = myQuery(world)// two Koota relations: IsParentOf and IsChildOf
parent.add(IsParentOf(child1), IsParentOf(child2));
child1.add(IsChildOf(parent));
child2.add(IsChildOf(parent));
// Custom Filter Function now lets you query for siblings even
// though there is no SiblingOf relation defined!
const HasSiblings = createTreeQueryFilter((e1, e2, _world) => {
const parentOfEntity1 = e1.targetFor(IsChildOf);
const parentOfEntity2 = e2.targetFor(IsChildOf);
// we got the same parent! we must be siblings
return (parentOfEntity1 === parentOfEntity2) &&
(parentOfEntity1 !== undefined) &&
(e1 !== e2); // careful: entities can match themselves
});
// Create a Tree Query
const siblings = createTreeQuery(HasSiblings(), /* add any extra traits here*/);
expect(siblings(world).length).toBe(2);
// -----------------------------------------------------------------------------
// Add extra traits
child1.add(Foo);
child2.add(Bar);
// query more specifically:
// Only siblings that also have Foo and Bar respectively!
const HasSiblingWithFoo = createTreeQuery(HasSiblings(Foo), /*Bar*/);
const HasSiblingWithBar = createTreeQuery(HasSiblings(Bar), /*Foo*/);
expect(HasSiblingWithFoo(world).length).toBe(1);
expect(HasSiblingWithBar(world).length).toBe(1);
expect(HasSiblingWithBar(world)).toContain(child1);
expect(HasSiblingWithFoo(world)).toContain(child2);// spawn a tree of Koota relationships
const treeRoot =
world.spawn(A, IsParentOf(
world.spawn(B, IsParentOf(
world.spawn(C, IsParentOf(
world.spawn(D, IsParentOf(
world.spawn(E)
))
))
))
));
// match that tree exactly as a query
const TreeRoot = createTreeQuery(
A, HasChild(
B, HasChild(
C, HasChild(
D, HasChild(
E))))
);
// we get back the root node
expect(TreeRoot(world).length).toBe(1);
expect(TreeRoot(world)).toContain(treeRoot);
// delete any part of the tree and the query no longer matches
world.queryFirst(E)!.destroy();
expect(TreeRoot(world).length).toBe(0);import {relation} from "koota";
const Position = trait({x: 0, y: 0});
const Radius = trait({value: 0});
const IsSpaceship = trait(); // π
const IsHealthPickup = trait(); // ποΈοΈ
// our custom query filter function:
const InPickupRange = createTreeQueryFilter((eid1, eid2, _world) => {
// get both positions and calc distance against a threshold (radii here)
const myPos = eid1.get(Position)!;
const otherPos = eid2.get(Position)!;
const myRadius = eid1.get(Radius)!.value;
const otherRadius = eid2.get(Radius)!.value;
// check if in range considering center distances and both radii
const dist = Math.sqrt((myPos.x - otherPos.x) ** 2 + (myPos.y - otherPos.y) ** 2);
return (dist - otherRadius) <= myRadius;
});
// -----------------------------------------------------------------------------
// example query 1 π(ποΈ): check for spaceships that have pickups in range
const spaceshipsWithPickupsInRange = createTreeQuery(
IsSpaceship, Radius, Position,
InPickupRange(IsHealthPickup, Radius, Position) // the filter needs Radius and Pos
);
// example query2 ποΈ(π): check for pickups that are in range of a spaceship
const pickupsThatHaveASpaceshipInRange = createTreeQuery(
IsHealthPickup, Radius, Position,
InPickupRange(IsSpaceship, Radius, Position)
);
// example query3 ποΈ(π(π§)): check for pickups that are in range of a spaceship
// that is controlled by a player
const pickupsInPlayerShipRange = createTreeQuery(
IsHealthPickup, Radius, Position,
InPickupRange(IsSpaceship, Radius, Position,
OperatedBy(IsPlayer)
)
);
// -----------------------------------------------------------------------------// let's add some spice: Explosives! π£
const IsExplosiveOnContact = trait();
const explosive = world.spawn(IsExplosiveOnContact, Radius, Position)
// query for explosives that are closeby!
const explosivesAboutToGoBoom = createTreeQuery(
Radius, Position, IsExplosiveOnContact,
// we restrict explosives to not interact with each other, or health pickups.
// (that restriction coud easily be lifted π₯)
InPickupRange(Radius, Position, Not(IsExplosiveOnContact), Not(IsHealthPickup))
);
// Not enough?
// π(ποΈ)(π£)
const spaceshipsLivingDangerously = createTreeQuery(
IsSpaceship, Position, Radius, // spaceships,
// that are in range of:
InPickupRange(
IsHealthPickup, Position, Radius, // health pickups,
// that are in range of:
InPickupRange(
IsExplosiveOnContact, Position, Radius // explosives
// ... we could continue here
)
)
);Sure, just copy the tree-query.ts source into your project (make sure you have Koota installed), and you're good to go.