はじめに

このチュートリアルでは、Second Life で使えるカウンターHUDを作りながら Hitoshina - Language(以下 HL)の基本を学びます。

何を作るか

完成するHUDは以下の機能を持ちます:

  • + ボタンでカウントを1増やす
  • - ボタンでカウントを1減らす
  • Menu ボタンでダイアログメニューを表示し、リセットや現在値の表示ができる

シンプルなHUDですが、HL の主要な概念をひと通り体験できます。

学べること

章学ぶ概念
プロジェクトの準備script 宣言、#init イベント、メッセージ送信
HUDの基本構造prim 定義、プロパティ
ボタンとタッチ操作#touch_start イベント、prims グループ
メッセージ通信-> 演算子、リンクメッセージ
状態管理とダイアログcontext、#listen、ダイアログ構文

前提知識

  • Second Life の基本操作(オブジェクトの作成、HUDの装着)
  • テキストエディタの操作(VS Code、Neovim など)
  • HL コンパイラ (hl) がインストール済みであること

コンパイラのインストールについてはダウンロードページを参照してください。

プロジェクトの準備

まず HL のプロジェクトを作成し、最初のスクリプトを書いてコンパイルするところまで進めます。

プロジェクトの作成

ターミナルで以下のコマンドを実行してプロジェクトを作成します。

以下のディレクトリ構成が生成されます。

  • lib/ — 再利用する関数・定数をまとめる .hlib ファイルの置き場
  • prc/ — 再利用するプロシージャをまとめる .hprc ファイルの置き場
  • msg/ — メッセージ受信時の処理を定義する .hmsg ファイルの置き場
  • output/ — コンパイル結果(LSLファイル)の出力先
  • tests/ — 単体テスト(.hltest ファイル)の置き場
  • main.hl — プロジェクトのエントリポイント。初期テンプレート("Hello, World!" -> world;)が書き込まれた状態で生成されます

最初のスクリプト

生成された counter_hud/main.hl を開いて、以下のコードに書き換えます。

このコードは3つの要素で構成されています:

  1. script main; — main という名前のスクリプトを宣言します。コンパイルすると output/main.lsl が生成されます
  2. main #init { ... } — スクリプトの初期化イベント。Second Life でスクリプトが読み込まれたとき、またはリセットされたときに実行されます
  3. "Counter HUD Ready" -> owner; — オーナー(自分)にメッセージを送信します

コンパイルと実行

プロジェクトディレクトリでコンパイルを実行します。

output/main.lsl が生成されます。このLSLファイルを Second Life のオブジェクトに入れてテストしてみましょう。

チャット欄に「Counter HUD Ready」と表示されれば成功です。

メッセージ送信の基本

HL では -> 演算子でメッセージを送信します。送信先はいくつか用意されています。

HUDの開発中は -> owner を使うと、自分にだけメッセージが見えるので便利です。

HUDの基本構造

HUDのボタンを HL のコードで扱えるようにするため、prim 定義とプロパティを設定します。

Second Life 側の準備

Second Life で以下のオブジェクトを作成してください:

  1. ルートプリム(HUD本体)を1つ作成
  2. 子プリムを3つリンクして追加(+ボタン、-ボタン、Menuボタン)
  3. 各子プリムの Name(名前) をそれぞれ btn_plus、btn_minus、btn_menu に設定

重要: HL の prim 識別子は Second Life のプリム名と一致している必要があります。タッチイベントはプリム名をもとに正しいハンドラへ振り分けられます。

prim 定義

main.hl に prim 宣言を追加します。prim はリンクセット内の子プリムを表します。

prim btn_plus, btn_minus, btn_menu; で3つのプリムを宣言しています。これらの識別子は Second Life 上のプリム名と一致させる必要があります。タッチイベントの振り分けはこのプリム名で行われます。

プロパティの設定

prim のメソッド(set_color など)を使うには、スクリプト起動時にリンク番号をキャッシュする必要があります。#cache_linknum プロパティを追加します。

また、HUDは装着して使うものです。装着時にも #init が実行されるように #attachable を追加します。

プロパティの意味

プロパティ効果
#cache_linknum起動時にリンクセットを走査し、プリム名と prim 識別子を照合してリンク番号を記録します。prim のメソッド呼び出し(set_color、name()、desc() など)に必要です
#attachableアバターに装着されたときにも #init と #animable(設定時)を再実行します

これで、コードからボタンプリムを操作する準備が整いました。

ボタンとタッチ操作

HUDのボタンをタッチしたときの処理を実装します。

touch_start イベント

HL では #touch_start イベントでプリムのタッチを検出します。イベントハンドラは <prim名> #touch_start @<スクリプト名> の形式で記述します。

各イベントハンドラの構成要素:

  • btn_plus — タッチを検出する prim
  • #touch_start — タッチ開始イベント
  • @main — このイベントを処理するスクリプト
  • |prim obj| — タッチされたプリムの情報を受け取るパラメータ

prim 名によるイベント振り分け

