Categories
Advanced Tutorials

Accessing table methods in Lua

Preface

Object Oriented Programming (OOP), is one the most used programming techniques used in the modern era of programming. There are a lot of programming languages that support OOP, and Lua provides a number of ways to practice OOP in your code. Lua isn’t a true OOP language itself, but the way it’s implemented in Crayta exposes several OOP concepts for use (tables/scripts, properties etc.).

Crayta uses the OOP paradigm in its scripts, allowing you to easily take advantage of it in your code.

In this tutorial we will explain how to access table values (properties and methods of an object, in OOP lingo) and common pitfalls to look for.

In Unity the C# language fully supports OOP. The Unity script classes, properties and method will look familiar in comparison to how a Crayta script is structured and used.

Objects in Lua

You implement OOP in Lua with the help of tables and first class functions. By placing functions and related data into a table, an object is formed.

Tables in Lua have the features of an OOP object like state and identity that is independent of its values. Two objects (tables) with the same value are different objects, whereas an object can have different values at different times, but it is always the same object. Like objects and tables have a life cycle that is independent of who created them or where they were created.

Each Crayta script is implemented as a table that can reference a number of properties and methods.

Accessing table methods

Understanding Colon and Dot Operators

Many programmers learning Lua for the first time struggle with the difference between a function call that uses a colon (:) to call table method and a call that uses a dot/period (.). 

For example:

local length = string.len( "Hello World" )
enemyEntity:SetRotation(rotation)

Both methods will work and execute correctly, so when should you be using each one?

Deciding whether to use the colon or dot syntax, to access a method, can often be determined by whether you need a reference to the object within the function. This is irrelevant for standalone functions, but it’s quite important for functions which are defined as methods of objects.

The colon (:) is used for implementing methods that automatically pass self as the first parameter.

The following call:

enemyEntity:SetRotation(rotation)

Has the same result with calling this method:

enemyEntity.SetRotation(enemyEntity, rotation)

So basically the colon (:) is used as a short form of calling a method and having self appended as a parameter automatically by the compiler.

Deciding whether to use the colon or dot syntax can often be determined by whether you need a reference to the object within the function.

Example error

Here is an example of a Crayta error, when using dot instead of colon out of place. Normally to access the entity the current script is attached to, you can do it like this:

self:GetEntity()

If you use a dot instead of colon, you will get an error like the following:

self.GetEntity()

This happens because of the GetEntity() Crayta method is expecting a reference to the script instance. If you do the following, the method will work correctly:

self.GetEntity(self)

Of course using the colon instead of dot here will result in cleaner and more readable code.

Here is a full Crayta script demonstrating how colon and dot is used in place:

local CollectableScript = {}

function CollectableScript:Init()
    self.collected = false;
end

function CollectableScript:OnCollision(collidingPlayerOrEntity)
    
    if self.collected == true then return end

    self.collected = true
    local name = self:GetEntity():GetName()
    local nameLength = string.len(name)
    
    -- inform the Player/UI of the item collected
    if collidingPlayerOrEntity:IsA(Character) then
        collidingPlayerOrEntity:SendToLocal('Collected', name, nameLength)    
    end 
end

return CollectableScript

Accessing table properties

We should mention here, to avoid confusion, that table properties, like the ones a Crayta script exposes for editing, always use the dot (.) operator.

Health.Properties = {}

Health.Properties = {
   { name = "maxHealth", type = "number"}
}

function Health:Init(){
   Print(self.properties.maxHealth);
}
Categories
Advanced Tutorials

Packages, migrating level components from one game to another

Preface

Unless you are developing a very small and basic game, you will quite often reach a point of complexity in your game’s setup that requires a shift in the way you build it. The ability to split your game in smaller pieces or components, that can be developed and tested separately can help a lot with reducing that complexity. Crayta provides a powerful feature that allows you to do this, called packages.

In this tutorial we will explore how we can use packages to migrate components from our game, like voxels and templates, to another game.

In Unity there is a similar tool that implements this concept of packaging parts of your game to be used in another scene or game altogether, called Packages as well.

You are able to create Packages that contain models, textures, prefabs etc, export them and import them in another project.

Unity provides an Asset Store where users can publicly share packages.

Packages

In Crayta, at any point, you are able to create new packages that contain any number of valid assets. Assets that can be included in the package are:

  • Voxel Meshes
  • Scripts
  • Widgets
  • Templates
  • Other packages (can be referenced as dependencies)

Core Crayta assets like Meshes or Sounds can’t be added in a package directly, although they can be part of a Template or World asset which can be added.

All new packages created are made available to all of your Crayta projects via the Library. When you are creating a new package you can make it public which in turn will make the package available not only to your projects but to all user created projects.

Packages Library

In Advanced Mode under the Library tab you can see a list of all the packages available to your project. Those include packages that have been installed from the Crayta library and those created in this project.

From here you can explore the contents of each package, review it, add it to your favourites and also update a package to its latest, if available, version.

Creating a Package

Clicking Create New Package will open the relevant window to start creating a new package.

There are several fields available here to provide a name, description, optional tags etc. for the package with the most important field being the Assets drag and drop zone. There you can drag and drop any valid asset from the Library window to be included in the new package.

You can add as many assets as you like this way in the package, and they will all be listed in the Assets list.

You can also drag and drop other packages to the Package dependencies field to be included here and Crayta will automatically add them as a dependency. This means when you are importing this package to another project, Crayta will make sure to download and install any other package required as a dependency.

Hitting Create will finish the creation process and make this package available in Crayta’s Library.

Importing a Package

The Crayta Community tab will list your newly created package allowing you to download and install it to any other project.

By doing so the contents of the package will be made available in the project’s Library. A special icon indicates that this asset is part of an installed package.

You can now use this asset much like any other asset included in your project.

That way packages become a powerful tool to share components between games.

You can use this feature to split the development of your game in components that can be easily developed and tested separately and when ready incorporated back to the original game.

You can also have templates used in one game migrated and reused in another game. 

Crayta users can make their packages public and share them with other users building games.

Updating a Package

At any point you can edit any of your packages to push changes to existing assets or add new ones.

To edit a package you start by locating it in the Packages library window and clicking edit.

This will bring up a window similar to the package creation window, in which you may use a similar method to edit the package.

When finished, clicking Update will update the published the package with any changes you have made. Any projects using this package will show an active Download button which users may then click on to download the newest version of this package.

By clicking this button the latest version of the package will get downloaded and the included assets will be updated.

Categories
Advanced Tutorials

Voxel meshes and templates, when to use each

Preface

Crayta provides several tools to help you build large and interesting worlds for your games.

One of the most powerful features provided is the ability to use voxels in various sizes and materials for terrain, structures, objects, etc. At the same time Crayta allows you to organize parts of your scene as reusable components called templates. Templates can be easily duplicated multiple times, and any changes made to the original will automatically propagate to all instances.

In this tutorial we will explore how to use voxel meshes and templates and when to use each.

In Unity doing world building involves using polygon assets called meshes/models that get placed in the scene to assemble a world. Quite often a terrain surface is used in addition, painted using a number of brushes to help you create realistic worlds. 

