Sutra.js
A JavaScript behavior tree library for easily creating and managing complex behavior patterns
Install / Use
/learn @yantra-core/Sutra.jsREADME
Sutra
JavaScript Behavior Tree Library
Sutra is a versatile library for creating and managing behavior trees in JavaScript. It allows for easy definition of complex behavior patterns using a simple and intuitive syntax.
Sutra is an ideal tool for JavaScript game development. Using Sutra will significantly streamline your game logic, allowing you to focus on creating a tapestry of complex game behaviors in seconds.
Sutras can be exported to a human-readable format ( with i18n support ). It is easy to read your Sutra in plain English and then modify it using the fluent API.
Features
- Conditional Logic - Simple
if,then,elseconstructs to define trees - Composite Conditions - Composite conditional logic using
AND,OR,NOT - Nested Subtrees - Re-use Sutras for easy-to-understand composability of complex behavior
- Dynamic Condition Evaluation - Evaluate conditions based on entity data or global game state
- Action Control - Define action objects with scoped parameters
- Data Transformation with
.map()- Transform context data within the tree using custom maps - Node Management -
add,update,find, andremovenodes within the tree - Tree Querying - Query and manipulate the tree using intuitive string selectors
- Event-Driven Architecture -
.on()and.emit()methods for managing actions - Human-readable Exports - Support for exporting sutras to plain English
- Sutra JSON Format - Import and Export tree definitions in
sutra.jsonformat
Release 1.8.0 | Files | CDN | Size | |---------------|--------------------------------------------------|-----------| | sutra.js | Link | 55kb | | sutra.min.js| Link | 23kb |
Crafting Sutras
Here we have the human read-able exported Sutra definition that we will get at the end:
if isBoss
if isHealthLow
updateEntity
color: 0xff0000
speed: 5
Written as Javascript, this Sutra will be responsible for changing the color and speed of isBoss when isHealthLow.
<script src="https://yantra.gg/sutra.js"></script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
let sutra = SUTRA.createSutra();
sutra
.if('isBoss')
.if('isHealthLow')
.then('updateEntity', { color: 0xff0000, speed: 5 });
});
</script>
This is small display of API. Scroll further down to see full API usage.
Live Demos
Full-featured Demo
Sutra.js is responsible for all Behavior Trees in Mantra.js. Here we have a full-featured demo running a composition of Sutras. Remember, Sutras can be nested through Sutra.use().
DEMO: https://yantra.gg/mantra/home
https://github.com/yantra-core/Sutra.js/assets/70011/ec6d4518-a9aa-47e6-9299-9c6e1dc55b35
We are actively working on a collection of curated Sutras for developers to share and re-use for common game logic ( such as entities, movement, puzzles, weapons, etc ). You can find our current Sutras gallery links here: mantra-sutras
Simple Weather Example
Try Demo On Yantra Try Demo On Codepen
<body>
<script src="https://yantra.gg/sutra.js"></script>
<h1>Sutra Simple Weather Rules</h1>
<div id="output"></div>
<pre><code id="humanOutput"></code></pre>
<script>
document.addEventListener('DOMContentLoaded', () => {
let sutra = initializeSutra(); // Initialize Sutra
// update human readable output
let humanOutput = document.getElementById('humanOutput');
humanOutput.innerHTML = sutra.toEnglish();
executeSutra(sutra);
});
function initializeSutra() {
let sutra = SUTRA.createSutra();
// Define conditions
sutra.addCondition('isSunny', (data) => data.isSunny);
sutra.addCondition('isWindy', (data) => data.isWindy);
sutra.addCondition('isRaining', (data) => data.isRaining);
// Define nested actions
sutra
.if('isSunny')
.then((rules) => {
rules
.if('isWindy')
.then('findShelter')
.else('enjoySun');
})
.then('planPicnic');
sutra
.if('isRaining')
.then('stayIndoors')
.else((rules) => {
rules
.if('isWindy')
.then('wearWindbreaker')
.else('goForAWalk');
});
// Define event listeners for actions
sutra.on('findShelter', (data) => logOutput("It's sunny but windy. Finding shelter!"));
sutra.on('enjoySun', (data) => logOutput("It's a perfect sunny day. Enjoying the sun!"));
sutra.on('planPicnic', (data) => logOutput("Planning a picnic for the sunny day."));
sutra.on('stayIndoors', (data) => logOutput("It's raining. Better stay indoors."));
sutra.on('wearWindbreaker', (data) => logOutput("It's windy. Wearing a windbreaker."));
sutra.on('goForAWalk', (data) => logOutput("It's a nice day. Going for a walk."));
return sutra;
}
function executeSutra(sutra) {
// Array of test data for a week, including the day of the week
let weekWeather = [
{ day: 'Monday', isSunny: true, isWindy: false, isRaining: false },
{ day: 'Tuesday', isSunny: false, isWindy: true, isRaining: false },
{ day: 'Wednesday', isSunny: false, isWindy: false, isRaining: true },
{ day: 'Thursday', isSunny: true, isWindy: true, isRaining: false },
{ day: 'Friday', isSunny: false, isWindy: false, isRaining: false },
{ day: 'Saturday', isSunny: true, isWindy: false, isRaining: true },
{ day: 'Sunday', isSunny: true, isWindy: false, isRaining: false }
];
logOutput(`++++++++++++++++++++++++++++++++++++++++++++++`);
// Iterate over the weekWeather array and tick the Sutra with each day's data
weekWeather.forEach(dayWeather => {
// Constructing the emoji string based on conditions
let weatherEmoji = '';
weatherEmoji += dayWeather.isSunny ? '☀️' : '';
weatherEmoji += dayWeather.isWindy ? '💨' : '';
weatherEmoji += dayWeather.isRaining ? '🌧️' : '';
logOutput(`${weatherEmoji} ${dayWeather.day}`);
sutra.tick(dayWeather);
logOutput(`++++++++++++++++++++++++++++++++++++++++++++++`);
});
}
function logOutput(message) {
let output = document.getElementById('output');
let newElement = document.createElement('div');
newElement.textContent = message;
output.appendChild(newElement);
}
</script>
</body>
Stand-alone Sutra Example
Moves CSS Dot based on WASD Keyboard Inputs
Try Demo On Yantra Try Demo On Codepen
https://github.com/yantra-core/sutra/assets/70011/12e2a64a-1be4-42c5-9e0a-c97c0caba081
Full Game Level Designed in Sutra and <a href="https://github.com/yantra-core/mantra">Mantra</a>
Tower Defense Type Game
https://github.com/yantra-core/sutra/assets/70011/edd4d09a-f48b-431e-bf5b-377ad60e3c49
More Examples
Explore the ./examples folder for additional examples
How
The following code will create a Sutra that changes the color of the Boss entity when it's health is low.
import { Sutra } from '@yantra-core/sutra';
// creates a new sutra instance
const sutra = new Sutra();
// adds a new condition as function which returns value
sutra.addCondition('isBoss', (entity) => entity.type === 'BOSS');
// adds a new condition using DSL conditional object
sutra.addCondition('isHealthLow', {
op: 'lessThan',
property: 'health',
value: 50
});
sutra
.if('isBoss')
.if('isHealthLow')
.then('updateEntity', { color: 0xff0000, speed: 5 });
// exports the sutra as json
const json = sutra.toJSON();
console.log(json);
// exports the sutra as plain english
const english = sutra.toEnglish();
console.log(english);
Remark: The fluent chaining APIs are optional. Keep reading
Running a Sutra with Data
Now that we have crafted a suitable Sutra for detecting if the boss's health is low, we will need to send some data to the Sutra in order to run the behavioral tree logic.
For this example, we will create a simple array of entities:
// create a simple array of entities
let allEntities = [
{ id: 1, type: 'BOSS', health: 100 },
{ id: 2, type: 'PLAYER', health: 100 }
];
Then we'll create a simple gameTick() function to iterate through our Entities array:
// create a gameTick function for processing entities with sutra.tick()
function gameTick () {
allEntities.forEach(entity => {
sutra.tick(entity);
});
}
Now that we have a way to send data into our Sutra, we'll need to listen for events on the Sutra in order to know if any of our conditional actions have triggered.
// listen for all events that the sutra instance emits
sutra.onAny(function(ev, data, node){
// console.log('onAny', ev, data);
})
// listen for specific events that the sutra instance emits
sutra.on('updateEntity', function(entity, node){
// here we can write arbitrary code to handle the event
console.log('updateEntity =>', JSON.stringify(entity, true, 2));
// In `mantra`, we simply call game.emit('updateEntity', data);
});
Now that we have defined our Sutra, defined data to send our Sutra, and have added event listeners to the Sutra