前章で Second Life のプリム名を btn_plus、btn_minus、btn_menu に設定しました。HL はこのプリム名と prim 識別子の一致をもとに、タッチイベントを自動的に正しいハンドラに振り分けます。

つまり、btn_plus #touch_start @main と書けば btn_plus という名前のプリムがタッチされたときだけ実行されます。条件分岐を自分で書く必要はありません。

prims グループ

ボタンが多い場合は prims グループでまとめて処理することもできます。例えば、3文字の接尾辞を持つプリム名であれば、以下のように書けます。

prims btn_*** = btn_add, btn_sub, btn_cfg; は3つの prim をグループ化します。ワイルドカード *** は3文字を表し、btn_ の後に続く部分(add, sub, cfg)にマッチします。

ワイルドカードのルール: * の数は接尾辞の文字数と一致する必要があります。全てのメンバーの接尾辞が同じ長さでなければなりません。今回の btn_plus(4文字)、btn_minus(5文字)、btn_menu(4文字) は接尾辞の長さが揃っていないため、prims グループにはできません。

今回は各ボタンで異なる処理を行い、かつ接尾辞の長さも揃っていないため、個別ハンドラで進めます。

ここまでのコード

ここまでの段階では、まだカウントの加減算は実装していません。次章でメッセージ通信を学んだ後、状態管理と組み合わせて完成させます。

コンパイルしてHUDに入れ、各ボタンをタッチすると対応するメッセージが表示されることを確認してください。

メッセージ通信

HL のメッセージ送信機能について、もう少し詳しく見ていきます。

メッセージ送信の種類

HL の -> 演算子には複数の送信先があります。

リンクメッセージ

複数のスクリプトを持つオブジェクトでは、リンクメッセージでスクリプト間通信を行います。HUDの規模が大きくなると、処理をスクリプト分割することが一般的です。

送信側:

受信側は #linkmsg イベントで受け取ります。

:100 はチャンネル番号です。送信側と受信側で同じ番号を使うことで、メッセージをフィルタリングできます。

チャンネルの使い分け

リンクメッセージのチャンネルは自由に決められますが、用途ごとに分けると管理しやすくなります。

const で定義した定数は、コード中のどこでも使えます。

カウンターHUDでの活用

今回のカウンターHUDは1スクリプトで完結するため、リンクメッセージは使いません。ただし、HUDが複雑化した場合(表示制御を別スクリプトに分離するなど)に必要になる重要な概念です。

次章では context を使った状態管理を導入し、カウンターの増減を実装します。

状態管理とダイアログ

カウンターの値を保持し、メニューダイアログを表示する機能を実装します。

context による状態管理

HL では context を使ってスクリプトの状態を管理します。context 内に宣言した変数は、イベントハンドラをまたいで値が保持されます。

context @main は main スクリプトに紐づく状態です。int count = 0; でカウンターの初期値を0に設定しています。

イベントハンドラで context にアクセスするには、パラメータリストに context を追加します。

カウンターの実装

context を使って、ボタンタッチでカウントを増減するコードを完成させます。

|prim obj, context ctx| のように、パラメータは複数受け取れます。ctx.count.to_str() は整数を文字列に変換するメソッドです。

ダイアログの表示

Menu ボタンをタッチしたときにダイアログを表示します。送信側と受信側で同じチャンネル番号を共有する必要があるため、5 章で見た const を使ってチャンネル番号に名前を付けておきます。リテラルを複数箇所に書くと、片方だけ変更してしまった際に送受信ミスマッチで気づきにくいバグが入ります。

ダイアログ応答用には負のチャンネルを使います。アバターは通常チャット欄からチャンネル 0 や正のチャンネルへしか発言できないため、負のチャンネルにはユーザー入力が混ざらず、ダイアログ応答以外のメッセージで誤動作する事故を防げます。

HL にはダイアログ専用の構文があり、ボタンの配置をコード上で視覚的に表現できます。

この構文の要素:

  • "Counter: " + ctx.count.to_str() — ダイアログのメッセージ
  • |"Reset", "Close"| — ボタンの1行(ソースコード上の並び順で表示されます)
  • @owner:CH_DIALOG — ダイアログの応答を受け取るターゲット(オーナー)とチャンネル(CH_DIALOG = -101)
  • -> world; — ダイアログの送信(固定構文)

ボタンの表示順序: LSL のダイアログはボタンリストを逆順で表示しますが、HL ではコンパイル時に自動的に逆転処理を行います。そのため、ソースコードに書いた並び順がそのままダイアログに反映されます。

listen イベントでダイアログの応答を処理

ダイアログのボタンが押されると、指定したチャンネル(CH_DIALOG)にメッセージが届きます。#listen イベントで受け取ります。

  • :CH_DIALOG — CH_DIALOG チャンネルのメッセージを受信
  • + owner — オーナーからのメッセージのみ受け付ける(セキュリティ対策)
  • |str msg, context ctx| — 受信メッセージと context を受け取る