There isn’t any notion of voxel building in Unity so usually a large number of assets have to be prepared in advance for all parts of the scene to be put together in the Unity editor. The only thing that might resemble Crayta voxel building is the ProBuilder Unity extension that can do part of the scene polygon modelling inside the Unity editor.

Crayta templates on the other hand are very much like Unity prefabs.

Voxel Mesh

Voxel building in Crayta involves adding and editing voxel meshes. Voxel meshes are groups of voxels that contain from a handful (e.g. a simple rock) to millions of voxels (e.g. a complex structure).

When creating a new project a voxel mesh is automatically added, containing a simple terrain surface to start building on.

The voxel mesh is saved to a voxel mesh asset, that can be found in the Library under the Voxel Meshes category. You can create multiple voxel meshes from a single voxel mesh asset simply by dragging and dropping it in your world:

Editing any of these terrain surfaces using the voxel tools will result in changes rendering in all terrain surfaces. This initially can be confusing, the reason is simply because all the terrain surfaces reference the same voxel mesh asset. So any change to that voxel mesh asset is being saved to that asset and in turn updates all voxel meshes referencing that asset.

You can change that behaviour for any voxel mesh by clicking the Create a unique copy of this asset button in the voxel mesh entity properties. That will create a new voxel mesh asset from this voxel mesh and assign it to it. From there and on any change on this voxel mesh will update the new voxel mesh asset created.

From the Library -> Voxel Meshes window you can also create new voxel mesh assets to be used to spawn voxel meshes in the world.

Template

Templates in Crayta is a powerful system that allows you to create reusable groups of entities that can be easily spawned anywhere in your world. At the same time retaining a link to the original template allowing you to make changes at any point to your template and have the changes propagate automatically to all the template instances.

A template can contain any kind of entities and components. You can use it for several things:

  • Collectables
  • Repeating structures
  • User template
  • Player templates
  • Effects etc.

You can easily create a new template by using an entity that already exists in your world. Select it and then click Template -> Create New Template…

This will create a new Template with this entity at the root of the template. From there you can easily add children entities, components etc.

You may spawn instances of a template by using the Library → Templates window and drag/drop your selected templates into the world. Spawned entities will show the full hierarchy of the template in the World Tree editor window and will contain a toolbar with the following template actions:

  • Edit template, will open this template for editing.
  • Copy to template, any changes made to this instance with this button will be saved back to the template and propagate to all template instances.
  • Break template link, this will severe the connection to the template and make this entity a normal World entity. You can do further changes to the entity without having the template override anything.

Voxel Meshes and Templates

Voxel meshes should be used when doing world building and you require parts of your world to be reused. Parts that don’t require any special functionality like scripting or lighting etc.

When those parts require additional components to be added and spawned on each instance a template should be used. Voxel meshes can be added to template entities much like any other component (script, sound, effect etc).

The only thing requiring special attention regarding voxel meshes in templates is the use of the Break template link button. This will break the link to the template, allowing the entity to be edited further without affecting the template. This holds true for all changes except editing voxel meshes attached to this entity or to its children.

This happens because, as we stated above, voxel meshes always reference voxel mesh assets. Breaking the link to a template will keep a reference to the same voxel mesh asset used by the template. So editing the voxel mesh on an entity that has broken the template link will have the, potentially undesirable, effect of editing the voxel mesh asset referenced by the voxel meshes in the template instances.

To stop this behavior, the solution is simple using the Create a unique copy of this asset button on those voxel meshes to create new voxel mesh assets that aren’t referenced by this template.

Categories
Advanced Tutorials

The relationship between Player and User

Preface

One of the most common concepts found in video games is that of a user controlled avatar. The player takes control of an avatar, which can be anything depending on the theme of the game (a human, a vehicle, an exotic spaceship) to navigate and interact with the game world.

In this tutorial we will explore how this concept is applied in Crayta and how to set it up in your game.

When getting on board Crayta one of the first things you are asked to do is to setup your Avatar using a range of controls to customize the body, hair, clothing etc.

Crayta games revolve around players that control human avatars. Crayta provides a camera system, player collisions, interaction etc, making it very easy to create first or third person games.

In Unity there isn’t a similar thing out of the box, the closest thing you could do is use the default first or third person controller script to start building a similar functionality.

User

A User is a type of entity which references the account of a person connected to the game. When you connect to a Crayta game a User entity will be spawned from the available User template and assigned to you.

All Crayta games are required to have a User template available. Only a single one is needed, since there isn’t any way to choose or set the active User template. Crayta will automatically detect and use it.

A default User template is provided in all new projects created which can be used to attach scripts and widgets.

Player

The User entity doesn’t have any physical representation in the game World, so you can’t see or interact with other connected Users.

When a new user is connected to the game and a User entity is spawned. Crayta will also attempt to spawn a Character entity from a Player template, unless a OnUserLogin method  has been added to a script. If that is the case then Crayta will not spawn a Player template and it is up to your game logic to handle this.

All player templates are required to have a root Character entity (other entity types may become available in the future).

Spawning a character entity will cause the game to render the user’s avatar and add a default camera.

A default Player template is added in all new projects created.

In contrast with the User template, a Player template exposes a list of properties allowing you to customize a number of settings like the jump height or running speed of the Player.

User/Player relationship

Each user connected to the game has at any instant a User entity spawned and referenced to his account and also a Player (Character) entity spawned and active in the game World.

Both entities are spawned from the User and Player templates available in the game. A game can have a single User template but can have multiple Player templates.

The User entity provides you with a method to change the active Player template that is used by each player:

This is quite powerful since it allows you to easily change the behavior of your game depending on the game state. For example you can have one Player template for the lobby area with an Orbit camera and players not being able to jump, a first person Player template for the main game action time and another one for players spectating and not actively playing.

Here is an example of how to change the Player template from a script attached to a User entity:

local MyUserScript = {}

MyUserScript.Properties = {
    { name = "playerTemplate", type = "template"}
}

function MyUserScript:ChangePlayerTemplate()

    self:GetEntity():SpawnPlayer(self.properties.playerTemplate)

end

And here is an example of getting a reference to the active Player (Character) from a script attached to a User entity:

function MyUserScript:GetActivePlayer()

   return self:GetEntity():GetPlayer()

end

And a more advanced case: getting a reference to the Player entity of all connected users from any script running on the server:

GetWorld():ForEachUser(
   function(userEntity)
       local username = userEntity:GetUsername()
       local player = userEntity:GetPlayer()
       -- do something with the player 
       -- e.g. access script methods or player properties
   end
)

You can easily add more Player (Character) templates to your game using the Primitives window.

Categories
Advanced Tutorials

Introduction to Widgets, building a game UI

Preface

Almost every game requires, in one form or the other, some sort of user interface (UI). For that reason most of the game engines or game creation systems go to great lengths in providing a robust UI solution to the developer.

In Crayta you can use a well established tech stack to build the UI for your game: HTML/CSS and Javascript. If you are coming from a web dev background you will feel right at home. If not, in this tutorial we will attempt to provide simple steps to get you started.

