HSL for Programmers

From HEWIKI
Jump to: navigation, search

Contents

He advanced.png

This page is an introduction to the HeroScript Language and HeroEngine development environment. It is aimed at experienced programmers and scripters who want to dive right into what makes HSL different from what they already know.

For easier "step by step" tutorials, see:

For this information in Video format:

We've recorded a number of videos from our training sessions. The Video Series "HSL For Programmers" comes in 3 parts, and assumes you have working knowledge of the fundamentals of programming languages.

Introduction

At a Glance:


HeroScript is a powerful high-level scripting language with a light-weight syntax. It shares the simplicity of expression of languages like Visual Basic, but with even lighter syntactical sugar. Yet it is also a fully object-oriented language, with powerful OO capabilities like multiple inheritance. Uniquely, a powerful capability of HSL's data model is that instantiations of classes (called Nodes) can dynamically change their type at run-time by appending new classes.

There are many other unique features of HSL, such as the separation of the data type system from the language itself (the Data Object Model), and a very unique system for managing the relationships between objects (called Associations).

HeroScript is fast to work with. Changes made to scripts are real-time and live. There is essentially zero compile-link-deploy-restart time involved in scripting. Compiling is instantaneous, and updates are reflected to all servers and clients immediately.

HeroScript is also fast. It is designed for high-capacity MMOs. But no language can guard against poor algorithm design, so profiling tools are also included to help you find performance bottlenecks and fix them.

HeroScript Everywhere

It is important to understand that HeroScript is a first-class development language. In HeroEngine, you implement your entire game in HeroScript (although you can use the plugin architecture or direct source code access where necessary for C++ efficiency). If, in the past, you are have worked with game engines where scripting did not exist, or was simply a way of implementing non-critical sections of the game (like UI or simple quests), then this will take some getting used to. In essence, you'll want to develop your entire game using HeroScript and then, only if necessary, resort to source code or plugins for anything that might require special optimization or capabilities not possible with HeroScript alone.

The Big Differences

First, lets discuss some big-picture issues about HSL that are significant to how you work with the language. Some of the things here may be somewhat unusual for a programming language, and others are just familiar variants.

Client and Server

The client and the server have independent script databases (and DOMs, see below). You explicitly create a script to exist on the client or the server.

Object Oriented and Procedural

HSL is both an object oriented langauge and a procedural language. HSL supports the notion of classes (w/ multiple inheritence) with methods as well as just plain old functions. Like C++ you can mix the two freely.

An instance of a class in HSL is called a Node (in other langauges, this is sometimes called an Object). One extremely powerful capability of HeroEngine is that in addition to the class the Node is instantiated from, a Node can mix-in other classes at run-time (this is called glomming a class). In this way, new capabilities can be added to an existing Node.


OO & Procedural? Why Both?

Often we are asked why we have both an OO model and a procedural model. It is worth noting that object-oriented methodology and procedural both have their places. In particular, a MMO is a great example where both paradigms have their advantages:

One would typically use an Object Oriented approach when designing systems, game mechanics, abilities, items and so forth. These concepts are more easily expressed in an Object Oriented design. Especially when considering the power of polymorphism.

But MMOs also require a second kind of scripting that is better done procedurally. For example, a typical MMO will have a large inventory of quests or missions. These often have a lot of one-off special case scripts for special events, triggers and other sorts of actions that occur. In an OO methodology, one would have to create a class to hold the method(s) for these operations. But because of the special one-off nature of these, this leads to a real pollution of the class name space. You essentially end up creating dummy classes just as a holder for a method and not for storage of information. Ick. So by using procedural scripting (functions instead of methods) you can more naturally express these special case scripting needs.

This is just one example of how HSL is designed with precise needs of an MMO in mind (as opposed to a more generalized language like Python).

Data Object Model

Most programming languages allow you to define the data model of your program in code such as in C++ with the class or struct statement. In HSL, however, the classes, fields and other data definitions are actually defined separately from the language by use of either DOM related CLI Commands or the DOM Editor interface.

