Skip to content

Ctrlmonster/koota-tree-queries

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

29 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Koota Tree Queries

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)

Enhance classic Koota relations

// 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);

Stupid nesting capabilities

// 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);

Too abstract? More gamified example:

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
    )
  )
);

Check out the tests for more examples.

I want to try it!

Sure, just copy the tree-query.ts source into your project (make sure you have Koota installed), and you're good to go.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published