If you are coming from Unity to Crayta and you have been using the Unity User Interface (Unity UI), initially you might find it difficult to adapt to the Crayta UI.

At the moment, there isn’t a visual editor to allow you to place elements visually, adjust the position, the properties etc. All Crayta UI elements are HTML elements that are styled using CSS and logic is added using Javascript.

You can find a plethora of resources online to help you get started with HTML, CSS and Javascript.

If you’ve used the new UIElements in Unity you will feel much more comfortable.

Widgets

All interface related code is being stored in a special asset named Widget which can be attached to entities. You add widgets to your entities in the same way that you add scripts.

There are three types of widgets that can render an interface:

  1. Screen: the UI will be rendered as a HUD on screen.
  2. World: the UI will be rendered as a texture in the 3D world on a 3D plane.
  3. World – Camera Facing: the UI will be rendered as a texture in the 3D world, as a camera facing 3D plane.

The type of a widget can be set using the type property.

The Widget references an asset in its html property that contains the HTML code used for rendering it, plus inline CSS and inline Javascript.

What is HTML?

HTML is a markup language used to render web pages that has been used since the very first websites went public on the internet. Over time it has evolved to be able to handle 2D and 3D styles and also include complex logic using Javascript.

<!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>

<p>My first paragraph.</p>

</body>
</html>

What is CSS?

The tags (e.g. <p></p>) used to define the HTML markup structure have a basic styling of their own. To extend or change that styling with your own, a special language was created called CSS (Cascading Style Sheets).

This language provides you with a number of rules that extend the styling of any HTML element. Here is an example of inline (meaning in the same file) CSS:

<html>
  <head>
    <title>Playing with Inline Styles</title>
      <style>
         p{
            margin-left: 20px;
         }
      </style>
  </head>
  <body>
    <p style="color:blue;font-size:46px;">
      I'm a big, blue, <strong>strong</strong> paragraph
    </p>
  </body>
</html>

CSS can be added directly to any element using the style attribute or using a special HTML element <style>.

What is Javascript?

Javascript, a turing complete language, is used for adding logic and interactivity to web pages and even to write complex web applications.

Crayta includes the V8 Javascript engine which is the same Javascript compiler used by Chrome and other applications. This means that you are able to run and execute any kind of Javascript code in Crayta.

Crayta exposes a clean API that can be used to communicate data back and forth to your Lua scripts in your Javascript code.

    <body>
       <p style="color:blue;font-size:46px;">
           I'm a big, blue, <strong>strong</strong> paragraph
       </p>

        <script>
            engine.createJSModel('data',
            {
                bgColor: "rgb(20, 200, 100)",
                
                title: "Sign Title",
                titleSize: 20,
                titleColor: "rgb(255, 255, 255)",
                
                message: "Sign Message",
                messageSize: 10,
                messageColor: "rgb(20, 20, 20)",
            });
        </script>
    </body>

To write Javascript in HTML you can use the special HTML element <script>, in the <head> and/or <body> elements.

It’s common practice to write your Javascript code at the end of your <body> element, since this allows you to access any other HTML elements in the script.

Building a simple UI

A lot of the Crayta provided packages come with one or more widgets to draw a UI. You can use them to start learning how to build a UI of your own or as a starting point for your game UI.

But to better understand how widgets work in Crayta, we will explore how we can get started building a UI from scratch.

A simple game

Let’s take a moment and create a simple game and use it as the base for our interface.

  • You can create a new blank project or work on your existing one. 
  • Place a number of random Mesh entities around the world.
  • Create a new script called collectableScript and attach it to all the entities you’ve placed.
  • Add the following code to the script:
local CollectableScript = {}

function CollectableScript:OnCollision()
    self:GetEntity():Destroy()
end

return CollectableScript

If you go and play the game now, as soon as the Player collides with an object it will be removed from the world, like a collectible.

Now that’s not so much fun since the Player can’t see which items they’ve picked up. Our interface to the rescue!

Adding a widget

  • Select the UI Widgets category from your Library, and create a new asset named myInterfaceWidget

You can have all of your interface in a single widget, or split it up into smaller widgets that can act as parts or components of your interface. The latter approach is particularly beneficial If you have a complex interface or plan on reusing parts of it in other games in the long term.

For our tutorial project we will be using a single widget. Crayta will automatically fill the widget html file with boilerplate code that includes placeholders for CSS, HTML and basic Javascript methods required to communicate with your Lua scripts.

  • Attach this widget to your Player template.

Running the game will result in this:

Let’s prepare the widget to host our interface code.

  • Remove all existing CSS code from your <style> element.
        <style type="text/css">
            /* YOUR CSS CODE HERE */
        </style>
  • Remove all html code from your <body> element, and Javascript from the script element.
    <body>
        <!-- YOUR HTML CODE HERE -->

        <script>
            /* YOUR JAVASCRIPT CODE HERE */
        </script>
    </body>

Let’s add a simple list that will serve as a container for all the collectables the Player gathers.

  • Add the following HTML code where you’ve removed the boilerplate one (at the top of the body section and under the <!– YOUR HTML CODE HERE –> comment).
        <div class="my-items-container">
            <h1>My items</h1>
            <div class="my-items-list">
                <div class="my-item">
                    <span>1</span>
                    <p>Item Collected</p>
                </div>
            </div>
        </div>
  • Add some CSS to make it look pretty inside the style element, under the /* YOUR CSS CODE HERE */ comment:
    .my-items-container {
        width: 30vh;
        margin: 10px;
        padding: 10px;
        border-radius: 7px;
        background-color: rgba(255,255,255,0.5);
    }
    
    .my-items-container h1{
        text-align: center;
        text-shadow: 0px 0px 5px white;
    }

    .my-items-list {
        color: #ccc;
        list-style-type: none;
    }
    
    .my-item{
        position: relative;
        font: bold italic 70px/1.5 sans-serif;
        margin-bottom: 20px;
    }
    
    .my-item p {
        font: 20px/1.5 sans-serif;
        padding-left: 60px;
        color: #555;
    }
    
    .my-item span {
        position: absolute;
        color: darkcyan;
    }

Running the game at this point:

Although it is a nice list, right now it is just a graphic. Let’s add some logic to the interface.

Adding logic

Our goal for this list is to list any collectable the player picks up. For that we need a Lua script on the Player template that keeps tracks of the items collected.

  • Add a script to the Player template called myCollectablesScript.
  • And add the following code to it:
local MyCollectablesScript = {}

function MyCollectablesScript:Collected(collectableName)
    
    self:GetEntity().myInterfaceWidget.js:CallFunction("collected", collectableName)
end

return MyCollectablesScript

This code is the starting point of establishing communication between our Lua scripts and the Widget.

MyCollectablesScript:Collected is a custom method that we will be calling each time the Player has picked up a collectable. Let’s implement that now.

  • Add the following code to the collectableScript.
local CollectableScript = {}

function CollectableScript:Init()
    self.collected = false
end

function CollectableScript:OnCollision(collidingPlayerOrEntity)
    
    if self.collected == true then return end
    self.collected = true
    
    -- destroy the collectable
    self:GetEntity():Destroy()
    
    -- inform the Player/UI of the item collected
    if collidingPlayerOrEntity:IsA(Character) then
        collidingPlayerOrEntity:SendToLocal('Collected', self:GetEntity():GetName())    
    end
    