There is a separate DOM for the client and server. Often the client has similar classes to the server, but they need a lot less fields. For example the server will know a lot about items in the game, but the client only needs to know enough to render them (or their icons). In fact, full details about the items may be a client<->server exchange that only happens when needed (otherwise every login might produce too much network traffic). So that you can code your game in the optimal manner, these two DOMs are kept separate.

The Data Object Model is dynamic. Anyone (who has permissions) can edit the DOM at anytime. Changes to identifiers are reflected instantaneously in all scripts (except those opened in an editor at that moment). So if you change a field's name from "Bob" to "George", any script that directly references it will be correct when next opened. Most changes that might cause a conflict or break a script are detected by editing the DOM and will be aborted.

Real-time editing of the DOM lets you build out your game data model without having to worry about database schema changes or anything of that nature.

It is important to note that the seperation of the data object model from the language itself has a lot of productivity ramifications. For example, in the real-world of MMO development, design often goes through many interative revisions during the development process. Say, for example, half way through implementation the design team decides that "enhancements" need to be renamed "foobars". Yet you have tons of classes and fields already named "enhancement-this" and "enhancement-that". You can choose to try and refactor all this (which is error prone and very time consuming) or just use the new names from that point forward which creates code maintenance problems. But with the DOM editor, you can just rename the fields or classes and their usage is updated in all scripts dynamically. No recompiling, no search-and-replace errors, just done.

This, again, is another example of how the HeroEngine's approach is focused on the real-life practical problems of MMO development with unique solutions.

To translate the terminology:

Flat Name Space for Identifiers

Probably the most unusual aspect of HSL's data model is that identifiers are in a flat name space. Therefore, if you have a field named "Bob", there is exactly one and only one definition of that field of that name. And it means the same thing no matter what class it may show up in.

In most programming languages, you would expect that a class Foo that has a member named Bob and class Bar that also has a member named Bob, that these two might often mean different things and certainly represent different data storage. But in HSL they are the same. In fact, if you have a Node that is of class Foo and then you glom on class Bar (remember both have the field Bob), the resulting Node will still have one and only one Bob field, not two.

Although this may seem unusual, it does serve some useful purposes. First, a field has a very well defined meaning; one classes' Bob is not different than another class. Further, since Nodes can mix-in classes at will during runtime, this helps keep everything straight syntactically and on a practical level.

The only major change to your programming practices is that this forces you to use more descriptive names for your fields and other data definitions. No longer is it sufficent to make a field named "Count"; instead "InventoryCount" may be what you need to define. However, if a field of name "Count" serves your purpose everywhere that might be used (and having one and only one on a Node is proper) you can use such a field name without problems.

Classes

Classes are a grouping of fields. Each field can be of a particular type, and HSL supports a wide variety of Data Types. Classes may inherit from more than one parent class. Keep in mind that if the same field is duplicated in the hierarchy of classes, it actually results in one and only one instance of that field on the instantiated Node. So if class Foo inherits from class Bar and both have the field Bob, that field will exist exactly once on the resulting Node instantiated from either class Foo or Bar.

Class Methods

All methods for a particular class exist in the script named {ClassName}ClassMethods. So, if you have a class named "Foo" then the methods for that class would be located in the script named "FooClassMethods".

Array Indexing

Array indexing is 1 based. Most langauges are 0 based, but because HSL was designed to be taught to non-programmers (people who are use to counting things starting with 1 instead of 0), we choose to go with 1. So bob[1] is the first element of array bob, and bob[0] is not legal.

The me Node

HSL has a special me node that is similar to the this variable in C++. One important distinction though, is that me is available outside of class methods. This is described in more detail below.

Exposed Functions

A library of functions exists to query information, or manipulate the client or server. Most are common to both the client and server, but some are unique. For a list of commonly used functions, see: Function Index

The Integrated Development Environment

