Introduction

In this tutorial, you will learn the basics of Hitoshina - Language (HL) by building a Counter HUD for use in Second Life.

What You Will Build

The completed HUD will have the following features:

  • + button to increment the counter by 1
  • - button to decrement the counter by 1
  • Menu button to display a dialog menu for resetting or viewing the current value

It is a simple HUD, but it covers all the major concepts in HL.

What You Will Learn

ChapterConcepts
Project Setupscript declaration, #init event, sending messages
HUD Structureprim definition, properties
Touch Handling#touch_start event, prims groups
Messaging-> operator, link messages
State Management & Dialogscontext, #listen, dialog syntax

Prerequisites

  • Basic Second Life operations (creating objects, attaching HUDs)
  • A text editor (VS Code, Neovim, etc.)
  • The HL compiler (hl) installed

For compiler installation, refer to the download page.

Project Setup

Start by creating an HL project, writing your first script, and compiling it.

Creating the Project

Run the following command in your terminal to create the project:

This generates the following directory structure:

  • lib/ — holds .hlib files of reusable functions and constants
  • prc/ — holds .hprc files of reusable procedures
  • msg/ — holds .hmsg files that define message-receive handlers
  • output/ — the output directory for compiled LSL files
  • tests/ — holds unit tests (.hltest files)
  • main.hl — the project’s entry point, generated with an initial template ("Hello, World!" -> world;)

Your First Script

Open the generated counter_hud/main.hl and replace its contents with the following:

This code has three parts:

  1. script main; — declares a script named main. Compiling it produces output/main.lsl
  2. main #init { ... } — the script’s initialization event, which runs when the script is loaded or reset in Second Life
  3. "Counter HUD Ready" -> owner; — sends a message to the owner (yourself)

Compiling and Running

Run the compiler from inside the project directory:

output/main.lsl is generated. Drop this LSL file into a Second Life object and test it.

If “Counter HUD Ready” appears in chat, you’re all set.

Message Sending Basics

In HL, the -> operator sends messages. Several destinations are available:

Using -> owner during HUD development is convenient because only you can see the messages.

HUD Structure

Set up prim definitions and properties so that the HUD buttons can be controlled from HL code.

Preparing the Object in Second Life

Create the following in Second Life:

  1. Create one root prim (the HUD body)
  2. Link three child prims to it (the +, -, and Menu buttons)
  3. Set each child prim’s Name to btn_plus, btn_minus, and btn_menu respectively

Important: HL prim identifiers must match the prim names in Second Life. Touch events are dispatched to the correct handler based on the prim name.

Prim Definitions

Add prim declarations to main.hl. A prim represents a child prim in the linkset:

prim btn_plus, btn_minus, btn_menu; declares three prims whose names must match the corresponding Second Life prim names.

Setting Properties

To use prim methods such as set_color, the script must cache link numbers at startup. Add the #cache_linknum property:

Since a HUD is worn as an attachment, #init should also run when it is attached. Add the #attachable property:

Property Reference

PropertyEffect
#cache_linknumScans the linkset at startup, matches prim names against prim identifiers, and records link numbers. Required for prim method calls such as set_color, name(), and desc()
#attachableRe-runs #init (and #animable if set) when attached to an avatar

The code is now ready to control the button prims.

Touch Handling

Implement the behavior for when HUD buttons are touched.

The touch_start Event

In HL, the #touch_start event detects when a prim is touched. Event handlers are written in the form <prim-name> #touch_start @<script-name>:

Elements of each event handler:

  • btn_plus — the prim to detect touches on
  • #touch_start — the touch-start event
  • @main — the script that handles this event
  • |prim obj| — parameter that receives information about the touched prim

Event Dispatch by Prim Name

HL automatically dispatches touch events to the correct handler by matching prim names against prim identifiers. Writing btn_plus #touch_start @main means the handler runs only when the prim named btn_plus is touched — no manual conditional branching needed.