end

return CollectableScript

Using SendToLocal on the Player entity is an easy way to communicate from the server to the local Player the item that this Player has collected.

  • Add the following Javascript code to the widget, replacing the current code.
      <script>
        /* YOUR JAVASCRIPT CODE HERE */

        engine.createJSModel('itemsModel',
        {
            items: []
        });

        engine.on("collected", function(collectableName) {
            itemsModel.items.push({
                index: itemsModel.items.length + 1,
                name: collectableName
            });
            engine.updateWholeModel(itemsModel);
            engine.synchronizeModels();
        });

    </script>

This Javascript code does two things:

  1. Creates a new model that contains an array property used to hold all of the collected items.
  2. Registers an event handler, that is a function that can be called from Lua code and can also accept arguments.

As soon as the Player picks up a collectable the widget will get notified, and the items array will get populated.

Dynamically updating the UI

Let’s explore how the interface can automatically get updated as the items are collected.

Crayta uses the Coherent Gameface UI library which allows you to add data bindings to any HTML element. This is powerful for the elements which can get updated/added/removed automatically reflecting your data.

  • Update the HTML code in the widget adding data attributes.
        <div class="my-items-container">
            <h1>My items</h1>
            <div class="my-items-list">
                <div data-bind-for="item:{{itemsModel.items}}"              class="my-item">
                    <span data-bind-value="{{item}}.index"></span>
                    <p data-bind-value="{{item}}.name"></p>
                </div>
            </div>
        </div>

There are a number of data-bind attributes available to use in your HTML code. Some common ones are:

  • data-bind-for: can be attached to an array and automatically clone this HTML element, together with its children, for each array item.
  • data-bind-if: can do conditionals to check and show/hide an HTML element based on that condition.
  • data-bind-value: automatically update the innerHTML value of any element with the value attached.

Now this HTML code will automatically reflect any changes to the itemsModel object.

Every item collected by the Player will now appear on the Interface.

Code

Here is the complete widget script:

<html>
    <head>
        <!-- Required includes -->
        <script type="text/javascript" src="coui://uiresources/js/crayta.js"></script>

        <style type="text/css">
            /* YOUR CSS CODE HERE */
            .my-items-container {
              width: 30vh;
              margin: 10px;
              padding: 10px;
              border-radius: 7px;
              background-color: rgba(255,255,255,0.5);
            }
            
            .my-items-container h1{
                text-align: center;
                text-shadow: 0px 0px 5px white;
            }

            .my-items-list {
              color: #ccc;
              list-style-type: none;
            }
            
            .my-item{
              position: relative;
              font: bold italic 70px/1.5 sans-serif;
              margin-bottom: 20px;
            }
            
            .my-item p {
              font: 20px/1.5 sans-serif;
              padding-left: 60px;
              color: #555;
            }
            
            .my-item span {
              position: absolute;
              color: darkcyan;
            }
        </style>
    </head>

    <body>
        <!-- YOUR HTML CODE HERE -->
        <div class="my-items-container">
            <h1>My items</h1>
            <div class="my-items-list">
                <div data-bind-for="item:{{itemsModel.items}}" class="my-item">
                    <span data-bind-value="{{item}}.index"></span>
                    <p data-bind-value="{{item}}.name"></p>
                </div>
            </div>
        </div>

        <script>
            /* YOUR JAVASCRIPT CODE HERE */

            engine.createJSModel('itemsModel',
            {
                items: []
            });

            engine.on("collected", function(collectableName) {
                itemsModel.items.push({
                    index: itemsModel.items.length + 1,
                    name: collectableName
                });
                engine.updateWholeModel(itemsModel);
                engine.synchronizeModels();
            });

        </script>
    </body>
</html>

Useful links

Coherent Gameface reference docs:

Categories
Advanced Tutorials

Client/Server relationship and how they communicate

Preface

Crayta takes a unique approach to how you develop multiplayer games, something that usually requires solving difficult programming problems and acquiring special infrastructure for your server. Instead of having the server and the client running separately, trying to synchronize the game instances, in Crayta everything runs in the cloud, both the server and the client, and even the development environment itself.

By default all game logic that you write in Crayta runs in the server and the engine takes care of replicating all movement, entity updates, collisions and physics etc. to the connected clients. When explicit client logic is required, for example to update something on the HUD like a health bar, any script can easily include that and communicate updates to the code running on the server.

In this tutorial we will explore how to handle the Server/Client relationship in your code and how to communicate between the two.

You will not easily find something similar to Crayta in other game engines, where everything requires a rigorous architecture to handle the synchronization between the server and the client.

Crayta will take care of most of the hardest parts of making a multiplayer game, leaving you to concentrate on creating the gaming experience.

Where is my code executing?

Crayta makes it very easy to start building a world, adding objects and effects and from the very first moment have your friends connect and play together. When adding code to your game, to enable custom behavior and logic, at first it might be confusing where the code is executing and in what order.

Crayta doesn’t require you to write your server and client logic separately. In the same script you can have server code and client code, that can share and reuse methods.

So how do we decide where our code should execute? In Crayta there are a number of functions that are called entry points and can execute code in one of the following contexts:

  • Server
  • All clients
  • Local client only

Let’s take the Init method and its variations to see how it is used there to get a better understanding of how networking works in Crayta.

After the game world and all of its entities have been created on the server the Init method will be called in any script using it. It will execute the code in the server and all state updates happening to entities, as a result of the code running, will be replicated to all connected clients. This method is useful when executing gameplay sensitive code that aims to have the same result executed to all connected clients.

After all entities, that have been created, arrived to each connected client, separately, the ClientInit method will be called. This will execute the code to each connected client but not in a synchronous manner since the entities will arrive to each client at a different time. This is useful when code has to run to all connected clients, but it doesn’t have a crucial effect on the gameplay when not in sync. For example powerups spinning or particles effects animating. This method is valid and can be used in any entity, except the User and Player entities and their children.

On the Player or User, the LocalInit method can be used. It is called immediately after the ClientInit and it executes only to the local client. It can be used to update things like the HUD that are visible only to the local client.

How to handle communication?

Code executing to the server or the client only isn’t always useful, quite often you will have to send a message from the server to the clients or the other way around. Crayta provides an easy to use API to help you with that.

SendToAllClients

This method can be used in the server, in any script, to communicate with all connected clients:

entity:SendToAllClients(string eventName, ... args)

When called you have to pass the eventName, that is a string of the name of the function that will be executed. It will find and call this function on all scripts of this Entity on all connected clients.

Optionally you can pass any number of arguments that will be made available to the function when executed.

SendToLocal

This method can be used in the server to communicate with the local client only:

entity:SendToLocal(string eventName, ... args)

As with the previous method, the name of the function to be executed is passed as the first argument. This function will be called on all scripts of this Entity on the local client that owns the Player or User this script is attached to.

A number of arguments can be passed to be made available to the function when executed.
This method is useful to communicate changes required only by the local client, like HUD updates.