ここまでの全コード

コンパイルしてHUDに入れてみましょう。+/-ボタンでカウントが増減し、Menuボタンでダイアログが表示されることを確認してください。

primの説明を表示する

これまでのチュートリアルでは #touch_start の |prim obj| パラメータを宣言だけして、中身は使ってきませんでした。本章では obj.name() と obj.desc() でタッチされた prim の情報を取得し、Second Life 側で設定した値を表示します。

Second Life 側の準備

各ボタンの Description(説明) フィールドに任意の文字列を設定します。Second Life の編集パネルから:

  1. HUD を装着または rez
  2. 編集モードで btn_plus、btn_minus、btn_menu を1つずつ選択
  3. General タブの Description に以下を入力
プリム名Description
btn_plusIncrement counter
btn_minusDecrement counter
btn_menuOpen menu

obj.name() と obj.desc()

|prim obj| は touch_start ハンドラに渡される prim インスタンスです。obj.name() で識別子名、obj.desc() で Description を取得できます。

タッチすると以下のように表示されます。

ここまでの全コード

Description はコンパイル後の LSL を変更しなくても Second Life 側だけで書き換えられるため、ボタンに紐づくラベルや設定値を後から差し替える用途にも使えます。

状態に応じて色を変える

context のカウンター値に応じて、Menu ボタンの色を切り替えます。set_color メソッドを使うと prim の色を動的に変更できます。

set_color メソッド

prim の色は #<prim識別子>.set_color|<面>, <色>, <透明度>| で設定します。

引数型説明
面int面番号。std::ALL_SIDES で全面
色vec<R, G, B> 形式(各成分 0.0〜1.0)
透明度float0.0(透明)〜 1.0(不透明)

std::ALL_SIDES は標準ライブラリの定数のため、import standard as std; をスクリプトの先頭に追加します。

カウンター値で分岐

カウンターが正なら緑、負なら赤、0 なら白に切り替えます。if / elif / else で分岐します。

比較演算子: HL でも一般的なコーディングルールとして < / <= のみを使用しています。0 < ctx.count のように左辺に小さい方の値を置きます。

プロシージャに切り出す

カウンターは #init、btn_plus、btn_minus、#listen 内 Reset の 4 箇所で変化します。各所に同じ if / elif / else を書くと冗長なので、色更新処理をプロシージャとして切り出します。

プロシージャは #<プロシージャ名> |<引数リスト>| { ... } で定義し、#<プロシージャ名>|<引数>|; で呼び出します。色を塗る対象も prim 型の引数として受け取るようにすれば、btn_menu 以外のボタンに使い回せます。

ポイント:

  • 仮引数 prim target に対しても、prim 識別子と同じく #target.set_color|...|; の形で # プレフィックスを付けてメソッドを呼び出します
  • 呼び出し側は対象の prim を # なしの識別子で渡します: #update_color|btn_menu, ctx.count|;

ここまでの全コード

コンパイルして HUD に入れ、+/- ボタンでカウンターを増減させると Menu ボタンの色が緑↔白↔赤と切り替わります。これで #cache_linknum が set_color の前提として実際に効いていることが確認できます。

完成とまとめ

カウンターHUDが完成しました。ここまでに学んだ概念を整理します。

完成コード

学んだ概念

概念HL の記法説明
スクリプト宣言script main;LSL出力ファイルの単位
インポートimport standard as std;標準ライブラリの読み込み
定数const int CH_DIALOG = -101;リテラル重複を避けて意図に名前を付ける
プリム定義prim btn_plus;リンクセット内のプリムを識別
プロパティ#cache_linknum, #attachableスクリプトの動作設定
イベント#init, #touch_start, #listenSecond Life イベントへの応答
メッセージ送信"msg" -> owner;様々なターゲットへの送信
状態管理context @main { int count = 0; }イベント間で共有する変数
ダイアログ"msg" |"btn"| @owner:ch -> world;インタラクティブなメニュー表示
制御構文if / elif / else, switch msg { "val" => ...; }条件分岐とパターンマッチ
プロシージャ#name |args| { ... }, #name|args|;副作用処理を切り出して再利用
prim メソッドobj.name(), obj.desc(), #prim.set_color|...|prim の情報取得と外観操作

次のステップ

このチュートリアルではHLの基礎を体験しました。さらに詳しく学ぶには以下のリソースを活用してください:

HLにはこのチュートリアルで扱わなかった多くの機能があります:

  • ライブラリ (.hlib) — 関数や定数を再利用可能なモジュールにまとめる
  • プロシージャ (.hprc) — 共通処理を分離してincludeで使い回す
  • メッセージハンドラ (.hmsg) — リンクメッセージの処理を別ファイルに切り出す
  • 型付きリスト (ilist, slist など) — 型安全なリスト操作
  • map/fold 式 — コレクションの変換・集約
  • テスト (.hltest) — ユニットテストの記述

これらの機能を使うことで、より大規模で保守しやすいプロジェクトを構築できます。