prims Groups

When there are many buttons, you can use a prims group to handle them together. For example, if the prim names share a suffix of the same length:

prims btn_*** = btn_add, btn_sub, btn_cfg; groups three prims together. The wildcard *** represents three characters and matches the part after btn_ (add, sub, cfg).

Wildcard rule: The number of * characters must equal the length of the suffix. All members must have suffixes of the same length. In this tutorial, btn_plus (4), btn_minus (5), and btn_menu (4) have suffixes of different lengths, so they cannot be grouped with prims.

Since each button has different logic and the suffix lengths are uneven, individual handlers are used here.

Code So Far

Counter increment and decrement are not implemented yet. After learning about messaging in the next chapter, they will be completed together with state management.

Compile the script, add it to your HUD, and confirm that touching each button displays the corresponding message.

Messaging

Let’s take a closer look at HL’s message sending feature.

Message Sending Options

The -> operator in HL supports multiple destinations:

In objects with multiple scripts, link messages are used for inter-script communication. As a HUD grows larger, splitting logic across scripts becomes common practice.

Sender:

The receiver listens with the #linkmsg event:

:100 is the channel number. Using the same number on both sides filters messages to only those on that channel.

Choosing Channels

Link message channels can be freely assigned, but organizing them by purpose makes the code easier to maintain:

Constants defined with const can be used anywhere in the code.

In the Counter HUD

This counter HUD fits within a single script, so link messages are not used here. However, they are an important concept when a HUD grows more complex — for example, when display control is split into a separate script.

The next chapter introduces state management with context and implements counter increment and decrement.

State Management & Dialogs

Implement counter value storage and a menu dialog triggered by the Menu button.

State Management with context

In HL, context is used to manage script state. Variables declared inside a context retain their values across event handler calls:

context @main is the state associated with the main script. int count = 0; sets the initial value of the counter to 0.

To access the context in an event handler, add context to the parameter list:

Implementing the Counter

Using context, complete the code so that button touches increment and decrement the count:

Multiple parameters can be received as in |prim obj, context ctx|. ctx.count.to_str() converts the integer to a string.

Displaying a Dialog

Show a dialog when the Menu button is touched. The send and receive sides have to share the same channel number, so use the const keyword introduced in chapter 5 to give that number a name. Sprinkling the same literal in multiple places is a classic source of mismatches when only one side is updated.

Use a negative channel for dialog responses. Avatars can only chat on channel 0 and positive channels through the normal chat bar, so negative channels stay free of user input and won’t get triggered by stray messages.

HL has dedicated syntax for dialogs that lets you visually arrange button layouts directly in code:

Elements of this syntax:

  • "Counter: " + ctx.count.to_str() — the dialog message
  • |"Reset", "Close"| — one row of buttons (displayed in the order written in source)
  • @owner:CH_DIALOG — the target (owner) and channel (CH_DIALOG = -101) for receiving the dialog response
  • -> world; — sends the dialog (fixed syntax)

Button order: LSL dialogs display buttons in reverse order, but HL automatically reverses the list at compile time. The order written in source is the order shown in the dialog.

Handling Dialog Responses with the listen Event

When a dialog button is pressed, a message arrives on the specified channel (CH_DIALOG). Use the #listen event to receive it:

  • :CH_DIALOG — listens for messages on the CH_DIALOG channel
  • + owner — accepts only messages from the owner (security measure)
  • |str msg, context ctx| — receives the message and context

Full Code So Far

Compile the script and add it to your HUD. Confirm that the +/- buttons change the count and that the Menu button opens a dialog.

Showing the Prim Description

Up to this point, the |prim obj| parameter on #touch_start has only been declared — never read. In this chapter we will use obj.name() and obj.desc() to fetch information about the touched prim and display values configured on the Second Life side.

Second Life Setup