HSE2 is HeroEngine's script editor. It is built directly into HeroBlade

Invoking

With HeroBlade open and focus in the render window, hit CTRL-H. Alternately, open HeroBlade's Organizer panel, and double-click a script to edit. You can also open the editor from HeroBlade's dropdown menus.

HeroBladeInvokeHeroScriptEditor.png

Opening a Script

As mentioned, scripts can be browsed via the Organizer panel, however when you know the name of the script you wish to open, hit CTRL-SHIFT-S for server scripts or CTRL-SHIFT-C for client scripts, and start typing the name of that script.

OpenScriptUsingHSE2.jpg

Language Fundamentals

Statements

Statements don't require any special character to indicate their end, except for multi-line code block, as described in that section below.

Output

The external function println() provides very basic functionality for printing and exists on both the server and the client.

println( "This is a message." )

When used in a client script, messages are printed to HeroBlade's Console panel (a.k.a. Logging from the Panels dropdown menu). In server scripts, the message is sent to the Chat panel on the hsl_debug channel.

On the server you can also use the MsgPlayer() external function if you know the account ID of the player to message.

MsgPlayer( accountID, "", "This is a message." )

Note that the second parameter is the channel name that the message should be displayed on, but can be left empty for the Default channel.

Ending Semicolons

Many languages, like C, C++, Java, C# etc. end statements with a semicolon. Since this is so popular, HSL statements can also end with a semicolon. These semicolons are simply ignored.

So, these two statements are identical:

println("Hello World!")
println("Hello World!");

Messaging

While println and MsgPlayer functions support simple string output, more complicated messaging is handled via Remote Calls or Replication. Remote calls are used to execute script code in a destination process. Replication maintains copies of nodes in destination processes and provides change notifications. Note by their nature, remotecalls and Replication function as asynchronous operations.

See also: Inter-Process Communication


See also: Asynchronous considerations


Declaration

HeroScript Language (HSL) is strongly typed, which means that variables need to be declared with their type:

count as Integer
location as Vector3
message as String
myItem as NodeRef of Class InventoryItem
questTable as LookupList indexed by ID of NodeRef of class QuestSpecifications

Local variables follow the same scope rules as other languages (they exist within their code block and hide identical identifer names that might exist outside of their code block). The exception being that it is a compile error to try to hide a variable within the same function.

Assignment

Assignment is permissible during declaration:

count as Integer = 1

Or at anytime thereafter:

count = count + 1

Note that both operators, = and ==, are valid for both assignment and equality testing, because their context will always be well-defined.

HSL does not allow for multiple assignment.

Variable Type Inference using VAR

HSL support intrinsic variable type inferencing with the var keyword. This is a convenient shortcut for declaring variables with complex types. So something like this:

macroMap as LookupList indexed by ID of List of String = Macro:GetMap( myID )

can be written as:

var macroMap = Macro:GetMap( myID )

The var keyword tells the compiler to declare macroMap as whatever type is returned by the function GetMap() in the script Macro.

Note: This is similar to the feature that is also found in the upcoming 3.0 specification for the C# language.

The var keyword, in this context means that the variable is typed based on the context. It does not mean "varient" as in Visual Basic; the variable is still strongly typed, and var only works when the type can be identified by the compiler at compile time.

Other examples:

var ThisWilLBeAnInt = 5
var IAmGoingToBeAString = "Hello World!"
var ThisWillEnsureIAmAFloat = 4.5
 
myItem as NodeRef of Class InventoryItem
var IWillAlsoBeANodeRefOfClassInventoryItem = myItem

This is a much more convenient way of specifying the type of an identifier. Also, it helps a lot during refactoing since the type is inferred by assignment.

See also: Data Types


Auto-Conversion

HSL has no variant type, so makes extensive use of auto-conversion for scalar types to facilitate ease of use and readability. Most scalar types autoconvert to String and vice versa.

count as Integer = 5
myString as String = count
println( "myString = " + myString )

