Skip to main content

ยท 8 min read
Marak Squires

Introductionโ€‹

๐Ÿ‘‹๐Ÿฝ Hello! Let's get started with crafting Game custom logic using the Sutra library.

Sutraโ€‹

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. Sutras can be crafted into complex chains of behaviors, allowing for intricate game logic with minimal complexity.

Sutras can be exported to a human-readable format. If you don't prefer using code to define your Sutra we have a Visual Editor currently in development.

Crafting Sutrasโ€‹

Here we have the human read-able exported Sutra definition that we will get at the end:

if isBoss
if isHealthLow
entity::updateEntity
color: 0xff0000
speed: 5

It's clear to read that this Sutra will be responsible for changing the color and speed of isBoss when isHealthLow.

Howโ€‹

Sutras can be deeply nested, use composite conditions, evaluate dynamic conditions with scoped parameters, emit events, and each node can be dynamically updated. Sutra is full-featured. In order to navigate the feature matrix, it's best we start with a most basic example of detecting if a Boss Entity is low on health.

Full Example Code on Github

Now let's look at the actual JavaScript code that has generated the above output:

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.addAction({
if: ['isBoss', 'isHealthLow'],
then: [{
action: 'entity::updateEntity',
data: { 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);

This simple rule set will create a Sutra that changes the color of the Boss entity when it's health is low.

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('entity::updateEntity', function(entity, node){
// here we can write arbitrary code to handle the event
console.log('entity::updateEntity =>', JSON.stringify(entity, true, 2));
// In `mantra`, we simply call game.emit('entity::updateEntity', data);
});

Now that we have defined our Sutra, defined data to send our Sutra, and have added event listeners to the Sutra. We can run our gameTick() function once with the Boss at full-health, then again with the Boss at low health.

// run the game tick with Boss at full health
// nothing should happen
gameTick();

// run the game tick with Boss at low health
// `entity::updateEntity` event should be emitted
allEntities[0].health = 40;
gameTick();

Results in:

entity::updateEntity => {
id: 1,
type: 'BOSS',
health: 40,
color: 0xff0000,
speed: 5
}

It's that simple. This demonstrates a single-level conditional action using basic logic.

Composition and Nested Sutrasโ€‹

In the previous example, we created a simple compositional if statement which used two conditions, isBoss and isHealthLow. Sutra supports conditional composition as well as deeply nested behavior trees.

For example, our previous Sutra Action could be rewritten as:

sutra.addAction({
if: 'isBoss',
then: [{
if: 'isHealthLow',
then: [{
action: 'entity::updateEntity',
data: { color: 0xff0000, speed: 5 } // Example with multiple properties
}]
}]
});

It's also possible to create a compositional condition using a logic operator. The default logical operator always defaults to and.

// Composite AND condition
sutra.addCondition('isBossAndHealthLow', {
op: 'and', // and, or, not
conditions: ['isBoss', 'isHealthLow']
});

Global Game State Scopeโ€‹

In many cases, your Sutra will need to reference a context outside of the Entity it's evaluating. For example, a global gameData object may have information about the boundary size of the map, or a global constant value such as maxUnits which is required as reference for a spawner.

For Global Game State, sutra.tick() supports an additional context property as it's second argument.

const gameState = { isGameRunning: false };
const allEntities = [
{ id: 1, type: 'BOSS', health: 40 },
{ id: 2, type: 'PLAYER', health: 100 }
];

sutra.addCondition('isGameRunning', (entity, gameState) => gameState.isGameRunning);
sutra.addCondition('isBoss', (entity) => entity.type === 'BOSS');
sutra.addCondition('isHealthLow', {
op: 'lessThan',
property: 'health',
value: 50
});

sutra.addAction({
if: ['isGameRunning', 'isBoss', 'isHealthLow'],
then: [{
action: 'entity::updateEntity',
data: { speed: 5 }
}]
});

allEntities.forEach(entity => {
sutra.tick(entity, gameState);
});
// nothing happens, `isGameRunning` condition returns false

// update the global game state
gameState.isGameRunning = true;

allEntities.forEach(entity => {
sutra.tick(entity, gameState);
});
// `entity::updateEntity` will be emitted

Dynamic Action Valuesโ€‹

Some Sutras may require a dynamic value by function reference when evaluating triggered actions. For example, if you wanted to change the Boss's color to a random color instead of providing a static color.

In this example, you will pass a function reference as a value, which will be dynamically executed upon each condition evaluation using the appropriate tree scope.

// Function to generate a random color integer
function generateRandomColorInt(entity, gameState, node) {
// entity is the entity scoped which is being evaluated
// gameState is the optional second argument to sutra.tick(entity, gameState)
// node is the reference to current Sutra Tree node element
return Math.floor(Math.random() * 255);
}

sutra.addAction({
if: ['isBoss', 'isHealthLow'],
then: [{
action: 'entity::updateEntity',
data: { color: generateRandomColorInt, speed: 5 }
}]
});

Using Nested Sutras with Subtreesโ€‹

Nested Sutras with subtrees provide a powerful way to organize complex behavior trees into modular, manageable sections. This feature allows you to create distinct Sutras for different aspects of your game logic and then integrate them into a main Sutra. Each subtree can have its own conditions and actions, which are executed within the context of the main Sutra.

Implementing Nested Sutrasโ€‹

Consider a tower defense game where we need separate logic for round management and NPC actions. We can create two Sutras: roundSutra for round logic and npcLogic for NPC behavior.

see: ./examples/nested-sutra.js

import Sutra from '@yantra-core/sutra';

let roundSutra = new Sutra();
roundSutra.addCondition('roundStarted', (entity, gameState) => gameState.roundStarted === true);
roundSutra.addCondition('roundEnded', (entity, gameState) => gameState.roundEnded === true);
roundSutra.addCondition('roundRunning', {
op: 'not',
conditions: ['roundEnded']
});

let npcLogic = new Sutra();
npcLogic.addCondition('isSpawner', (entity) => entity.type === 'UnitSpawner');
npcLogic.addAction({
if: 'isSpawner',
then: [{
action: 'spawnEnemy',
data: {
type: 'ENEMY',
position: { x: 100, y: 50 },
health: 100
}
}]
});

let levelSutra = new Sutra();
levelSutra.use(roundSutra);
levelSutra.use(npcLogic, 'npcLogic'); // optionally, identify the subtree with name
levelSutra.addAction({
if: 'roundRunning',
subtree: 'npcLogic'
});

In this setup, roundSutra and npcLogic are defined separately with their specific conditions and actions. Then, they are integrated into the main levelSutra``. The roundRunning condition in levelSutra`` governs whether the npcLogic subtree should be executed.

Running Nested Sutrasโ€‹

To run a nested Sutra, you call the tick method on the main Sutra with relevant data and `gameState``. The main Sutra evaluates its conditions and decides whether to invoke the actions or subtrees.

levelSutra.tick({ type: 'UnitSpawner' }, { roundStarted: true, roundEnded: false });

Moreโ€‹

Sutra's API is versatile and still in development. We have several examples and a comprehensive test suite which serves as the definitive source of the API specification.

If you are interested in Sutra or have any additional question you can Open an Issue or Join our Discord

Try Sutra with Mantra on Yantra

mantra games can be developed offline using Sutra Rules. These games can be deployed to yantra where the Sutra Rules are enforced as authorative server logic.

The best way to incorporate Sutra with Matra is using the Sutra Plugin. This will enable you to extend any mantra game to use Sutra Behavioral Trees for controlling game state and entities.

Here is a live demo of Sutra + Mantra with CodePen

ยท 10 min read
Marak Squires

Introductionโ€‹

๐Ÿ‘‹๐Ÿฝ Greetings, fellow humans!

Mantraโ€‹

Mantra is a revolutionary game development framework, unlike any other solutions on the market. Mantra removes the complications of implementing graphics, physics, entities, and high-performance netcode, allowing you to begin coding your game's logic and design instantly using your favorite libraries. We promise you will say Mantra each day once you try it out!

The quickest way to start up a Mantra is by including this snippet on any HTML page:

<script src="https://yantra.gg/mantra.js"></script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
let game = new MANTRA.Game();
// optionally use plugins like Bullet
game.use('Bullet');
game.start();
});
</script>

This will create a new Mantra.Game instance using default settings. You customize the game instance by using Plugins and registering event emitters.

To jump straight into the interactive examples, see the following:

Demosโ€‹

View all Demos on Yantra.gg

Online Demosโ€‹

LibrariesDemo Link
Matter.js + Babylon.jsYantra
Matter.js + Phaser 3Yantra

Offline Demosโ€‹

LibrariesDemo LinkCodePen Link
Matter.js + Babylon.jsYantraCodePen
Matter.js + Phaser 3YantraCodePen
Matter.js + CSS GraphicsYantraCodePen
NVIDIA PhysX 5.3.0 + Babylon.jsYantraCodePen
RenderPlex - Matter.js + Babylon.js + Phaser 3YantraCodePen

Using the CodePen links you can view these examples in the browser and begin modifying game code immediately.

Yantraโ€‹

Yantra is the world's first Serverless Physics hosting platform. Yantra allows developers to deploy their Mantra Game class directly to the cloud. Powered by Hathora, Mantra games hosted on Yantra can auto-scale to thousands of concurrent players in all major global regions

To get started with Yantra install the CLI tool with the following command:

npm install -g yantra-core/cli

Your system will now have access to the yantra command. Run yantra login to create a new account.

Section 1: What is Mantra?โ€‹

At its core, Mantra can be considered an Entity Component System with an extensible plugin system. Mantra works both on the server and in the browser using the same code.

Everything in Mantra is a plugin, and Games are created by creating and combining plugins called by game.use(pluginInstance).

Mantra works 100% offline from the ground up while seamlessly transitioning to a serverless hosted environment.

Mantra is highly configurable while using a convenient set of conventions for all default settings.

Section 2: What is Yantra?โ€‹

Yantra is a serverless hosting platform where you can deploy Mantra games. Don't be concerned; you can always deploy your Mantra games to any standard hosting solution using mantra-server. It's also possible to deploy your Mantra Game class directly to Cloudflare Edge workers using mantra-edge.

One of the key benefits of using Yantra is Yantra's ability to host your game's authoritative backend serverlessly, ensuring that you will only pay for the actual usage. Yantra is capable of auto-scaling your game's backend to thousands of players across the entire globe. All of this, without any special coding or esoteric configuration options. Yantra's specialized serverless physics hosting cloud ensures your game will always have low latency, fast response times, and global scalability.

Section 3: Getting Started with Mantraโ€‹

Currently, Mantra is still ALPHA software and is currently under active development. Features are being added and refined. Your feedback and contributions at this stage are invaluable and will shape the future of this framework.

The simplest way to start with Mantra development is to clone the starter-blueprint which includes both a server and several clients.

git clone [email protected]:yantra-core/starter-blueprint.git
cd starter-blueprint
npm install

Start Local Development Serverโ€‹

From here, you can start your game using:

npm start

This command will start a local development server at http://127.0.0.1:8888. From here, you can pick any one of the offline examples in the ./client/examples folder and begin modifying the code and extending the Game object.

Authoritative Server / Online Clientโ€‹

In order to test connect to the local websocket server use the client found at: http://127.0.0.1:8888/online.html

Section 4: Building Your First Gameโ€‹

When building your first game using Mantra, you will first decide which Physics and Graphics engines you will use. The current default settings are 2D Physics with 3D Graphics using Matter.js and Babylon.js.

let game = new MANTRA.Game({
width: 2000,
height: 2000,
physics: 'matter', // enum, 'physx', 'matter
collisions: true,
graphics: ['babylon'], // array enum, 'Babylon,' 'phaser,' 'CSS,' 'three.'
camera: 'follow'
});

Now that we have a game instance, we will add some of the default Plugins to extend functionality:

let Plugins = MANTRA.plugins;

// Adds projectile Bullets to the game
game.use('Bullet')

// Adds destructible Blocks to the game
game.use('Block');

// Adds a nice StarField background
game.use('StarField');

// Adds a controller input legend
game.use('ControlsGUI');

// Shows the current frames per second
game.use('CurrentFPS');

Now your game has bullets, destructible blocks, a StarField, and two GUI components showing Inputs and Current FPS.

The game will use the default player creation logic if we do not provide a custom player::joined event handler. Let's customize our game to have custom players and not use the default player creation logic:

function getRandomCoordinate() {
// Random number between -1000 and 1000
return Math.floor(Math.random() * 2001) - 1000;
}

// custom player::joined handler, this is optional
// without setting a player::joined, the default event is used
game.on('player::joined', function (playerData) {
// Generate random starting positions for players
let randX = getRandomCoordinate();
let randY = getRandomCoordinate();
console.log('player joined', playerData)
let player = game.createEntity({
type: 'PLAYER',
position: {
x: randX,
y: randY
}
});
console.log('player created', player)
// make sure to emit the player::created event
game.emit('player::created', player);
});

Now that we have customized our game with plugins and registered a custom player creation event, we can call game.start(), which will start the local offline gameloop. Once our game starts, we will create a single destructible block.

// start the game loop
game.start(function () {
// Creates a single Block; since we have used Block plugin, this will be a destructible Block
game.createEntity({
type: 'BLOCK',
width: 500,
height: 500,
depth: 200,
position: {
x: 0,
y: -500
},
});
});

Section 5: Deploying with Yantraโ€‹

The easiest way to deploy your Mantra game is using the yantra cli tool.

Configuring your Game for Deploymentโ€‹

First, make sure you have installed the yantra-cli

npm install -g yantra-core/cli

Now, create a new directory on your local system:

mkdir my-awesome-game
cd my-awesome-game

Now run yantra init:

yantra init 

From here, you will be prompted ( press ENTER for all values if you wish )

Then you should see the following:

ReadMe.md
client
package.json
server.js
my-awesome-game initialized successfully!

Now, your game is almost ready to deploy!

You must run npm install after the project initializes. Once you install the required dependencies, you can run npm start to test the game locally at http://localhost:8888

Login with Emailโ€‹

If you haven't already created an account, simply run:

yantra login

You will be prompted to authenticate with an One Time Password sent to your email.

Deploying your gameโ€‹

Once you have successfully initiated your game, you can deploy it with the following command:

yantra deploy

It's that simple! Your game will zip itself up and deploy to Yantra. In return, you will receive a shareable game link and you will receive a game link that looks like this:

https://yantra.gg/mantra/hathora?world=my-awesome-game&owner=Marak

This static link will now host your Game. Your servers will remain idle until a player connects at which point they will quickly boot up to meet the demand.

Region is determined by a ping test and may be optionally overriden using the region query parameter.

Available Regions: /yantra-api/available-regions

Section 6: Entity Input and Movementโ€‹

So far, we've been using the default Entity Input and Entity Movement systems. These default systems provide an input mapping for your input devices ( keyboard, gamepad, mouse ) and a corresponding movement system to match the inputs.

Inside these systems, you can implement chainable Strategies based on the design of your game. This simple yet elegant design allows for unlimited customization of entity control, allowing for 2D, 2.5D, or 3D control systems all within the same game.

If you want more advanced ways to customize your Mantra game, please drop by our Discord to say hello!

Section 7: Community and Supportโ€‹

Mantra is an open-source project and under active development. Features are being added and refined. Your feedback and contributions at this stage are invaluable and will shape the future of this framework.

The main Mantra Github Repository:

https://github.com/yantra-core/mantra

Get in touch by joining the AYYO Discord:
https://discord.gg/bbyjf6MXC2

Conclusion

In conclusion, Mantra stands out as an innovative and highly versatile game development framework that drastically simplifies the game creation process. Its seamless integration with Yantra makes deploying games a breeze, catering to both novice and experienced developers. The framework's focus on removing complexities in graphics, physics, and net code implementation allows developers to concentrate more on the creative aspects of game design.

With its extensible plugin system and compatibility with various libraries, Mantra provides an adaptable environment that supports both offline and online game development. The incorporation of serverless technology through Yantra further enhances the game development experience by offering auto-scaling capabilities and global reach, ensuring that your games perform optimally regardless of the number of concurrent players.

Mantra's open-source nature and active development community make it a continually evolving platform, inviting contributions and feedback from its users. This collaborative approach ensures that Mantra stays relevant and up-to-date with the latest gaming trends and technologies.

Get your Mantra on Yantra!โ€‹

We invite you to dive into the world of game development with Mantra and Yantra. Whether you're a seasoned game developer or just starting out, these tools offer an exciting opportunity to bring your gaming ideas to life with ease and efficiency.

  • Explore Mantra: Visit the GitHub repository to explore the framework's capabilities and get started with your first project.
  • Join the Community: Become part of the growing community by joining the AYYO Discord. Here, you can connect with other developers, share ideas, and get support.
  • Stay Updated: Sign up for updates to stay informed about the latest developments in Mantra and Yantra. Your feedback and contributions are invaluable in shaping the future of these tools.

Start your game development journey today with Mantra and Yantra, and be part of a community that's redefining the boundaries of game creation. Happy coding!