Set the Description field of each button to any string you like. From the Second Life edit panel:

  1. Wear or rez the HUD.
  2. Open edit mode and select btn_plus, btn_minus, and btn_menu one by one.
  3. Enter the following into the Description field on the General tab.
Prim nameDescription
btn_plusIncrement counter
btn_minusDecrement counter
btn_menuOpen menu

obj.name() and obj.desc()

|prim obj| is the prim instance passed to the touch_start handler. obj.name() returns the identifier name and obj.desc() returns the Description:

Touching the button now produces output like:

Full Code So Far

Because the Description can be edited from Second Life without recompiling the LSL, this technique is also useful for swapping per-button labels or configuration values after deployment.

Color That Reflects State

Switch the color of the Menu button based on the counter value held in context. The set_color method changes a prim’s color dynamically.

The set_color Method

Set a prim’s color with #<prim-id>.set_color|<face>, <color>, <alpha>|.

ArgumentTypeDescription
faceintFace index. std::ALL_SIDES selects every face.
colorvec<R, G, B> (each component in 0.0..1.0).
alphafloat0.0 (transparent) to 1.0 (opaque).

std::ALL_SIDES is a constant from the standard library, so add import standard as std; at the top of the script.

Branching on the Counter Value

Switch to green for positive values, red for negative values, and white for zero. Use if / elif / else:

Comparison operators: As a general coding convention this codebase uses only < / <=. Place the smaller value on the left side, e.g. 0 < ctx.count.

Extracting a Procedure

The counter changes in four places: #init, btn_plus, btn_minus, and the Reset branch in #listen. Repeating the same if / elif / else block in each spot would be noisy, so extract the color-update logic into a procedure.

A procedure is defined with #<name> |<params>| { ... } and called with #<name>|<args>|;. By accepting the prim to color as a prim parameter, the same procedure can be reused for any button:

Notes:

  • A prim parameter such as target is invoked the same way as a static prim identifier — with the # prefix: #target.set_color|...|;.
  • Call sites pass the target prim as a plain identifier (without #): #update_color|btn_menu, ctx.count|;.

Full Code So Far

Compile, install the HUD, and use the +/- buttons. The Menu button cycles through green ↔ white ↔ red — confirming that #cache_linknum is now actually doing the work that set_color requires.

Completion & Summary

The Counter HUD is complete. Here is a summary of the concepts covered so far.

Final Code

Concepts Learned

ConceptHL SyntaxDescription
Script declarationscript main;The unit of LSL output file
Importimport standard as std;Loading the standard library
Constantsconst int CH_DIALOG = -101;Naming an intent so the literal isn’t duplicated
Prim definitionprim btn_plus;Identifies a prim in the linkset
Properties#cache_linknum, #attachableScript behavior configuration
Events#init, #touch_start, #listenResponding to Second Life events
Message sending"msg" -> owner;Sending to various targets
State managementcontext @main { int count = 0; }Variables shared across events
Dialogs"msg" |"btn"| @owner:ch -> world;Interactive menu display
Control flowif / elif / else, switch msg { "val" => ...; }Branching by condition or pattern
Procedures#name |args| { ... }, #name|args|;Reusable side-effect blocks
Prim methodsobj.name(), obj.desc(), #prim.set_color|...|Reading prim info and updating appearance

Next Steps

This tutorial covered the fundamentals of HL. To learn more, use the following resources:

  • Language Reference — the complete specification for the type system, expressions, standard library, and more
  • Playground — an in-browser editor to try HL code instantly

HL has many features not covered in this tutorial:

  • Libraries (.hlib) — bundle functions and constants into reusable modules
  • Procedures (.hprc) — extract shared logic into separate files included via include
  • Message handlers (.hmsg) — move link message handling into dedicated files
  • Typed lists (ilist, slist, etc.) — type-safe list operations
  • map/fold expressions — transform and aggregate collections
  • Tests (.hltest) — write unit tests for your code

Using these features, you can build larger and more maintainable projects.