The following also would have worked because of auto-conversion:

var count = 5
println( "myString = " + count )

Additionally, NodeRef and ID autoconvert between each other.

See also: Auto Conversion


Code Blocks

Code blocks require no special character to start (always being indicated by a keyword), but are ended with a period on a line by themselves:

if count == 1
  println( "You're all alone." )
.

While code blocks are indented by convention, this is not enforced in the editor. Note that the TAB key in the editor inserts 2 spaces.

Flow Control Statements

The following are common looping and branching logic statements.

if count == 1
  println( "You are alone." )
else if count == 2
  println( "There is someone nearby." )
else
  println( "You are not alone." )
.

Note: = is the same as ==. Allowing for == is a convienence for C++, Java and C# programmers.

when myInteger
  is 1
    // do option 1
  .
  is 2
    // do option 2
  .
  default
    // give error feedback
  .
.

Note: There is no "fall through" on when as there is on the switch statement in C++.

var myIntegerList = NonExistentUtil:GetAList()
foreach item in myIntegerList
  println( "The next integer is " + item + "." )
.

Note that foreach implicity defines the "iterator" variable type, which is item in this example. The scope of item is limited to the foreach statement. This is a convienence that makes foreach statements much simpler syntactically.

var nameToNumberMap = NonExistentUtil:GetThatLookupList()
foreach key in nameToNumberMap
  println( "Hello, " + key + ".  Your number is " + nameToNumberMap[key] + "." )
.

Note that key did not need to be declared, was auto-typed to the lookuplist's key type, and only exists in the scope of the foreach statement.

loop i from 1 to myList.length
  println( "Found " + myList[i] + "." )
.

Note that i does not need to be declared, is auto-typed as an Integer, and only exists in the scope of the loop block.

loop f from 0 to 1 by 0.1
  println( "f=" + f )
.

Note that f does not need to be declared, is auto-typed as Float, and only exists in the scope of the loop block.

var isCool = TRUE
while isCool
  isCool = DoSomething()
.

Break and Continue

In order to break out of any loop, use the break statement.

var myIntegerList = NonExistentUtil:GetAList()
foreach item in myIntegerList
  if item == 5
    break
  .
.

In order to end the current loop cycle and reprocess the loop conditional, use the continue statement.

var myIntegerList = NonExistentUtil:GetAList()
while myIntegerList.length > 5
  DoSomething( myIntegerList )
  if CheckSomething( myIntegerList )
    continue
  .
  DoSomethingElse( myIntegerList )
.

Nodes

Nodes are Class instantiations. To instantiate a class, use the CreateNodeFromClass() function. This is similar to new in C++/C#/Java/etc.

myNode as NodeRef of Class MyClass = CreateNodeFromClass( "MyClass" )

Fields and methods on a node are accessed with dot notation.

var count = count + myNode.total
myNode.DoCoolThing("Hello World!")

Note: There is no concept of constructors or destructors in HSL as there is in languages like C++.


See also

Class Reflection

To determine Class of a Node, use the where statement and the kindof or exactly operator:

where myNode
  is kindof MyClass
    // do MyClass-specific operations on myNode
  .
  is kindof YourClass
    // do YourClass-specific operations on myNode
  .
  is exactly TheirClass
     // do TheirClass-specific operations on myNode
  .
  default
    // do whatever is done with unknown types
  .
.

There is also an inlined variation of the where statement:

where myNode is kindof MyClass
  // do MyClass-specific operations on myNode
.

The where/is structure not only allows you to operate on different code paths based on the class of the node, it also type-casts the variable inside that code block. This means that for the scope of that code block, the variable is now of class type indicated by the is block.

The is exactly clause works the same was as is kindof but ignores inherited classes.

Recall that nodes are derived from a class, but that class can have parent classes and, furthmore, nodes can have other classes "glomed" on to them at run-time. The where construct makes it easy to deal with the many-classes nature of nodes.

The me Node