SendToServer

This method can be used in the client to communicate with the server:

entity:SendToServer(string eventName, ... args)

It will call eventName on all scripts attached to this Entity on the server, together with any arguments given.

IsLocal / IsClient

There are two helper methods that can be used in any script to check if the code executing is running on the server or in the client.

bool entity:IsClient()

This method will check if this Entity is on the client.

bool entity:IsLocal()

This method will check if this Entity is owned by the local client, that means it is the User or Player entity, or it is child to these entities.

Script Properties

Another way to easily communicate changes between the server and client is the property bag. All properties defined in a script automatically replicate to the clients and can therefore be used to transmit state and data without the need to use the Send methods.

The advantage of doing that is that new players will automatically get the latest state of a script, rather than having to catch up on their OnUserLogin and send it manually.

Categories
Advanced Tutorials

Coroutines in Crayta. Using Schedules and OnTick

Preface

In game development it is a common requirement to be able to schedule the execution of your code. Normally when you call a function the code will run from start to end and return in a single frame. There are cases where something has to happen over a sequence of frames or after a scheduled delay. Example cases:

  • Pause the code execution for a specified amount of time.
  • Change to the next level 3 seconds after the user has reached the finish line.
  • Calculate something complex over a number of frames that would normally make the game hang.

In this tutorial we will learn how to use Schedules and the special OnTick method to execute our code at an appropriate time.

In Unity a similar concept exists called Coroutines, allowing you to schedule the execution of your code.

The Coroutine class in Unity is similar to the Crayta Schedule method. They can both pause and resume their operation, where they left off, on the following or on a coming frame. A Unity Coroutine runs in the same thread, in a similar manner in Crayta the Schedule method will execute in sequence with the main thread.

How to use Schedules?

Here is a simple example on how to use a Schedule in Crayta:

function MyScript:OnInteract()
   self:Schedule( function()
        Wait(1.0)
        Print("Interact was pressed a second ago...")

        Wait(1.0)
        Print("Interact was pressed two seconds ago...")

        Wait(1.0)            
        Print("Interact was pressed three seconds ago...")
   end)
end

This method will print a couple of messages in the console but instead of printing them at the same time, it will print them with a second difference one from the other.

Schedule is a method available in the ScriptComponent class, meaning that it is available to be used in any Crayta script.

Wait

In the example above we used the Wait() method which is available to be used inside the callback function that is executed by the Schedule. As the name implies this method forces the code execution to pause and resume at a later point.

There are two ways to use this method:

  1. Wait( duration ) where duration is the time in seconds to pause in minimum before resuming the execution.
  2. Wait() pause the execution for a single frame and then resume in the next frame.

Both methods return the exact time in seconds taken from the pause instant till the method resumed. That is useful to implement non-linear smooth animations.

The code below will animate an entity moving periodically up and down when placed in an :OnTick(dt) function:

local angle = 0
while true do
    local dt = Wait()
    angle = angle + dt
    local position = Vector.New(0, 0, 100 * math.sin(angle))
    self:GetEntity():SetPosition(position)
end

IsInSchedule

There is also another method available to be used inside Schedule, IsInSchedule(). As the name suggests this will return true or false, depending if the code executing is running in a schedule or not. This is quite useful if you are calling methods in a schedule that you also call, with a different behavior, in other parts of your script.

Cancel

If required you can easily cancel any schedule by calling the Cancel() method in the parent script. When you call the Schedule() method a handler is returned which can be stored in a variable or script property for later use.

That handle can be used at any point by passing it in the Cancel( handler ) method to stop immediately the execution of that schedule.

function MyScript:Init()
   self.mySchedule = self:Schedule( function()
       -- animating the current entity
   end)
end

function MyScript:OnInteract()
   -- this will stop the animation as soon as the player interacts with it
   self:Cancel(self.mySchedule)
end

How to use OnTick?

OnTick is a method available in all scripts, that is updated automatically by Crayta on each frame. The deltaTime is in seconds, which is the time elapsed since the last frame was rendered, and is made available as an argument to be used by the executing code (e.g. to make smooth animations).

Here is an example using OnTick to infinitely rotate an entity around its Z axis, which is useful for items such as powerups:

function PowerupProp:ClientInit(dt)
    Self.rotation = Rotation.New(0,0,0)
end

function PowerupProp:ClientOnTick(dt)

    self.rotation.yaw = self.rotation.yaw + dt * self.properties.rotationSpeed

    self:GetEntity():SetRotation(self.rotation)
end

You will notice that instead of using OnTick(), we used a variation of it called ClientOnTick(). Much like the Init function the OnTick comes with the same three variants depending on the context it is used in:

  • OnTick (executed in the server)
  • ClientOnTick (executed in all clients)
  • LocalOnTick (executed in the local client)

Usually for objects that require cosmetic animations, when the movement of the entity doesn’t affect the gameplay, it is better to do it on each client separately, using ClientOnTick().

For animating something specific to a certain player, that is not required to be in sync or even visible to other players, LocalOnTick() can be used, but only on scripts attached to a User or Player template. That makes it useful for doing stuff in a Player’s HUD, but can’t be used to rotate a collectable.

When something should be synchronized across all connected players, OnTick() should be used.

Schedules vs OnTick

Both methods do something similar, execute code across several frames. So when to use each?

Schedules are more versatile. You have control over how often the code will execute, you can get the deltaTime elapsed and even stop the execution at any point. Also Schedules can be started at any point in your script code, so all contexts are supported in Schedules, too (Server, Client, Local).

Schedules ultimately can be configured to have the same behaviour as the OnTick method (using a while loop and a Wait).

OnTick is automatically called by Crayta, providing the deltaTime elapsed and it should be used when something should be executed per frame. For code that pauses or works occasionally Schedules can be a better alternative from a performance point of view.

Schedules are better used when you require exact control of when your code is executed. Also when you have to do something complex that would normally cause the the game to hang for a while. Putting that code in a Schedule and spreading calculations across a number of frame, can keep the frame rate constant.

Categories
Advanced Tutorials Uncategorized

Adding editor properties to Crayta scripts

Preface

All Crayta scripts can have properties that can be edited directly in the editor. This is a powerful concept both for easily testing values, and reusing the script on different entities to produce new kinds of behavior each time.

In this tutorial we will learn how to add properties in scripts as well as learn all the different types of properties available.

If you are coming from Unity to Crayta you will recognize this concept and find that it works in a similar manner.

In Unity to expose properties to the editor you add public properties to your script class (or use the SerializeField attribute). In Crayta you add a child table to the Script.Properties table of your script.

In Unity the exposed properties appear in the Inspector window in the editor, in Crayta in the Properties window.

In Unity you can use editable properties to edit values or reference objects, in Crayta you can do the same.

Adding properties in a script

When creating a new script, Crayta will add by default an empty table named Properties (often called the property bag) to your script.

If your script is named HealthScript then the property bag will be initially an empty Lua table:

Health.Properties = {}

To add a new property you append a child table (a Lua associative array) to the properties table with a number of options (key/values) in it:

Health.Properties = {
   { name = "maxHealth", type = "number"}
}