A me node is a special variable which is sometimes set to refer to a particular node. The way in which this is used, depends on whether it is being done with functions or methods:

ME in functions

When script functions are called, the me is sometimes used. It is not typed by class, so working with it almost always requires type checking with the WHERE statement. The following roughly outlines the situations when the me node is set:

myNode.someScriptField:ASharedFunction()


ME in methods

When a method is called on a node, the me node is always the node the method was called on. In that method, the compiler knows it to be of the class the method belongs to (but no other class). So, for methods, me is like the "this" keyword in C++ or C#.

Example:

method Bar()
  println("Bar")
.
 
method Foo()
  println("Foo, will now call Bar()")
  me.Bar()  // Assumes this
.

See also: System nodes

Associations and Node Hierarchies

HeroEngine features an Association Engine which handles keeping track of how nodes are associated with each other. For example, what items are in your inventory, or what characters are the member of a guild. Although you can maintain such lists in many other ways (arrays, lookup lists, etc.), the Association Engine provides a great deal of power that these other techniques do not. Which you choose to use depends on the particular situation.

An Association is particularly useful because it is an independent data structure. For example, if you associate node A with node B, it is bi-directional. So B can know it is associated with A just as easily as A knows it is associated with B (and exactly what type of assocation). Furthermore, if either node A or node B were to be deleted, the assocation simply goes away; no dangling pointers or other problems common in other languages.

An association is a complex data type that has the following attributes:

Source and target can be thought of as parent node and child node, respectively. The association type is simply a string identifier that describes the relationship between the source and target nodes.

Associations come in two types, hard and soft.

Hard Associations

Hard associations are used to define node hierarchies that delineate ownership of children node(s) by a parent node. Hard assocations always describe one-to-one or a one-to-many relationships, and are always exclusive of each other.

Hard associations are used by the server to identify which nodes in a hierarchy are to be loaded when a given parent node is loaded.

Soft Associations

Soft associations are used to define all non-ownership relationships between nodes. They can describe one-to-one, one-to-many, many-to-one, and many-to-many relationships. Soft associations may or may not be grouped to provide for exclusivity with other association types.

Querying Associations

The external function QueryAssociation( source as NodeRef, type as String, target as NodeRef ) returns a list of assocation containing all associations that match the specified parameters. Up to two of the parameters can be wild-carded by specifying None for the source/target, and/or an empty string ("") for the association type. Some examples:

The following will return a list of all associations with myNode as the source and myAssociation as the association type, thereby allowing you to iterate through all the targets:

QueryAssociation( myNode, "myAssociation", None )

The following will return a list of all associations of the myAssociation type between any pair of nodes:

QueryAssociation( None, "myAssociation", None )

The following will return a list of all associations in which myNode is the source.

QueryAssociation( myNode, "", None )

There are other specialized functions for common queries. For more information, please see the section on Associations.

Getting Root Nodes

On the client, the top-level node for all instantiated nodes is accessed via the system variable:

world as NodeRef of Class world_anchor = SYSTEM.INFO.WORLDANCHOR

On an area server, the top-level node for all instantiated nodes is accessed via the GetRootNode() function:

root as NodeRef of Class AreaRoot = GetRootNode()

See also: Associations


Procedural or Object Oriented?

HSL started as a procedural language and has added features of object oriented programming over time. Consequently, HSL is capable of expressing both styles of programming allowing the use of whichever style is most appropriate for your needs. Most callbacks from the C++ engine into script code are procedural.

Procedural Coding

As you would expect, calling a function procedurally requires the compilier either being able to identify the script in which the function is located or the scripter explicitly declare the script in which the function is located.

Calling a Function in a Script

Assume you wish to call a function Bar located in the script Foo.

Calling a function in another script directly via the script name.

Foo:Bar()

Calling via a scriptref.

// Assume you have a scriptref variable ( s ) referencing the script Foo
s:Bar()

Note that a colon(:) is used to make function calls.

Calling a function located in the same script.

// Note because the function is located in the same script, you may omit the script name and :
Bar()

Calling External Functions

External functions communicate directly with the C++ game engine, as such you do not need to specify a script to call them.

var nodeAsString = MarshalNode( myNode )

Note all external functions are declared in the _ExternalFunctions script.

See also: User Functions


Object Oriented Coding

Unlike most object oriented languages you do not define your data object model (i.e. the class and fields) in your object's code; in other words there is no "class definition" syntax in HSL. Instead, these things are defined in a seperate database called the Data Object Model or DOM. You create and edit these data type definitions using the DOM Editor in HeroBlade.

Class methods of a particular class are written in a script named <ClassName> + ClassMethods. For example, a class Foo would look for a script named FooClassMethods.

Assume your class is named Foo and it contains a method named baz. Assume you have an object named obj which is of class Foo.

HeroEngine's DOM supports class inheritance and multiple inheritance at the field/class level, but requires you resolve method conflicts either by explicitly declaring which parent class's method should be called or by avoiding structures where you get the diamond pattern of inheritance.

Calling an Object's Methods

obj.Baz()

Note that methods are called using a period(.) between the object and the method name.

See also: Using Class Methods


Me, the new This

In C++ the current object is referenced in a Method by the keyword this. In HSL, the equivelent is me:

me.LopOffHead(true)


Function/Method Signatures

Signatures are made up of one or more keyword modifiers preceding a name, with arguments offset by () and optionally specifying a return type.

<keyword(s)> + name + (arg as String, intArg as Integer...n) + <as ReturnType>

public function DoStuff( arg as String, intArg as Integer ) as boolean
  return true
.

Arguments may be passed using AS, REFERENCES, COPIES with the normal provisos that unnecessary copying is slow.

Function modifiers