Below you can see the property bag of the main script used in the Health package.

And here is how these properties are exposed in the editor:

Property options

There are different types of properties (text, number, entity, template etc) which we will explain in detail in the next section. Though there are a number of property options that are available to all property types:

  • name – string
    • The property name that will appear in the editor.
  • type – string.
    • All available types are explained in the next section.
  • tooltip – string
    • A tooltip message that appears when hovering over the element in editor.
  • editable – bool (default:true)
    • False hides the property in the editor UI. Useful if you want a property just for replicating data to clients, and want to keep the UI tidy by preventing the user from being able to see/edit it.
  • editableInBasicMode – bool (default:true)
    • False hides the property, but only in basic mode.
  • container – string (default:nil)
    • Possible values: “” (empty string or nil), “array”
    • Setting this to “array” will present the property as an array of the given type, allowing the user to add and remove values. Each array element, and the “length” field is inherited from a template individually.
  • visibleIf – function (default: nil)
               function(properties) return 
                  properties.allowRespawns == true
               end

This function should return true when the property should be shown on the UI, false to hide it. Useful for keeping the UI tidy by hiding irrelevant options. The function’s properties parameter provides access to the other properties (just as you would access with self.properties in a script)

Property types

  • number
    • Optional extra attributes:
      • default – number (0)
      • min/max – number – sets the min/max value that can be entered into the editor UI. Doesn’t apply at runtime.
      • stepSpeed – number (default: 1) – adjusts how fast the number changes with a gamepad
      • slowStepSpeed – number (default: 0.1) – as above, but when pressing the ‘slow’ modifier
      • fastStepSpeed – number (default: 10) – as above, but when pressing the ‘fast’ modifier
      • allowFloatingPoint – bool (default: true) – allow/disallow decimal numbers in the editor UI. Doesn’t apply at runtime.
      • options – table, a list of allowed values, either named: {Slow = 0.25, Normal = 1, Fast = 2, Ludicrous = 100}, or unnamed: {0.25, 1, 2, 100}
      • editor – string (default: chosen automatically based on other settings; either enum, int or float). Changes the widget displayed in the editor.
      • Possible values: int, float, seconds, days, slider
  • string
    • Optional extra attributes:
      • default – string (“”)
      • options – table – a list of allowed values, either named: {Monday = “Mon”, Tuesday = “Tue”, Wednesday = “Wed”}, or unnamed: {“Monday”, “Tuesday”, “Wednesday”}
  • text
    • Similar to string, but localisable. Uses a Text type at runtime.
  • boolean
    • Optional extra attributes:
      • default – boolean (false)

  • vector
    • Optional extra attributes:
      • default – Vector.Zero or any other Vector value using Vector.New(1,2,3)
      • stepSpeed – number (default: 1) – adjusts how fast the number changes with a gamepa
      • slowStepSpeed – number (default: 0.1) – as above, but when pressing the ‘slow’ modifie
      • fastStepSpeed – number (default: 10) – as above, but when pressing the ‘fast’ modifier
  • vector2d
    • Optional extra attributes:
      • default – Vector2D.Zero or any other Vector2D value using Vector2D.New(1,2)
      • stepSpeed – number (default: 1) – adjusts how fast the number changes with a gamepa
      • slowStepSpeed – number (default: 0.1) – as above, but when pressing the ‘slow’ modifie
      • fastStepSpeed – number (default: 10) – as above, but when pressing the ‘fast’ modifier
  • rotation
    • Optional extra attributes:
      • default – Rotation.Zero or any other Rotation value using Rotation.New(0,90,0)
      • stepSpeed – number (default: 1) – adjusts how fast the number changes with a gamepa
      • slowStepSpeed – number (default: 0.1) – as above, but when pressing the ‘slow’ modifie
      • fastStepSpeed – number (default: 10) – as above, but when pressing the ‘fast’ modifier
  • entity
    • Optional extra attributes:
      • is – string (default: “”) – require the entity to be of a specific physical type: Character, User, Mesh, Light, Sound, Effect, Voxels, Trigger, Locator, Camera. This doesn’t apply at runtime.
  • color
    • Optional extra attributes:
      • default – Color.New(1, 1, 1, 1)
  • event
    • An event property allows you to broadcast a message to a number of listeners all at once.
    • Each event can have one or more listeners. 
    • Using the Send function available on the event will trigger all listeners, passing on any parameters given to it:
       -- using the send method on an event property
       self.properties.myEvent:Send(myParam)

There are two ways to setup a listener in the editor:

  1. Specifying an entity in the world (or in the same template) like a normal entity property.
  2. Specifying an entity within any template.

Then as a final step you choose a script on that entity and a function within that script. In the first case, which is the simplest, only the specified entity will receive the event (when using the send method).

In the second case, the event will be broadcasted to every instance of that entity in the world, including any entity that is spawned after the game starts. This is quite powerful when you have to inform all entities of the same type for a certain event e.g. all chests have to refill or all doors have to close.

For more advanced uses, there is a Listen method available on the event property which allows you to add a listener to an event procedurally, in code:

gameManager.properties.OnMatchStart:Listen(self, "SwitchOn")

This method accepts two arguments:

  • A script component, usually that would be set to self, the script where this method is used from.
  • A function name, which will be the method in that script which will be called when the event is broadcasted.
  • [asset type]
    • The following asset types can be easily referenced by any script using a property with one of the following types:
      • template, voxel, mesh, sound, world, effect, script, widget, skydome, innerhorizon, outerhorizon, grip, colorgrading, postprocess, skymesh, skylayer, voxelmesh
MyScript.Properties = {
    { name = "greenVoxel", type = "voxel"}
}

Here is how a script exposing multiple properties to the editor, looks like:

Categories
Advanced Tutorials

Using the Inventory Package

Preface

In this tutorial we will learn how to use the Inventory package provided by Crayta to help us easily set up a system to manage a player’s inventory.

Crayta has an inbuilt Asset Library which provides easy access to packages provided by the Crayta team or Crayta users.

If you are coming from a Unity background this is similar to the Unity Asset Store. A Unity Package downloaded from their store will be a Crayta Package, that is a collection of templates, scripts, widgets etc that can be easily shared between Crayta projects to enhance the functionality of your game.

The Crayta team provides and maintains a collection of packages (Game, Shop, Inventory etc) that can help you easily put in place the basic functionality for a game or learn by studying how they work.

Installing the Inventory Package

Using the Community Tab of the Asset Library window in the editor we can easily install the inventory package to any game by selecting it from the packages list and hitting install.

Crayta will download and install the package to your game alongside any dependencies required. You can now see what the package has imported to your project by selecting and expanding it in the Packages section of the library window:

Structure of the Inventory Package

Let’s take a look at the contents of the package that are now available to be used in our game. A Crayta package can contain any kind of assets that can be authored in the editor (meshes, effects, scripts, templates etc.). In this case the Inventory package imported two templates, a widget and a number of scripts.

Templates

  • User Inventory, is a simple template that holds the main inventoryScript component and an example of setting a default item.
  • Player Inventory View, is a template containing the widget/UI logic for the inventory.

Those templates provide a basic layout on how to structure your inventory logic, you can use them as they are by dragging them straight onto the User and Player template accordingly or attaching the required scripts and widgets yourself to the User/Player entity.

Widget

  • inventoryViewWidget, this is a Crayta widget asset (a file containing HTML/CSS/Javascript) which is responsible for rendering the inventory interface on screen. This widget has to be attached to the Player template to work.

The default layout renders a configurable number of slots at the bottom of the screen, each slot can be empty or hold an item. Using HTML/CSS you can easily add your own styling to the interface.

Scripts

  • inventoryScript, this is the heart of the inventory system, this script is responsible for initialising and managing all inventory items. To use this script attach it to the User template.
  • inventoryDefaultScript, this is an optional script used to set the default items in inventory slots that are available when the game starts. You can attach this on any entity (the inventoryScript can be configured where to look for default items) or add it to the User template.
  • inventoryViewScript, this script is responsible for communicating all inventory logic to the inventory widget for rendering. It also handles equipping/unequipping items and passing through inputs / button presses to the currently equipped item. It should be attached together with the widget to the Player template.
  • inventoryItemSpecScript, is used to define an item which can be picked up by the Player and added to the inventory. It should be attached to a Template.
  • pickupSpawnerScript, this is an optional script used to handle spawning and respawning pickups of items that can be picked by the Player and added to the inventory. It can be either attached to a Template to use that template as pickup or to any entity and be configured to use a template as the pickup item.

Most of the methods in the inventory scripts have detailed comments explaining how they work and what they do, making it easy to understand and extend their behavior.

How to setup the inventory

Let’s examine now how we can start using those scripts to setup a basic inventory easily and quickly in our game.

Putting the scripts in place

After installing the package we should start by adding the base inventory scripts and widgets in place. We can do that easily by:

  1. Dragging and dropping the User Inventory template from the inventory package to our User template.
  2. Dragging and dropping the Player Inventory View template from the inventory package to our Player template.

Those two templates will add script folders with the inventory scripts to our User/Player templates.

Running the game by pressing the preview button (Key: F5) will render the inventory automatically.

Adding items to the inventory

There are 2 steps to making an item become collectable: first we have to turn the item into a template, and then we need to add the appropriate scripts to it.

Step 1: Select your item (in our example it’s a ChineseLantern) and then select Template->Create New Template. Give your new template a name (we’ve called ours ‘Lamp’).

Create a new template of your collectable item.

Step 2: At the top of the world tree you need to click the dropdown to change the view from World to your template:

Click the dropdown to select your template.

You now need to add the inventoryItemSpecScript to your template:

  • Select Entity->Add->Script->inventoryItemSpecScript

The inventoryItemSpecScript makes it easy to make any template in our game ready to be added to the inventory. We can give it a freindly name (such as ‘Lamp’), choose whether it becomes a stacked item, and what grip the player’s character should use when they carry it.

This script adds the required properties to any template so that it can now be added to the player’s inventory.

To actually appear in the Player’s inventory, we have two options – let’s explore both.

Option 1: Start with the item in the inventory

Adding the User Inventory template to our User template created a new script folder called Defaults

Add an inventoryDefaultScript component to this folder, and set the template to the one for your item (i.e. Lamp). You should now start with the item already in the player’s inventory. Here you can also configure the quantity of this item the User will start with by playing with the count property.

Running the game now you will see an item being automatically added to the inventory. The Player carries it on its assigned grip position. If no grip position is selected, the item will attach to the right hand by default.

Option 2: Add pickup items to the game

Having the Player carrying all the items from the start of the game might make it too easy and not so fun. Let’s learn how to easily add pickup spawnable items to the game world.

Add the pickupSpawnerScript to your custom item template. Now this script comes with a large number of properties to customize it to your liking:

  1. For the moment let’s decrease the min/max respawnTime property to 5 seconds, which affects how often the item will respawn after it is picked by the Player.
  2. Also be sure to enable the showOnInit property which will make the item available from the start of the game.

Return to the World Tree and add a few instances of this template to the World. Now if you go and preview the game you will find yourself able to start picking up those items with the Interact key (Keyboard E or gamepad face button left).

You can also enable the useOnCollision property on the pickupSpawnerScript to have the Player pick up any item he collides with automatically.

Configuring the inventory

You will notice that you can pick up several items and the number of items on that slot will increase. That is happening thanks to the canStack property on the inventoryItemSpecScript.

Go ahead and disable canStack and run the game again. You will see that as soon as you pick up this item, it will be added to the third item slot of the inventory. Trying to pick up more items will fail since your inventory is full.

To have more slots available go ahead on the User template and on the inventoryScript increase the maxSize property.

Using inventory items

We now have an inventory full of useful items, so how do we use them? The inventory package has by default input configured to use the following controls:

  1. Extra Action 2/Extra Action 3 to switch the active inventory slot to the previous/next slot. This is an easy way to cycle through your inventory items in game. (Keys F/Q on keyboard and Gamepad Right/Left Shoulder).
  2. Hotbar 1,2,3,4 etc to change the active inventory slot by its index (this works only on a keyboard right now, Keys 1,2,3,4 etc)

Advanced inventory features (Keyboard required)

Extending the default styling

To start extending the default styling provided by the inventory package, you have to edit the HTML/CSS code found in the inventoryViewWidget widget.

Let’s change the styling of the selected item. To do that locate the .item-selected class in the widget and replace it with the following code:

        .item-selected {
            background-color: #ffff1a;
            color: black;
            text-shadow: 0 1px rgba(0,0,0,.3);
            height: 100%;
            width: 100%;
            border-radius: 50px;
            box-shadow: 0px 0px 0px 2px #fff inset;
        }

This will update the color of the background and text and also increase the border radius making the item slot round.

Let’s make all item slots match this style by updating the .item and .item-empty classes accordingly to:

        .item {
            margin-right: 5px;
            display: block;
            background-color: #f3cda5;
            text-align: center;
            width: 100px;
            border-radius: 50px;
            height: 100px;
            color: #808080;
            z-index: 1;
        }

        .item-empty {
            background-color: #c1c1c1;
            height: 100%;
            width: 100%;
            border-radius: 50px;
        }

And also update the .item-hotkey class to center the slot index:

        .item-hotkey {
            width: 100px;
            display: block;
            padding-top: 2px;
        }
Note that you undo these changes with CTRL + Z

Saving/loading items

You can install a package available in the Crayta store, called Auto-save that can help you persist a Player’s inventory items between sessions or when changing worlds.

Installing the package will add two scripts in your project, the autoSaveScript and the inventorySaveScript.

To enable this functionality all you have to do is add those two scripts to your User template. There is only a single property that can be configured called interval. This sets the gameplay time after it elapses the inventory will automatically save in the background.

When the Player starts a new session or moves to a new world, his inventory will be automatically filled with the items he had in place.

Removing inventory items

Many inventory items in games can be used limited times or just once before they are consumed. The inventory package doesn’t provide an automatic way to handle that, though it provides a method that you can easily use from any script to add item removal logic.

Here is the method, found in the inventoryScript:

function Inventory:RemoveCurrent(removeCount)
    return self:RemoveFromItem(self.inventory[self.properties.currentIndex], removeCount)
end

Let’s create a new script called consumeItemScript and populate it with the following code:

local ConsumeItemScript = {}

function ConsumeItemScript:OnButtonReleased(btnName)
    if btnName == "primary" then
        self.inventoryScript = self:GetEntity():GetUser().inventoryScript
        self.inventoryScript:RemoveCurrent(1)
    end
end

return ConsumeItemScript

Now attach this script to your Player template and try pressing the primary action button (Left Mouse Button or Gamepad Right Trigger) while playing the game.

The active item will be used once and then it will be removed from the inventory.

Accessing the inventory items

All items the inventory holds, together with info regarding their quantity and the slot index they occupy, are stored in the self.inventory table in the inventoryScript.

Here is sample code on how to access that table from another User/Player script and print the item names in the console:

function ConsumeItemScript:Init()
    self.inventoryScript = self:GetEntity():GetUser().inventoryScript
    
    for index, inventoryItem in ipairs(self.inventoryScript.inventory) do
        
        if inventoryItem.template ~= nil then
            Printf('Item name {1}', inventoryItem.template:GetName())
        end
    end
end

Categories
Advanced Tutorials

Event Functions in Crayta Scripts

Preface

Scripts in Crayta use an event-driven approach by calling certain functions that can be declared in any script. These functions, which can be called entry points, are activated by Crayta as a response to events that occur in the game, quite often with associated arguments.

All these methods are specified in the Crayta Lua API. Some of them you will already have seen like the Init (called on the server to initialize a script) or the OnInteract (called on the server when a player interacts with an entity on all scripts of the entity.)

In this tutorial you will be introduced to the most common and important event functions.

If you are coming to Crayta from a Unity background, entry points will look familiar.

What might be new is the context of execution, where the script is running:  Server, Client or Local. So, many event functions will have variants depending on where they are executing e.g. Init() vs ClientInit() vs LocalInit().

In the beginning this can be slightly confusing but in the long run it will become a powerful way of programming multiplayer games. 

By sharing the same codebase between client/server and easily communicating between the server/client. In another tutorial the relationship between server and client and how the two communicate will be fully explained.

Initialization

It is a common use case to be able to call initialization code before updates occur in gameplay. The Init function provided by Crayta is used to do that and is called as soon as the script is initialised.

There are three variants of this method depending on where it is called:

  • Init, called on the server, on the first frame that the server is running.
  • ClientInit, called on each connected client when that client initializes.
  • LocalInit, this is similar to ClientInit except it only works for scripts attached to either the User or Player template and is only called on the local client.
function ScriptComponent:Init() ... end
function ScriptComponent:ClientInit() ... end
function ScriptComponent:LocalInit() ... end

Here are some examples to help you understand where to use each:

  1. In a dungeon crawler you would like to position random crates with hidden power ups across the dungeon. Running the code in the Init method will ensure that the server is responsible for spawning crates in random places and have those entities propagate automatically to all connected clients.
  2. You would like to start animating a cosmetic object (not important to gameplay) as soon as a player connects. ClientInit will execute as soon as that client is ready and the animation will start playing for the player to watch. If animating entities important to gameplay e.g. a door opening/closing that blocks a gameplay area, that should be done on the Server.
  3. You would like to render in the UI how much time the player has been playing. As soon as he is connected you can save the current time in the LocalInit and pass it to a Widget to be used in the interface logic to measure time.

Regular Updates

Much like most game engines Crayta provides a way to run code that is executed per frame. That is key to program changes to the position, rotation or behavior of objects before each frame is rendered. The OnTick function is the main place to put that kind of code. 

When OnTick runs it also provides the delta time in seconds which is the time elapsed since the last frame was rendered.

function ScriptComponent:OnTick(number deltaTimeSeconds) ... end
function ScriptComponent:ClientOnTick(number timeDeltaSeconds) ... end
function ScriptComponent:LocalOnTick(number timeDeltaSeconds) ... end

Much like the Init function, OnTick comes with the same three variants (regular/Server, Client and Local).

Here is an example on how to use OnTick to rotate an entity each frame:

local RotateScript = {}

RotateScript.Properties = {
    { name = "speed", type = "number", default = 200}
}

function RotateScript:Init()
    self.yaw = 0
end

function RotateScript:OnTick(dt)

    self.yaw = self.yaw + self.properties.speed * dt

    local rotation = Rotation.New(0, self.yaw, 0)
    self:GetEntity():SetRotation(rotation)
end

return RotateScript

Input

Crayta has in place a default player controller (configurable on the Player template) that handles all player and camera movement in the world (walking, running, jumping etc).

Further input handling is provided by a set of event methods that take care of abstracting the source of input (keyboard/mouse or controller).

Interact

The OnInteract method is executed on entities the player interacts with (player targeting the entity and pressing keyboard E or the left face button on the controller with the default mappings).

When OnInteract runs it provides the Player that interacted with that entity as an argument as well as the hitResult on the entity.

function ScriptComponent:OnInteract(Entity player, HitResult hitResult) ... end

There are also two other methods available, OnInteractPressed and OnInteractReleased, that run on scripts attached to either the User or the Player when the interact key is pressed or released accordingly. These two methods will be executed even when there isn’t any object available to interact with.

All OnInteract methods run on the server only.

Actions

Similar to the OnInteractPressed and OnInteractReleased methods, Crayta provides additional methods to listen to user input (keyboard/mouse or controller). 

All methods run on the server only, and most of them on scripts attached to the User or Player entity.

function ScriptComponent:OnButtonPressed(btnName) ... end
function ScriptComponent:OnButtonReleased(btnName) ... end

World

There are several event functions provided by Crayta to interface with the game world and respond to updates or changes that happen in it.

Here are some of the most commonly used events.

OnUserLogin/OnUserLogout

This is a method called on the server to inform you when a new user joins or leaves a World. When it runs the User that has joined or left the World is passed as argument.

function ScriptComponent:OnUserLogin(User user) ... end
function ScriptComponent:OnUserLogout(User user) ... end

OnTriggerEnter/OnTriggerExit

This is a method called on the server by a trigger component placed in the world when any entity enters or exits the trigger. When it runs the Entity that is responsible for triggering the event is passed as argument.

function ScriptComponent:OnTriggerEnter(Entity other) ... end
function ScriptComponent:OnTriggerExit(Entity other) ... end

OnCollision

This method is called on the server when a player collides with an entity. The method will be called on all scripts of that entity and also on the player’s scripts. 

In the first case the Player that collided with the entity will be passed as an argument, in the second case the Entity that the player has collided with will be passed.

function ScriptComponent:OnCollision(Entity collidingPlayerOrEntity) ... end

OnDamage

This method is called on the server on all scripts of the entity when damage is inflicted on that entity, provided the damageEnabled property is enabled.

The arguments passed when this method runs are the damage amount (number), the damage causer (Entity) and the hit result (HitResult).

function ScriptComponent:OnDamage(number damageAmount, Entity damageCauser, HitResult hitResult) ... end