Main page: Function/method modifiers
(none) 
Default for functions, meaning functions which may only be called from within the same script
Public 
Functions may be called by other scripts without any particular restrictions.
Shared 
Similar to public, but functions live in a shared signature space and have an expected signature, which is checked at compile-time.
Remote 
Functions or methods which handle calls from other servers made via Remote Call
Untrusted 
Similar to Remote, but indicates functions or methods which may receive a remote call from the client. Because you can never be sure if the client is hacked or not, all functions marked as untrusted have an implicit contract to validate the parameter data, and even the fact that they are being called at all.
Unique
Methods which exist in one and only one classMethods script. This is useful for methods you do not wish to allow child classes to override (similar to sealed in C#).
ProxyLocal 
Used in conjunction with the Replication system to mark methods which, if run on a proxied node, should be handled in the local GOM
ProxyForward
Used in conjunction with the Replication system to mark methods which, if run on a proxied node, should be forwarded back to the source node for handling in that area's GOM

See also

Serialization

It is often useful to move data from one server to another and a server to the client. HSL supports a number of methods for serialization.

Replication

HeroEngine has a fully featured Replication System for communicating data between servers and the player client. Replication is the primary means of communicating game data to the client.


External Functions - Marshal

There are a number of external functions that support marshaling a node or prototype. The process of marshalling serializes the fields ( by name ) on a node and the data those fields contain. The serialized data can then be unmarshalled onto a node in the destination. As a result of marshalling working by field name, the source and destination nodes do not necessarily need to be of the same class. For fields not present in the destination node, unmarshalling either ignores the extraneous data or errors (depending on what you prefer).

Marshalling a node.

n as noderef of class Foo = someNode
var marshalStr = MarshalNode( n )

Marshalling a prototype.

var marshalStr = MarshalPrototype( getPrototype( "myProto" ) )

Unmarshalling to an existing node.

aFooNode = UnmarshalNode( marshalStr, true )


Required Utility - MarshalUtils

While you do not always want or need to reconstruct the node in the destination GOM, when you do you need additional information such as the class hierarchy. One of the Required Utility scripts is the MarshalUtils script, it has a variety of functions used to marshal a node including marshalling only selected fields as indicated by the classes on the node ( via their classMethods scripts ) or appending additional fields from templated data.

Unmarshalling using the MarshalUtils script supports recreating a node using the class hierarchy data stored in the serialized form created using the marshal functions from the utility.


Language Supported - Marshal Variables

Frequently the data with which you are working is stored in a variable rather than a node, rather that forcing you to create a node and copying the data HSL supports directly serializing a variable. Once serialized, you can unmarshal into a variable or a field that has the same type. This functionality supports arbitrarily complex fields such as list of class Foo.

Assume myVar is a variable of type list of class Foo.

MARSHAL myVar TO marshalStr

newVar as List of class Foo
UNMARSHAL newVar FROM marshalStr

See also: Marshaling


Data Storage

There are a variety of methods supported by HeroEngine for persisting data, as in any language there are trade-offs that must be considered when choosing one method over the others. A brief analysis of the different methods is detailed on the linked page and our staff is available to discuss which particular method(s) are suited to a given task.

See also: Data Storage


Client / Server Communication

The client and the server can communicate in several ways. The low-level capabilities that allow for this are remote calls and (in version 1.22 and later) replication.

Remote calls allow a server to call a function or class method on another server, or on the client. The client can also call a script function or class method on the area server it is connected to (but only those functions/methods marked as untrusted). Note: Because of the reality of hacking and spoofed-clients, it is important for the server to verify any remote calls it receives from a client.


Replication, new in version 1.22, is a mechanism whereby nodes in one GOM (on the server) can be replicated (have a proxy) on attached clients which are handled by a different area server. Updates to fields on these nodes are transmitted based on a prioritization strategy (allowing you to take advantage of bandwidth management features). Data can be replicated in both directions, and even between area instances.

More information

Appendices

Appendix 1 - OO Programming: Define a Class and Execute Its Code

Overview

In HSL, the data object model (classes, fields, enums, etc.) are defined independently of the scripting language (HSL). This is different than most computer languages, but gives HeroEngine it's distinctive power and real-time collaborative flexibility. The DOM can be manipulated with the CLI, but it is much easier to use the DOM Editor GUI.

This section will walk you through the steps of defining and using a new class.

Defining your Data Model

Open the DOM editor from HeroBlade's Panels menu (ALT + P).


DOMEditorLayout.png


CreateNewClassDialog.png
CreateNewFieldDialog.png
AddFieldToClassDialog.png


Having followed these steps should leave you with a class which has a single string field in it. In this particular example I have created the class ObjectOrientedDemo.

Create a new Class Methods Script

Once you have a class defined using the DOM, you need now to create a script that follows a specific naming convention so that HeroEngine knows where to look for the code that belongs to your object. The name expected is NameOfYourClass + ClassMethods. That means the name of my ClassMethods script will be ObjectOrientedDemoClassMethods.

method SetObjectOrientedDemo()
  me.ObjectOrientedField = "This was neat on the client."
.
 
method PrintObjectOrientedDemo()
  println( me.ObjectOrientedField )
.
 
public function ConsoleDo()
// Note because we don't have an instantiated object yet, we need to create one to call its methods
//   which makes this function the only procedural one in this class methods script
//
  demo as NodeRef of Class ObjectOrientedDemo = CreateNodeFromClass("ObjectOrientedDemo")
  demo.SetObjectOrientedDemo()
  demo.PrintObjectOrientedDemo()
 
  // Destroy the node since we do not need it anymore
  destroyNode( demo )
.

Call your code from the Console

See also: Invoking scripts

Public functions may be called directly from the console's command line.

ConsoleCallFunctionExample.png

Exercise

Go forth and use what you have learned.

Video Tutorials

My First HSL Script, a brief, step-by-step tutorial.


HeroScript for Programmers pt 1

HeroScript for Programmers pt 2

HeroScript for Programmers pt 3

See also

Video:

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox